From ad2d50138aea8d8469618861d0861c9f981a7957 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Mon, 23 Mar 2026 11:26:11 +0530 Subject: [PATCH] feat: Dynamically fetch and display featured agents from an API endpoint with image resolution and add agent verification status. --- .../widgets/features_section.dart | 100 +++++++++++++++--- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/lib/features/home/presentation/widgets/features_section.dart b/lib/features/home/presentation/widgets/features_section.dart index 6801d4e..ff7aa49 100644 --- a/lib/features/home/presentation/widgets/features_section.dart +++ b/lib/features/home/presentation/widgets/features_section.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart'; +import 'package:real_estate_mobile/core/network/api_client.dart'; +import 'package:real_estate_mobile/core/utils/image_url_resolver.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'; @@ -88,6 +90,7 @@ class _StaticAgent { final String location; final String experience; final String image; + final bool isVerified; const _StaticAgent({ required this.name, @@ -96,6 +99,7 @@ class _StaticAgent { required this.location, required this.experience, required this.image, + this.isVerified = true, }); } @@ -109,12 +113,15 @@ class FeaturesSection extends ConsumerStatefulWidget { class _FeaturesSectionState extends ConsumerState { int _currentPage = 0; late ScrollController _agentScrollController; + List<_StaticAgent> _agents = List.from(_staticAgents); + bool _agentsLoaded = false; @override void initState() { super.initState(); _agentScrollController = ScrollController(); _agentScrollController.addListener(_onAgentScroll); + _fetchFeaturedAgents(); } @override @@ -124,11 +131,57 @@ class _FeaturesSectionState extends ConsumerState { super.dispose(); } + Future _fetchFeaturedAgents() async { + try { + final dio = ApiClient.instance.dio; + final response = await dio.get('/cms/page/landing'); + final sections = response.data['data'] as List?; + if (sections == null) return; + + for (final section in sections) { + if (section['sectionKey'] == 'features') { + final content = section['content'] as Map?; + final featuredAgents = content?['featuredAgents'] as List?; + if (featuredAgents == null || featuredAgents.isEmpty) return; + + final List<_StaticAgent> fetched = []; + for (final agent in featuredAgents) { + String image = agent['imageUrl'] as String? ?? ''; + if (image.isNotEmpty && !image.startsWith('http') && !image.startsWith('/')) { + try { + image = await ImageUrlResolver.instance.resolve(image) ?? ''; + } catch (_) {} + } + + fetched.add(_StaticAgent( + name: agent['name'] as String? ?? '', + role: agent['role'] as String? ?? '', + rating: agent['rating'] as String? ?? '', + location: agent['location'] as String? ?? '', + experience: agent['experience'] as String? ?? '', + image: image, + )); + } + + if (fetched.isNotEmpty && mounted) { + setState(() { + _agents = fetched; + _agentsLoaded = true; + }); + } + break; + } + } + } catch (_) { + // Keep fallback static agents + } + } + void _onAgentScroll() { if (!_agentScrollController.hasClients) return; - const cardWidth = 254.0 + 16.0; // card width + padding + const cardWidth = 254.0 + 16.0; final page = (_agentScrollController.offset / cardWidth).round(); - if (page != _currentPage && page >= 0 && page < _staticAgents.length) { + if (page != _currentPage && page >= 0 && page < _agents.length) { setState(() => _currentPage = page); } } @@ -191,10 +244,10 @@ class _FeaturesSectionState extends ConsumerState { controller: _agentScrollController, scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 24), - itemCount: _staticAgents.length, + itemCount: _agents.length, itemBuilder: (context, index) => Padding( padding: const EdgeInsets.only(right: 16), - child: _buildStaticAgentCard(_staticAgents[index]), + child: _buildStaticAgentCard(_agents[index]), ), ), ), @@ -202,7 +255,7 @@ class _FeaturesSectionState extends ConsumerState { // Dot indicators Row( mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(_staticAgents.length, (index) { + children: List.generate(_agents.length, (index) { final isActive = index == _currentPage; return Container( width: isActive ? 10 : 8, @@ -307,18 +360,31 @@ class _FeaturesSectionState extends ConsumerState { ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), - child: Image.asset( - agent.image, - width: 254, - height: 161, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( - width: 254, - height: 161, - color: const Color(0xFFC4D9D4), - child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), - ), - ), + child: agent.image.startsWith('http') + ? Image.network( + agent.image, + width: 254, + height: 161, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + width: 254, + height: 161, + color: const Color(0xFFC4D9D4), + child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), + ), + ) + : Image.asset( + (agent.image.isNotEmpty ? agent.image : 'assets/images/agent-anderson.jpg').replaceFirst(RegExp(r'^/'), ''), + width: 254, + height: 161, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + width: 254, + height: 161, + color: const Color(0xFFC4D9D4), + child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), + ), + ), ), // Content