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. const _fallbackImages = [ 'assets/images/professional-1.jpg', 'assets/images/professional-2.jpg', 'assets/images/professional-3.jpg', ]; /// Default agent data — matches web's hardcoded agentsData fallback. /// Used when CMS returns no data from admin. 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'], ), ]; /// Featured professionals carousel shown above the agents/lenders tab section. /// Matches Figma node 49:6248 — compact card with image, name, subtitle, /// verified+rating row, location, and experience. /// /// Data flow (same as web pattern): /// CMS admin → GET /cms/page/landing → topProfessionals.agents /// Fallback → _defaultAgents (hardcoded, like web's agentsData) class FeaturedProfessionalsSection extends ConsumerStatefulWidget { const FeaturedProfessionalsSection({super.key}); @override ConsumerState createState() => _FeaturedProfessionalsSectionState(); } class _FeaturedProfessionalsSectionState extends ConsumerState { 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); final topProfessionals = homeState.content?.topProfessionals; // Use CMS agents if available, otherwise fallback to defaults (same as web) final cmsAgents = topProfessionals?.agents ?? []; final professionals = cmsAgents.isNotEmpty ? cmsAgents : _defaultAgents; return Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Column( children: [ // Card carousel SizedBox( height: 370, child: PageView.builder( controller: _pageController, itemCount: professionals.length, onPageChanged: (index) { setState(() => _currentPage = index); }, itemBuilder: (context, index) { return _buildFeaturedCard(professionals[index], index); }, ), ), const SizedBox(height: 16), // Dot indicators _buildDotIndicators(professionals.length), ], ), ); } Widget _buildFeaturedCard(ProfessionalItem professional, int index) { 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, 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name (Fractul Bold 16px) Text( professional.name, style: const TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), // Subtitle (e.g. "Rental & Investment Consultant") if (professional.subtitle.isNotEmpty) Text( professional.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: [ SvgPicture.asset( 'assets/icons/verified_badge_icon.svg', width: 19, height: 19, placeholderBuilder: (_) => const Icon( Icons.verified, color: Color(0xFF1DA1F2), size: 19, ), ), const SizedBox(width: 6), const Text( 'Verified Agent', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), ), const SizedBox(width: 16), SvgPicture.asset( 'assets/icons/star_rating_icon.svg', width: 19, height: 19, placeholderBuilder: (_) => const Icon( Icons.star, color: Color(0xFFFFDE21), size: 19, ), ), const SizedBox(width: 4), const Text( '4.9 Rating', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), ], ), const SizedBox(height: 6), // Location row with filled icon if (professional.location.isNotEmpty) Row( children: [ SvgPicture.asset( 'assets/icons/location_filled_icon.svg', width: 19, height: 19, placeholderBuilder: (_) => const Icon( Icons.location_on, color: AppColors.accentOrange, size: 19, ), ), const SizedBox(width: 6), Expanded( child: Text( professional.location, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), overflow: TextOverflow.ellipsis, ), ), ], ), const Spacer(), // Experience if (professional.experience.isNotEmpty) RichText( text: TextSpan( children: [ const TextSpan( text: 'Experience: ', style: TextStyle( fontFamily: 'SourceSerif4', 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, ), ], ), ), ), ], ), ); } // ── Image helpers ── 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); } 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; } 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) => Container( width: double.infinity, height: 192, color: AppColors.gradientStart, child: const Icon(Icons.person, size: 60, 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), ), ); }), ); } }