feat: add static agent carousel with navigation dots to the FeaturesSection.

This commit is contained in:
pradeepkumar
2026-03-14 23:33:32 +05:30
parent 630fedc78a
commit 04a57dc137
6 changed files with 386 additions and 138 deletions

View File

@@ -4,12 +4,11 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:shimmer/shimmer.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart';
import 'package:real_estate_mobile/core/widgets/s3_image.dart';
import 'package:real_estate_mobile/features/agents/data/models/agent_profile.dart';
import 'package:real_estate_mobile/features/agents/presentation/providers/agents_provider.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';
/// Featured professionals carousel shown above the agents/lenders tab section.
/// Now fetches REAL agent data from GET /agents API (like web does)
/// instead of using CMS/hardcoded fallback data.
/// Uses CMS data from the admin panel (Top Professionals section).
class FeaturedProfessionalsSection extends ConsumerStatefulWidget {
const FeaturedProfessionalsSection({super.key});
@@ -49,11 +48,13 @@ class _FeaturedProfessionalsSectionState
@override
Widget build(BuildContext context) {
// Real agent data from GET /agents API
final topProState = ref.watch(topProfessionalsProvider);
final professionals = topProState.agents;
final homeState = ref.watch(homeProvider);
final cmsContent = homeState.content?.topProfessionals;
if (topProState.isLoading) {
// Use CMS agents list for the featured carousel
final professionals = cmsContent?.agents ?? [];
if (homeState.isLoading) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Shimmer.fromColors(
@@ -89,23 +90,6 @@ class _FeaturedProfessionalsSectionState
Container(height: 14, width: 180, color: Colors.white),
const SizedBox(height: 12),
Container(height: 12, width: 140, color: Colors.white),
const SizedBox(height: 12),
Row(
children: List.generate(
5,
(_) => Padding(
padding: const EdgeInsets.only(right: 4),
child: Container(
height: 18,
width: 18,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
),
),
),
],
),
),
@@ -156,12 +140,7 @@ class _FeaturedProfessionalsSectionState
);
}
Widget _buildFeaturedCard(AgentProfile agent, int index) {
final rating = agent.averageRating ?? 0.0;
final ratingText = rating > 0 ? '${rating.toStringAsFixed(1)} Rating' : '';
final subtitle = agent.agentType?.name ?? agent.headline ?? '';
final experience = agent.experienceText;
Widget _buildFeaturedCard(ProfessionalItem item, int index) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -175,7 +154,16 @@ class _FeaturedProfessionalsSectionState
// Image
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
child: _buildCardImage(agent, index),
child: item.imageUrl.isNotEmpty
? S3Image(
imageUrl: item.imageUrl,
width: double.infinity,
height: 192,
alignment: Alignment.topCenter,
placeholder: (_) => _buildImagePlaceholder(index),
errorWidget: (_) => _buildImagePlaceholder(index),
)
: _buildImagePlaceholder(index),
),
// Content
@@ -186,7 +174,7 @@ class _FeaturedProfessionalsSectionState
children: [
// Name
Text(
agent.fullName,
item.name,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 16,
@@ -197,10 +185,10 @@ class _FeaturedProfessionalsSectionState
),
const SizedBox(height: 2),
// Subtitle/headline
if (subtitle.isNotEmpty)
// Subtitle
if (item.subtitle.isNotEmpty)
Text(
subtitle,
item.subtitle,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
@@ -212,72 +200,51 @@ class _FeaturedProfessionalsSectionState
),
const SizedBox(height: 6),
// Verified + Rating row
// Verified badge
Row(
children: [
if (agent.isVerified) ...[
SvgPicture.asset(
'assets/icons/verified_badge_icon.svg',
width: 16,
height: 16,
placeholderBuilder: (_) => const Icon(
Icons.verified,
color: Color(0xFF638559),
size: 16,
),
SvgPicture.asset(
'assets/icons/verified_badge_icon.svg',
width: 16,
height: 16,
placeholderBuilder: (_) => const Icon(
Icons.verified,
color: Color(0xFF638559),
size: 16,
),
const SizedBox(width: 4),
const Text(
'Verified Agent',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF638559),
),
),
const SizedBox(width: 4),
const Text(
'Verified Agent',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF638559),
),
],
if (agent.isVerified && ratingText.isNotEmpty)
const SizedBox(width: 16),
if (ratingText.isNotEmpty) ...[
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),
Text(
ratingText,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
],
),
],
),
const SizedBox(height: 6),
// Location row
if (agent.location.isNotEmpty)
if (item.location.isNotEmpty)
Row(
children: [
SvgPicture.asset(
'assets/icons/location_pin_icon.svg',
'assets/icons/location_orange_icon.svg',
width: 16,
height: 16,
placeholderBuilder: (_) => const Icon(
Icons.location_on,
color: AppColors.accentOrange,
size: 16,
),
),
const SizedBox(width: 6),
Expanded(
child: Text(
agent.location,
item.location,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
@@ -292,7 +259,7 @@ class _FeaturedProfessionalsSectionState
const SizedBox(height: 8),
// Experience
if (experience.isNotEmpty)
if (item.experience.isNotEmpty)
RichText(
text: TextSpan(
children: [
@@ -306,7 +273,7 @@ class _FeaturedProfessionalsSectionState
),
),
TextSpan(
text: experience,
text: item.experience,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
@@ -327,19 +294,6 @@ class _FeaturedProfessionalsSectionState
);
}
// -- Image helpers --
Widget _buildCardImage(AgentProfile agent, int index) {
return S3Image(
imageUrl: agent.avatarUrl,
width: double.infinity,
height: 192,
alignment: Alignment.topCenter,
placeholder: (_) => _buildImagePlaceholder(index),
errorWidget: (_) => _buildImagePlaceholder(index),
);
}
Widget _buildImagePlaceholder(int index) {
const colors = [
Color(0xFFC4D9D4),
@@ -354,8 +308,6 @@ class _FeaturedProfessionalsSectionState
);
}
// -- Dot indicators --
Widget _buildDotIndicators(int count) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,