import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:shimmer/shimmer.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/core/widgets/s3_image.dart'; import 'package:real_estate_mobile/features/agents/data/models/agent_profile.dart'; import 'package:real_estate_mobile/features/agents/presentation/providers/agents_provider.dart'; /// Featured professionals carousel shown above the agents/lenders tab section. /// Now fetches REAL agent data from GET /agents API (like web does) /// instead of using CMS/hardcoded fallback data. class FeaturedProfessionalsSection extends ConsumerStatefulWidget { const FeaturedProfessionalsSection({super.key}); @override ConsumerState createState() => _FeaturedProfessionalsSectionState(); } class _FeaturedProfessionalsSectionState extends ConsumerState { int _currentPage = 0; late ScrollController _scrollController; @override void initState() { super.initState(); _scrollController = ScrollController(); _scrollController.addListener(_onScroll); } @override void dispose() { _scrollController.removeListener(_onScroll); _scrollController.dispose(); super.dispose(); } void _onScroll() { if (!_scrollController.hasClients) return; final cardWidth = MediaQuery.of(context).size.width - 80; if (cardWidth <= 0) return; final page = (_scrollController.offset / cardWidth).round(); if (page != _currentPage) { setState(() => _currentPage = page); } } @override Widget build(BuildContext context) { // Real agent data from GET /agents API final topProState = ref.watch(topProfessionalsProvider); final professionals = topProState.agents; if (topProState.isLoading) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Shimmer.fromColors( baseColor: const Color(0xFFE0E0E0), highlightColor: const Color(0xFFF5F5F5), child: Container( height: 370, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 192, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( top: Radius.circular(15), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container(height: 16, width: 160, color: Colors.white), const SizedBox(height: 8), Container(height: 12, width: 100, color: Colors.white), const SizedBox(height: 12), Container(height: 14, width: 180, color: Colors.white), const SizedBox(height: 12), Container(height: 12, width: 140, color: Colors.white), const SizedBox(height: 12), Row( children: List.generate( 5, (_) => Padding( padding: const EdgeInsets.only(right: 4), child: Container( height: 18, width: 18, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), ), ), ), ), ], ), ), ], ), ), ), ); } if (professionals.isEmpty) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Column( children: [ // Card carousel SizedBox( height: 370, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), itemCount: professionals.length, itemBuilder: (context, index) { final cardWidth = MediaQuery.of(context).size.width - 80; return SizedBox( width: cardWidth, child: Padding( padding: EdgeInsets.only( left: index == 0 ? 0 : 8, right: index == professionals.length - 1 ? 0 : 8, ), child: _buildFeaturedCard(professionals[index], index), ), ); }, ), ), const SizedBox(height: 16), // Dot indicators _buildDotIndicators(professionals.length), ], ), ); } Widget _buildFeaturedCard(AgentProfile agent, int index) { final rating = agent.averageRating ?? 0.0; final ratingText = rating > 0 ? '${rating.toStringAsFixed(1)} Rating' : ''; final subtitle = agent.agentType?.name ?? agent.headline ?? ''; final experience = agent.experienceText; return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.primaryDark, width: 0.1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Image ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), child: _buildCardImage(agent, index), ), // Content Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name Text( agent.fullName, style: const TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), // Subtitle/headline if (subtitle.isNotEmpty) Text( subtitle, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), // Verified + Rating row Row( children: [ if (agent.isVerified) ...[ SvgPicture.asset( 'assets/icons/verified_badge_icon.svg', width: 16, height: 16, placeholderBuilder: (_) => const Icon( Icons.verified, color: Color(0xFF638559), size: 16, ), ), const SizedBox(width: 4), const Text( 'Verified Agent', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF638559), ), ), ], if (agent.isVerified && ratingText.isNotEmpty) const SizedBox(width: 16), if (ratingText.isNotEmpty) ...[ SvgPicture.asset( 'assets/icons/star_rating_icon.svg', width: 16, height: 16, placeholderBuilder: (_) => const Icon( Icons.star, color: Color(0xFFFFDE21), size: 16, ), ), const SizedBox(width: 4), Text( ratingText, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), ], ], ), const SizedBox(height: 6), // Location row if (agent.location.isNotEmpty) Row( children: [ SvgPicture.asset( 'assets/icons/location_pin_icon.svg', width: 16, height: 16, ), const SizedBox(width: 6), Expanded( child: Text( agent.location, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), // Experience if (experience.isNotEmpty) RichText( text: TextSpan( children: [ const TextSpan( text: 'Experience: ', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), TextSpan( text: experience, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), ], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ); } // -- Image helpers -- Widget _buildCardImage(AgentProfile agent, int index) { return S3Image( imageUrl: agent.avatarUrl, width: double.infinity, height: 192, alignment: Alignment.topCenter, placeholder: (_) => _buildImagePlaceholder(index), errorWidget: (_) => _buildImagePlaceholder(index), ); } Widget _buildImagePlaceholder(int index) { const colors = [ Color(0xFFC4D9D4), Color(0xFFD4C9B8), Color(0xFFB8C4D4), ]; return Container( width: double.infinity, height: 192, color: colors[index % colors.length], child: const Icon(Icons.person, size: 80, color: AppColors.primaryDark), ); } // -- Dot indicators -- Widget _buildDotIndicators(int count) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(count, (index) { final isActive = index == _currentPage; return Container( width: isActive ? 10 : 8, height: isActive ? 10 : 8, margin: const EdgeInsets.symmetric(horizontal: 4), decoration: BoxDecoration( shape: BoxShape.circle, color: isActive ? AppColors.primaryDark : AppColors.primaryDark.withValues(alpha: 0.3), ), ); }), ); } }