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_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:real_estate_mobile/core/constants/app_colors.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/data/models/landing_page_content.dart';
|
||||||
import 'package:real_estate_mobile/features/home/presentation/providers/home_provider.dart';
|
import 'package:real_estate_mobile/features/home/presentation/providers/home_provider.dart';
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ class _StaticAgent {
|
|||||||
final String location;
|
final String location;
|
||||||
final String experience;
|
final String experience;
|
||||||
final String image;
|
final String image;
|
||||||
|
final bool isVerified;
|
||||||
|
|
||||||
const _StaticAgent({
|
const _StaticAgent({
|
||||||
required this.name,
|
required this.name,
|
||||||
@@ -96,6 +99,7 @@ class _StaticAgent {
|
|||||||
required this.location,
|
required this.location,
|
||||||
required this.experience,
|
required this.experience,
|
||||||
required this.image,
|
required this.image,
|
||||||
|
this.isVerified = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,12 +113,15 @@ class FeaturesSection extends ConsumerStatefulWidget {
|
|||||||
class _FeaturesSectionState extends ConsumerState<FeaturesSection> {
|
class _FeaturesSectionState extends ConsumerState<FeaturesSection> {
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
late ScrollController _agentScrollController;
|
late ScrollController _agentScrollController;
|
||||||
|
List<_StaticAgent> _agents = List.from(_staticAgents);
|
||||||
|
bool _agentsLoaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_agentScrollController = ScrollController();
|
_agentScrollController = ScrollController();
|
||||||
_agentScrollController.addListener(_onAgentScroll);
|
_agentScrollController.addListener(_onAgentScroll);
|
||||||
|
_fetchFeaturedAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -124,11 +131,57 @@ class _FeaturesSectionState extends ConsumerState<FeaturesSection> {
|
|||||||
super.dispose();
|
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() {
|
void _onAgentScroll() {
|
||||||
if (!_agentScrollController.hasClients) return;
|
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();
|
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);
|
setState(() => _currentPage = page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,10 +244,10 @@ class _FeaturesSectionState extends ConsumerState<FeaturesSection> {
|
|||||||
controller: _agentScrollController,
|
controller: _agentScrollController,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
itemCount: _staticAgents.length,
|
itemCount: _agents.length,
|
||||||
itemBuilder: (context, index) => Padding(
|
itemBuilder: (context, index) => Padding(
|
||||||
padding: const EdgeInsets.only(right: 16),
|
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
|
// Dot indicators
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: List.generate(_staticAgents.length, (index) {
|
children: List.generate(_agents.length, (index) {
|
||||||
final isActive = index == _currentPage;
|
final isActive = index == _currentPage;
|
||||||
return Container(
|
return Container(
|
||||||
width: isActive ? 10 : 8,
|
width: isActive ? 10 : 8,
|
||||||
@@ -307,18 +360,31 @@ class _FeaturesSectionState extends ConsumerState<FeaturesSection> {
|
|||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.vertical(top: Radius.circular(15)),
|
const BorderRadius.vertical(top: Radius.circular(15)),
|
||||||
child: Image.asset(
|
child: agent.image.startsWith('http')
|
||||||
agent.image,
|
? Image.network(
|
||||||
width: 254,
|
agent.image,
|
||||||
height: 161,
|
width: 254,
|
||||||
fit: BoxFit.cover,
|
height: 161,
|
||||||
errorBuilder: (_, __, ___) => Container(
|
fit: BoxFit.cover,
|
||||||
width: 254,
|
errorBuilder: (_, __, ___) => Container(
|
||||||
height: 161,
|
width: 254,
|
||||||
color: const Color(0xFFC4D9D4),
|
height: 161,
|
||||||
child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark),
|
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
|
// Content
|
||||||
|
|||||||
Reference in New Issue
Block a user