From 751388ed0037e18819c6dbfee3a5a785e408de1f Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sat, 14 Mar 2026 21:30:37 +0530 Subject: [PATCH] feat: Fetch real agent data for featured professionals and update associated icons and image placeholders. --- assets/icons/hire_quickly_icon.svg | 4 +- assets/icons/quote_icon.svg | 4 + assets/icons/top_rated_icon.svg | 6 +- assets/icons/trusted_icon.svg | 7 +- assets/icons/verified_icon.svg | 6 +- .../providers/agents_provider.dart | 3 +- .../screens/agent_detail_screen.dart | 241 +++++++----------- .../featured_professionals_section.dart | 27 +- .../widgets/features_section.dart | 7 + 9 files changed, 124 insertions(+), 181 deletions(-) create mode 100644 assets/icons/quote_icon.svg diff --git a/assets/icons/hire_quickly_icon.svg b/assets/icons/hire_quickly_icon.svg index c006e74..518b135 100644 --- a/assets/icons/hire_quickly_icon.svg +++ b/assets/icons/hire_quickly_icon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/quote_icon.svg b/assets/icons/quote_icon.svg new file mode 100644 index 0000000..b299b26 --- /dev/null +++ b/assets/icons/quote_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/top_rated_icon.svg b/assets/icons/top_rated_icon.svg index 87b97a8..4e4f264 100644 --- a/assets/icons/top_rated_icon.svg +++ b/assets/icons/top_rated_icon.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/assets/icons/trusted_icon.svg b/assets/icons/trusted_icon.svg index 3204dea..6686e04 100644 --- a/assets/icons/trusted_icon.svg +++ b/assets/icons/trusted_icon.svg @@ -1,5 +1,4 @@ - - - - + + + diff --git a/assets/icons/verified_icon.svg b/assets/icons/verified_icon.svg index 9b9b097..b09669a 100644 --- a/assets/icons/verified_icon.svg +++ b/assets/icons/verified_icon.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/lib/features/agents/presentation/providers/agents_provider.dart b/lib/features/agents/presentation/providers/agents_provider.dart index 961b56e..5e46d01 100644 --- a/lib/features/agents/presentation/providers/agents_provider.dart +++ b/lib/features/agents/presentation/providers/agents_provider.dart @@ -72,7 +72,8 @@ class TopProfessionalsNotifier extends StateNotifier { final name = type.name.toLowerCase(); if (name.contains('lender') || name.contains('lending')) { lenderTypeId = type.id; - } else if (name.contains('agent') || + } else if (name.contains('professional') || + name.contains('agent') || name.contains('realtor') || name.contains('broker')) { agentTypeId = type.id; diff --git a/lib/features/agents/presentation/screens/agent_detail_screen.dart b/lib/features/agents/presentation/screens/agent_detail_screen.dart index 68c975e..1aadb11 100644 --- a/lib/features/agents/presentation/screens/agent_detail_screen.dart +++ b/lib/features/agents/presentation/screens/agent_detail_screen.dart @@ -1045,7 +1045,7 @@ class _AgentDetailScreenState extends ConsumerState { ), const SizedBox(height: 16), SizedBox( - height: 320, + height: 380, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: state.testimonials.length, @@ -1130,7 +1130,7 @@ class _ExpandableChipsSectionState extends State<_ExpandableChipsSection> { ), ), ), - if (hasMore) + if (hasMore || _expanded) GestureDetector( onTap: () => setState(() => _expanded = !_expanded), child: IntrinsicWidth( @@ -1199,7 +1199,7 @@ class _ExpandableCertificationsSection extends StatefulWidget { const _ExpandableCertificationsSection({ required this.certs, - this.initialCount = 3, + this.initialCount = 2, }); @override @@ -1278,7 +1278,7 @@ class _ExpandableCertificationsSectionState ), ), ), - if (hasMore) + if (hasMore || _expanded) Center( child: GestureDetector( onTap: () => setState(() => _expanded = !_expanded), @@ -1444,7 +1444,7 @@ class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> { } } -// ── Testimonial Card ── +// ── Testimonial Card (matches Figma & web TestimonialCard) ── class _TestimonialCard extends StatelessWidget { final Map data; @@ -1458,165 +1458,108 @@ class _TestimonialCard extends StatelessWidget { final authorRole = data['authorRole'] as String? ?? ''; final rating = (data['rating'] as num?)?.toInt() ?? 5; - return SizedBox( - width: 320, + return Container( + width: 300, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: AppColors.primaryDark.withValues(alpha: 0.1), + width: 1, + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Main card body with speech bubble shape + // Quote icon + SvgPicture.asset( + 'assets/icons/quote_icon.svg', + width: 23, + height: 13, + ), + const SizedBox(height: 12), + // Title snippet (bold) + Text( + text.length > 35 ? '${text.substring(0, 35)} ..' : text, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.primaryDark, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 12), + // Full text Expanded( - child: Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(20, 16, 20, 20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow( - color: const Color(0xFFD9D9D9).withValues(alpha: 0.5), - offset: const Offset(0, 10), - blurRadius: 20, - ), - ], + child: Text( + text, + style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + height: 1.4, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Quote icon - const Text( - '\u201C', - style: TextStyle( - fontFamily: 'Fractul', - fontSize: 36, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - height: 0.8, - ), - ), - const SizedBox(height: 6), - // Title snippet - Text( - text.length > 35 ? '${text.substring(0, 35)} ..' : text, - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 10), - // Full text - Expanded( - child: Text( - text, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 14, - color: AppColors.primaryDark, - height: 1.5, - ), - overflow: TextOverflow.fade, - ), - ), - ], + overflow: TextOverflow.fade, + ), + ), + const SizedBox(height: 12), + // Divider + Divider( + color: AppColors.primaryDark.withValues(alpha: 0.08), + height: 1, + ), + const SizedBox(height: 12), + // Star rating + Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 5, + (i) => Padding( + padding: const EdgeInsets.only(right: 2), + child: Icon( + i < rating ? Icons.star : Icons.star_border, + size: 18, + color: i < rating + ? const Color(0xFFFFDE21) + : AppColors.primaryDark.withValues(alpha: 0.3), + ), ), ), ), - // Speech bubble triangle - Padding( - padding: const EdgeInsets.only(left: 30), - child: CustomPaint( - size: const Size(16, 10), - painter: _TrianglePainter(color: Colors.white), + const SizedBox(height: 8), + // Author name + Text( + authorName, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, ), ), - const SizedBox(height: 6), - // Stars + Author info - Padding( - padding: const EdgeInsets.only(left: 4), - child: Row( - children: [ - // Stars - Row( - mainAxisSize: MainAxisSize.min, - children: List.generate( - 5, - (i) => Icon( - i < rating ? Icons.star : Icons.star_border, - size: 16, - color: i < rating - ? AppColors.accentOrange - : AppColors.primaryDark.withValues(alpha: 0.3), - ), - ), - ), - const SizedBox(width: 10), - // Author - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - authorName, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 14, - fontWeight: FontWeight.w500, - color: AppColors.primaryDark, - ), - ), - if (authorRole.isNotEmpty) - Text( - authorRole, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.primaryDark, - ), - ), - ], - ), - ), - ], + // Author role + if (authorRole.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + authorRole, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.primaryDark, + ), ), - ), + ], ], ), ); } } -// ── Triangle painter for speech bubble ── -class _TrianglePainter extends CustomPainter { - final Color color; - _TrianglePainter({required this.color}); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color - ..style = PaintingStyle.fill; - final shadowPaint = Paint() - ..color = const Color(0xFFD9D9D9).withAlpha(40) - ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4); - - final path = Path() - ..moveTo(0, 0) - ..lineTo(size.width, 0) - ..lineTo(size.width / 2, size.height) - ..close(); - - canvas.drawPath(path, shadowPaint); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} - // ── Connect Modal Content (matches web ConnectRequestModal) ── class _ConnectModalContent extends StatefulWidget { @@ -1683,7 +1626,9 @@ class _ConnectModalContentState extends State<_ConnectModalContent> { 24, 24 + MediaQuery.of(context).padding.bottom, ), - child: _success ? _buildSuccessContent() : _buildFormContent(), + child: SingleChildScrollView( + child: _success ? _buildSuccessContent() : _buildFormContent(), + ), ), ); } diff --git a/lib/features/home/presentation/widgets/featured_professionals_section.dart b/lib/features/home/presentation/widgets/featured_professionals_section.dart index 59929e0..b1b96fc 100644 --- a/lib/features/home/presentation/widgets/featured_professionals_section.dart +++ b/lib/features/home/presentation/widgets/featured_professionals_section.dart @@ -7,13 +7,6 @@ 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'; -/// Local fallback images for professionals. -const _fallbackImages = [ - 'assets/images/professional-1.jpg', - 'assets/images/professional-2.jpg', - 'assets/images/professional-3.jpg', -]; - /// 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. @@ -148,7 +141,7 @@ class _FeaturedProfessionalsSectionState left: index == 0 ? 0 : 8, right: index == professionals.length - 1 ? 0 : 8, ), - child: Container(), + child: _buildFeaturedCard(professionals[index], index), ), ); }, @@ -348,18 +341,16 @@ class _FeaturedProfessionalsSectionState } Widget _buildImagePlaceholder(int index) { - final assetPath = _fallbackImages[index % _fallbackImages.length]; - return Image.asset( - assetPath, + const colors = [ + Color(0xFFC4D9D4), + Color(0xFFD4C9B8), + Color(0xFFB8C4D4), + ]; + return Container( width: double.infinity, height: 192, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => Container( - width: double.infinity, - height: 192, - color: AppColors.gradientStart, - child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), - ), + color: colors[index % colors.length], + child: const Icon(Icons.person, size: 80, color: AppColors.primaryDark), ); } diff --git a/lib/features/home/presentation/widgets/features_section.dart b/lib/features/home/presentation/widgets/features_section.dart index 13328f7..19728d9 100644 --- a/lib/features/home/presentation/widgets/features_section.dart +++ b/lib/features/home/presentation/widgets/features_section.dart @@ -39,11 +39,18 @@ const _defaultFeatures = FeaturesContent( ); /// Map of feature icon keys to local SVG asset paths and Material fallback icons. +/// Supports both default slug keys and CMS web-style icon paths. const _iconMap = { + // Default slug-based keys 'hire_quickly': ('assets/icons/hire_quickly_icon.svg', Icons.bolt), 'verified': ('assets/icons/verified_icon.svg', Icons.verified_user_outlined), 'top_rated': ('assets/icons/top_rated_icon.svg', Icons.star_outline), 'trusted': ('assets/icons/trusted_icon.svg', Icons.people_outline), + // CMS web-path keys (from CMS iconPath field) + '/assets/icons/hourglass-icon.svg': ('assets/icons/hire_quickly_icon.svg', Icons.bolt), + '/assets/icons/verified-badge-icon.svg': ('assets/icons/verified_icon.svg', Icons.verified_user_outlined), + '/assets/icons/star-orange-icon.svg': ('assets/icons/top_rated_icon.svg', Icons.star_outline), + '/assets/icons/trusted-people-icon.svg': ('assets/icons/trusted_icon.svg', Icons.people_outline), }; class FeaturesSection extends ConsumerWidget {