import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.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'; import 'package:real_estate_mobile/features/home/data/models/landing_page_content.dart'; import 'package:real_estate_mobile/features/home/presentation/providers/home_provider.dart'; /// Local fallback images for professionals when API returns no avatar. const _fallbackImages = [ 'assets/images/professional-1.jpg', 'assets/images/professional-2.jpg', 'assets/images/professional-3.jpg', ]; /// Default CMS content for title/CTA text only. const _defaultCmsContent = TopProfessionalsContent( title: '"Meet top real estate professionals"', ctaText: 'Discover 5,000+ Top Real Estate Agents in Our Network.', ctaButtonText: 'See All Agents', agents: [], lenders: [], ); class TopProfessionalsSection extends ConsumerStatefulWidget { const TopProfessionalsSection({super.key}); @override ConsumerState createState() => _TopProfessionalsSectionState(); } class _TopProfessionalsSectionState extends ConsumerState { int _currentPage = 0; String _activeTab = 'agents'; 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 - 48; if (cardWidth <= 0) return; final page = (_scrollController.offset / cardWidth).round(); if (page != _currentPage) { setState(() => _currentPage = page); } } @override Widget build(BuildContext context) { // CMS data for title/CTA text only final homeState = ref.watch(homeProvider); final cmsContent = homeState.content?.topProfessionals ?? _defaultCmsContent; // Dynamic data from database (top-rated agents/lenders, matching web) final profState = ref.watch(topProfessionalsProvider); final activeList = _activeTab == 'agents' ? profState.agents : profState.lenders; final isLoading = profState.isLoading || homeState.isLoading; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ Text( cmsContent.title.isNotEmpty ? cmsContent.title : _defaultCmsContent.title, textAlign: TextAlign.center, style: const TextStyle( fontFamily: 'Fractul', fontSize: 25, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 24), // Agents / Lenders Tab _buildCategoryTab(_activeTab), const SizedBox(height: 24), // Content if (isLoading) _buildShimmerCards() else if (activeList.isEmpty) _buildEmptyState(_activeTab) else ...[ // Professional Cards - horizontal scroll SizedBox( height: 540, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), itemCount: activeList.length, itemBuilder: (context, index) { final cardWidth = MediaQuery.of(context).size.width - 48; return SizedBox( width: cardWidth, child: Padding( padding: EdgeInsets.only( left: index == 0 ? 0 : 8, right: index == activeList.length - 1 ? 0 : 8, ), child: _buildProfessionalCard(activeList[index], index: index), ), ); }, ), ), const SizedBox(height: 12), // Progress indicator _buildProgressIndicator(activeList.length), ], const SizedBox(height: 32), // See All Agents CTA _buildSeeAllAgentsCta(cmsContent), ], ), ); } Widget _buildCategoryTab(String activeTab) { return Container( height: 45, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.primaryDark, width: 0.7), ), child: Row( children: [ // Agents tab Expanded( child: GestureDetector( onTap: () { setState(() { _activeTab = 'agents'; _currentPage = 0; }); if (_scrollController.hasClients) { _scrollController.jumpTo(0); } }, child: Container( alignment: Alignment.center, decoration: BoxDecoration( color: activeTab == 'agents' ? AppColors.accentOrange : Colors.transparent, borderRadius: BorderRadius.circular(15), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ SvgPicture.asset( 'assets/icons/agents_tab_icon.svg', width: 24, height: 24, colorFilter: ColorFilter.mode( activeTab == 'agents' ? Colors.white : AppColors.primaryDark, BlendMode.srcIn, ), placeholderBuilder: (_) => Icon( Icons.people, color: activeTab == 'agents' ? Colors.white : AppColors.primaryDark, size: 24, ), ), const SizedBox(width: 8), Text( 'Agents', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 17, fontWeight: FontWeight.w600, color: activeTab == 'agents' ? Colors.white : AppColors.primaryDark, ), ), ], ), ), ), ), // Lenders tab Expanded( child: GestureDetector( onTap: () { setState(() { _activeTab = 'lenders'; _currentPage = 0; }); if (_scrollController.hasClients) { _scrollController.jumpTo(0); } }, child: Container( alignment: Alignment.center, decoration: BoxDecoration( color: activeTab == 'lenders' ? AppColors.accentOrange : Colors.transparent, borderRadius: BorderRadius.circular(15), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ SvgPicture.asset( 'assets/icons/lenders_tab_icon.svg', width: 24, height: 24, colorFilter: ColorFilter.mode( activeTab == 'lenders' ? Colors.white : AppColors.primaryDark, BlendMode.srcIn, ), placeholderBuilder: (_) => Icon( Icons.calendar_today, color: activeTab == 'lenders' ? Colors.white : AppColors.primaryDark, size: 24, ), ), const SizedBox(width: 8), Text( 'Lenders', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 17, fontWeight: FontWeight.w600, color: activeTab == 'lenders' ? Colors.white : AppColors.primaryDark, ), ), ], ), ), ), ), ], ), ); } Widget _buildImagePlaceholder(int index) { final assetPath = _fallbackImages[index % _fallbackImages.length]; return Image.asset( assetPath, width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _buildAvatarPlaceholder(), ); } // -- Professional Card using dynamic AgentProfile data (matching web) -- Widget _buildProfessionalCard(AgentProfile agent, {int index = 0}) { final imageUrl = agent.avatarUrl ?? ''; final expertiseTags = agent.specializations; final experience = agent.experienceText; final locationText = agent.location; final subtitle = agent.agentType != null ? '(${agent.agentType!.name})' : ''; final rawRating = agent.averageRating ?? 0.0; final rating = rawRating > 0 ? rawRating : 5.0; return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.primaryDark, width: 0.1), ), clipBehavior: Clip.antiAlias, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image (226px per Figma) SizedBox( width: double.infinity, height: 226, child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), child: imageUrl.isNotEmpty ? S3Image( imageUrl: imageUrl, width: double.infinity, height: 226, fit: BoxFit.cover, alignment: Alignment.topCenter, errorWidget: (_) => _buildImagePlaceholder(index), ) : _buildImagePlaceholder(index), ), ), // Content Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name Text( agent.fullName, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // Subtitle (agent type) if (subtitle.isNotEmpty) Text( subtitle, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 10, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // Verified badge if (agent.isVerified) Row( children: [ 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: 6), const Text( '\u201CVerified local agent\u201D', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF638559), ), ), ], ), const SizedBox(height: 8), // Location if (locationText.isNotEmpty) ...[ const Text( 'Location:', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 2), Row( children: [ SvgPicture.asset( 'assets/icons/location_orange_icon.svg', width: 16, height: 16, placeholderBuilder: (_) => const Icon( Icons.location_on, color: AppColors.accentOrange, size: 16, ), ), const SizedBox(width: 4), Expanded( child: Text( locationText, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 10, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), ], // Expertise tags (max 3 visible + "more" pill) if (expertiseTags.isNotEmpty) ...[ const Text( 'Expertise:', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 6), Wrap( alignment: WrapAlignment.start, spacing: 6, runSpacing: 6, children: [ ...expertiseTags.take(3).map((tag) => _buildTag(tag)), if (expertiseTags.length > 3) Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: AppColors.accentOrange.withValues(alpha: 0.1), border: Border.all(color: AppColors.accentOrange, width: 0.7), ), child: Text( '+${expertiseTags.length - 3} more', style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.accentOrange, ), ), ), ], ), const SizedBox(height: 12), ], // Experience if (experience.isNotEmpty) ...[ RichText( text: TextSpan( children: [ const TextSpan( text: 'Experience: ', style: TextStyle( fontFamily: 'Fractul', 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, ), const SizedBox(height: 8), ], // Star Rating Row( children: List.generate(rating.round().clamp(0, 5), (i) { return Padding( padding: const EdgeInsets.only(right: 4), child: SvgPicture.asset( 'assets/icons/star_filled_icon.svg', width: 18, height: 17, ), ); }), ), ], ), ), ), ), ], ), ); } Widget _buildShimmerCards() { return SizedBox( height: 540, child: Shimmer.fromColors( baseColor: const Color(0xFFE0E0E0), highlightColor: const Color(0xFFF5F5F5), child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image placeholder Container( height: 226, 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( 3, (_) => Container( height: 28, width: 70, margin: const EdgeInsets.only(right: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), ), ), ), ), const SizedBox(height: 12), Container(height: 14, width: 200, 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, ), ), ), ), ), ], ), ), ], ), ), ), ); } Widget _buildAvatarPlaceholder() { return Container( width: double.infinity, height: 226, color: AppColors.gradientStart, child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), ); } Widget _buildTag(String label) { return Container( height: 28, padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.primaryDark, width: 0.7), ), child: Text( label, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 13, fontWeight: FontWeight.w400, color: AppColors.primaryDark, height: 1, ), ), ); } Widget _buildProgressIndicator(int totalCards) { return LayoutBuilder( builder: (context, constraints) { final indicatorWidth = totalCards > 0 ? constraints.maxWidth / totalCards : 103.0; final offset = totalCards > 0 ? (_currentPage * constraints.maxWidth / totalCards) : 0.0; return Stack( children: [ Container( height: 5, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.primaryDark, width: 0.1), ), ), AnimatedPositioned( duration: const Duration(milliseconds: 200), left: offset, child: Container( height: 5, width: indicatorWidth.clamp(40.0, 150.0), decoration: BoxDecoration( color: AppColors.primaryDark, borderRadius: BorderRadius.circular(15), ), ), ), ], ); }, ); } Widget _buildEmptyState(String activeTab) { return Padding( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ Icon( activeTab == 'agents' ? Icons.people_outline : Icons.account_balance_outlined, color: AppColors.hintText, size: 48, ), const SizedBox(height: 12), Text( 'No ${activeTab == 'agents' ? 'agents' : 'lenders'} found', style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.hintText, ), ), ], ), ); } Widget _buildSeeAllAgentsCta(TopProfessionalsContent content) { final ctaText = content.ctaText.isNotEmpty ? content.ctaText : _defaultCmsContent.ctaText; final ctaButtonText = content.ctaButtonText.isNotEmpty ? content.ctaButtonText : _defaultCmsContent.ctaButtonText; return Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [AppColors.gradientStart, AppColors.gradientEnd], ), borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.primaryDark, width: 0.2), boxShadow: const [ BoxShadow( color: Color(0xFFD9D9D9), offset: Offset(10, 0), blurRadius: 20, ), ], ), child: Column( children: [ Image.asset( 'assets/icons/building_icon.png', width: 50, height: 38, errorBuilder: (_, __, ___) => const Icon( Icons.business, color: Color(0xFF5BA4A4), size: 38, ), ), const SizedBox(height: 12), Text( ctaText, textAlign: TextAlign.center, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => context.push('/agents/search'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.accentOrange, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), elevation: 0, ), child: Text( ctaButtonText, style: const TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white, ), ), ), ), ], ), ); } }