feat: Dynamically fetch and display featured agents from an API endpoint with image resolution and add agent verification status.
This commit is contained in:
@@ -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<FeaturesSection> {
|
||||
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<FeaturesSection> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _fetchFeaturedAgents() async {
|
||||
try {
|
||||
final dio = ApiClient.instance.dio;
|
||||
final response = await dio.get('/cms/page/landing');
|
||||
final sections = response.data['data'] as List<dynamic>?;
|
||||
if (sections == null) return;
|
||||
|
||||
for (final section in sections) {
|
||||
if (section['sectionKey'] == 'features') {
|
||||
final content = section['content'] as Map<String, dynamic>?;
|
||||
final featuredAgents = content?['featuredAgents'] as List<dynamic>?;
|
||||
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<FeaturesSection> {
|
||||
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<FeaturesSection> {
|
||||
// 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<FeaturesSection> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user