feat: add static agent carousel with navigation dots to the FeaturesSection.
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user