import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:real_estate_mobile/config/app_config.dart'; import 'package:real_estate_mobile/core/constants/app_colors.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 CMS returns no image. const _fallbackImages = [ 'assets/images/professional-1.jpg', 'assets/images/professional-2.jpg', 'assets/images/professional-3.jpg', ]; /// Default agents data — matches web's hardcoded agentsData fallback. const _defaultAgents = [ ProfessionalItem( name: 'Arjun Mehta', subtitle: 'Residential Property Expert', location: 'San Francisco, CA', experience: '10+ years in the real estate industry.', expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'], ), ProfessionalItem( name: 'Anderson', subtitle: 'Rental & Investment Consultant', location: 'New York', experience: '7+ years in the real estate industry.', expertise: ['Residential', 'Rental', 'Commercial'], ), ProfessionalItem( name: 'Sarah Johnson', subtitle: 'Luxury Real Estate Specialist', location: 'Los Angeles, CA', experience: '12+ years in the real estate industry.', expertise: ['Luxury', 'Residential', 'Investment'], ), ]; /// Default lenders data — matches web's hardcoded lendersData fallback. const _defaultLenders = [ ProfessionalItem( name: 'Sarah Johnson', subtitle: 'Mortgage Specialist', location: 'Los Angeles, CA', experience: '10+ years in mortgage lending.', expertise: ['FHA Loans', 'Conventional', 'VA Loans', 'Refinancing', 'Jumbo'], ), ProfessionalItem( name: 'Michael Chen', subtitle: 'Senior Loan Officer', location: 'Seattle, WA', experience: '7+ years in lending.', expertise: ['Jumbo Loans', 'Refinancing', 'Investment', 'First-time', 'USDA'], ), ProfessionalItem( name: 'Emily Davis', subtitle: 'Home Loan Advisor', location: 'Austin, TX', experience: '5+ years in mortgage industry.', expertise: ['First-time Buyers', 'FHA', 'USDA Loans', 'VA Loans', 'Conventional'], ), ]; /// Default content — matches web's defaultContent pattern. const _defaultContent = TopProfessionalsContent( title: '"Meet top real estate professionals"', ctaText: 'Discover 5,000+ Top Real Estate Agents in Our Network.', ctaButtonText: 'See All Agents', agents: _defaultAgents, lenders: _defaultLenders, ); class TopProfessionalsSection extends ConsumerStatefulWidget { const TopProfessionalsSection({super.key}); @override ConsumerState createState() => _TopProfessionalsSectionState(); } class _TopProfessionalsSectionState extends ConsumerState { String _activeTab = 'agents'; int _currentPage = 0; late PageController _pageController; @override void initState() { super.initState(); _pageController = PageController(); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final homeState = ref.watch(homeProvider); // Use CMS data if available, otherwise fallback to defaults (same as web) final data = homeState.content?.topProfessionals ?? _defaultContent; final activeList = _activeTab == 'agents' ? data.agents : data.lenders; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ Text( data.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(), const SizedBox(height: 24), // Content if (homeState.isLoading) const Padding( padding: EdgeInsets.symmetric(vertical: 40), child: CircularProgressIndicator( color: AppColors.accentOrange, ), ) else if (homeState.error != null) _buildErrorState(homeState.error!) else if (activeList.isEmpty) _buildEmptyState() else ...[ // Professional Cards - horizontal scroll SizedBox( height: 500, child: PageView.builder( controller: _pageController, itemCount: activeList.length, onPageChanged: (index) { setState(() => _currentPage = index); }, itemBuilder: (context, index) { return _buildProfessionalCard(activeList[index], index: index); }, ), ), const SizedBox(height: 12), // Progress indicator _buildProgressIndicator(activeList.length), ], const SizedBox(height: 32), // See All Agents CTA _buildSeeAllAgentsCta(data), ], ), ); } Widget _buildCategoryTab() { 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 (_pageController.hasClients) { _pageController.jumpToPage(0); } }, child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: _activeTab == 'agents' ? AppColors.accentOrange : Colors.transparent, borderRadius: BorderRadius.circular(15), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, 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 (_pageController.hasClients) { _pageController.jumpToPage(0); } }, child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: _activeTab == 'lenders' ? AppColors.accentOrange : Colors.transparent, borderRadius: BorderRadius.circular(15), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, 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, ), ), ], ), ), ), ), ], ), ); } /// Resolves an image URL: full URL → use as-is, relative → prepend base URL. String? _resolveImageUrl(String imageUrl) { if (imageUrl.isEmpty) return null; if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { return imageUrl; } final baseUrl = AppConfig.apiBaseUrl; if (baseUrl.isNotEmpty) { return '$baseUrl$imageUrl'; } return null; } /// Builds the card image: CachedNetworkImage for valid URLs, local asset fallback otherwise. Widget _buildCardImage(ProfessionalItem professional, int index) { final resolvedUrl = _resolveImageUrl(professional.imageUrl); if (resolvedUrl != null) { return CachedNetworkImage( imageUrl: resolvedUrl, width: double.infinity, height: 192, fit: BoxFit.cover, placeholder: (context, url) => _buildImagePlaceholder(index), errorWidget: (context, url, error) => _buildImagePlaceholder(index), ); } return _buildImagePlaceholder(index); } /// Shows a local fallback professional image (cycles through 3 images). Widget _buildImagePlaceholder(int index) { final assetPath = _fallbackImages[index % _fallbackImages.length]; return Image.asset( assetPath, width: double.infinity, height: 192, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _buildAvatarPlaceholder(), ); } // ── Unified Professional Card (matches Figma design for both tabs) ── Widget _buildProfessionalCard(ProfessionalItem professional, {int index = 0}) { 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, children: [ // Image ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), child: _buildCardImage(professional, index), ), // Content Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Row 1: Name + verified badge + star Rating Row( children: [ Expanded( child: Row( children: [ Flexible( child: Text( professional.name, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 6), SvgPicture.asset( 'assets/icons/verified_badge_icon.svg', width: 16, height: 16, placeholderBuilder: (_) => const Icon( Icons.verified, color: Color(0xFF1DA1F2), size: 16, ), ), ], ), ), 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), const Text( 'Rating', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), ], ), const SizedBox(height: 4), // Row 2: (Subtitle) in parentheses if (professional.subtitle.isNotEmpty) Text( '(${professional.subtitle})', style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 10, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // Row 3: Verified badge + "Verified local agent" in green Row( children: [ SvgPicture.asset( 'assets/icons/verified_badge_icon.svg', width: 14, height: 14, placeholderBuilder: (_) => const Icon( Icons.verified, color: Color(0xFF638559), size: 14, ), ), const SizedBox(width: 4), const Text( '"Verified local agent"', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 10, fontWeight: FontWeight.w400, color: Color(0xFF638559), ), ), ], ), const SizedBox(height: 8), // Row 4: Location: label if (professional.location.isNotEmpty) ...[ const Text( 'Location:', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 2), // Pin icon + location text Row( children: [ SvgPicture.asset( 'assets/icons/location_pin_icon.svg', width: 14, height: 14, placeholderBuilder: (_) => const Icon( Icons.location_on, color: AppColors.accentOrange, size: 14, ), ), const SizedBox(width: 4), Expanded( child: Text( professional.location, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 10, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), ], // Row 5: Expertise: label + tags if (professional.expertise.isNotEmpty) ...[ const Text( 'Expertise:', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 6), // Tags as bordered pills Wrap( spacing: 8, runSpacing: 6, children: professional.expertise .take(6) .map((tag) => _buildTag(tag)) .toList(), ), ], const Spacer(), // Experience text if (professional.experience.isNotEmpty) RichText( text: TextSpan( children: [ const TextSpan( text: 'Experience: ', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), TextSpan( text: professional.experience, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), ], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), // 5 star rating icons row at bottom Row( children: List.generate( 5, (index) => Padding( padding: const EdgeInsets.only(right: 4), child: SvgPicture.asset( 'assets/icons/star_rating_icon.svg', width: 18, height: 18, placeholderBuilder: (_) => const Icon( Icons.star, color: Color(0xFFFFDE21), size: 18, ), ), ), ), ), ], ), ), ), ], ), ); } Widget _buildAvatarPlaceholder() { return Container( width: double.infinity, height: 192, color: AppColors.gradientStart, child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), ); } Widget _buildTag(String label) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), 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, ), ), ); } 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 _buildErrorState(String error) { return Padding( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ const Icon( Icons.error_outline, color: AppColors.accentOrange, size: 48, ), const SizedBox(height: 12), Text( error, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), TextButton( onPressed: () => ref.read(homeProvider.notifier).loadContent(), child: const Text( 'Retry', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.accentOrange, ), ), ), ], ), ); } Widget _buildEmptyState() { 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) { 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/network_icon.png', width: 30, height: 23, errorBuilder: (_, e, s) => const Icon( Icons.hub, color: AppColors.primaryDark, size: 30, ), ), const SizedBox(height: 12), Text( content.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: () {}, style: ElevatedButton.styleFrom( backgroundColor: AppColors.primaryDark, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: Text( content.ctaButtonText, style: const TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w700, ), ), ), ), ], ), ); } }