From e9d069b09baf0910b18efb9cab7d2cc7515071c3 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Thu, 26 Mar 2026 00:14:40 +0530 Subject: [PATCH] feat: Enhance professional card display by reducing section height, adding list padding, applying a box shadow, and aligning card content to the top. --- .../widgets/top_professionals_section.dart | 457 +++++++++--------- 1 file changed, 237 insertions(+), 220 deletions(-) diff --git a/lib/features/home/presentation/widgets/top_professionals_section.dart b/lib/features/home/presentation/widgets/top_professionals_section.dart index 8a20910..776ccab 100644 --- a/lib/features/home/presentation/widgets/top_professionals_section.dart +++ b/lib/features/home/presentation/widgets/top_professionals_section.dart @@ -68,7 +68,8 @@ class _TopProfessionalsSectionState Widget build(BuildContext context) { // CMS data for title/CTA text only final homeState = ref.watch(homeProvider); - final cmsContent = homeState.content?.topProfessionals ?? _defaultCmsContent; + final cmsContent = + homeState.content?.topProfessionals ?? _defaultCmsContent; // Dynamic data from database (top-rated agents/lenders, matching web) final profState = ref.watch(topProfessionalsProvider); @@ -107,11 +108,12 @@ class _TopProfessionalsSectionState else ...[ // Professional Cards - horizontal scroll SizedBox( - height: 540, + height: 510, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only(bottom: 16, left: 10, right: 10), itemCount: activeList.length, itemBuilder: (context, index) { final cardWidth = MediaQuery.of(context).size.width - 48; @@ -122,7 +124,13 @@ class _TopProfessionalsSectionState left: index == 0 ? 0 : 8, right: index == activeList.length - 1 ? 0 : 8, ), - child: _buildProfessionalCard(activeList[index], index: index), + child: Align( + alignment: Alignment.topCenter, + child: _buildProfessionalCard( + activeList[index], + index: index, + ), + ), ), ); }, @@ -289,239 +297,254 @@ class _TopProfessionalsSectionState final expertiseTags = agent.specializations; final experience = agent.experienceText; final locationText = agent.location; - final subtitle = agent.agentType != null ? '(${agent.agentType!.name})' : ''; + final subtitle = agent.agentType != null + ? '(${agent.agentType!.name})' + : ''; final rawRating = agent.averageRating ?? 0.0; final rating = rawRating > 0 ? rawRating : 5.0; return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - border: Border.all(color: AppColors.primaryDark, width: 0.1), - ), - clipBehavior: Clip.antiAlias, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Image (226px per Figma) - SizedBox( - width: double.infinity, - height: 226, - child: ClipRRect( - borderRadius: - const BorderRadius.vertical(top: Radius.circular(15)), - child: imageUrl.isNotEmpty - ? S3Image( - imageUrl: imageUrl, - width: double.infinity, - height: 226, - fit: BoxFit.cover, - alignment: Alignment.topCenter, - errorWidget: (_) => _buildImagePlaceholder(index), - ) - : _buildImagePlaceholder(index), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: AppColors.primaryDark, width: 0.1), + boxShadow: [ + BoxShadow( + color: const Color(0xFFD9D9D9).withValues(alpha: 0.5), + offset: const Offset(0, 10), + blurRadius: 20, + ), + ], + ), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Image (226px per Figma) + SizedBox( + width: double.infinity, + height: 226, + child: ClipRRect( + borderRadius: const BorderRadius.vertical( + top: Radius.circular(15), ), + child: imageUrl.isNotEmpty + ? S3Image( + imageUrl: imageUrl, + width: double.infinity, + height: 226, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + errorWidget: (_) => _buildImagePlaceholder(index), + ) + : _buildImagePlaceholder(index), ), + ), - // Content - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), - child: SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Name + // Content + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Name + Text( + agent.fullName, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, + ), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + + // Subtitle (agent type) + if (subtitle.isNotEmpty) Text( - agent.fullName, + subtitle, style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 10, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + + // Verified badge + if (agent.isVerified) + Row( + children: [ + 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: 6), + const Text( + '\u201CVerified local agent\u201D', + style: TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Color(0xFF638559), + ), + ), + ], + ), + const SizedBox(height: 8), + + // Location + if (locationText.isNotEmpty) ...[ + const Text( + 'Location:', + style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), - overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 4), - - // Subtitle (agent type) - if (subtitle.isNotEmpty) - Text( - subtitle, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 10, - fontWeight: FontWeight.w400, - color: AppColors.primaryDark, + const SizedBox(height: 2), + Row( + children: [ + SvgPicture.asset( + 'assets/icons/location_orange_icon.svg', + width: 16, + height: 16, + placeholderBuilder: (_) => const Icon( + Icons.location_on, + color: AppColors.accentOrange, + size: 16, + ), ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 4), + const SizedBox(width: 4), + Expanded( + child: Text( + locationText, + style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 10, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + ], - // Verified badge - if (agent.isVerified) - Row( - children: [ - SvgPicture.asset( - 'assets/icons/verified_badge_icon.svg', - width: 16, - height: 16, - placeholderBuilder: (_) => const Icon( - Icons.verified, - color: Color(0xFF638559), - size: 16, + // Expertise tags (max 3 visible + "more" pill) + if (expertiseTags.isNotEmpty) ...[ + const Text( + 'Expertise:', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, + ), + ), + const SizedBox(height: 6), + Wrap( + alignment: WrapAlignment.start, + spacing: 6, + runSpacing: 6, + children: [ + ...expertiseTags.take(3).map((tag) => _buildTag(tag)), + if (expertiseTags.length > 3) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + color: AppColors.accentOrange.withValues( + alpha: 0.1, + ), + border: Border.all( + color: AppColors.accentOrange, + width: 0.7, + ), + ), + child: Text( + '+${expertiseTags.length - 3} more', + style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 13, + fontWeight: FontWeight.w600, + color: AppColors.accentOrange, + ), ), ), - const SizedBox(width: 6), - const Text( - '\u201CVerified local agent\u201D', + ], + ), + const SizedBox(height: 12), + ], + + // Experience + if (experience.isNotEmpty) ...[ + RichText( + text: TextSpan( + children: [ + const TextSpan( + text: 'Experience: ', style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, + ), + ), + TextSpan( + text: experience, + style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, - fontWeight: FontWeight.w500, - color: Color(0xFF638559), + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, ), ), ], ), - const SizedBox(height: 8), - - // Location - if (locationText.isNotEmpty) ...[ - const Text( - 'Location:', - style: TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - ), - const SizedBox(height: 2), - Row( - children: [ - SvgPicture.asset( - 'assets/icons/location_orange_icon.svg', - width: 16, - height: 16, - placeholderBuilder: (_) => const Icon( - Icons.location_on, - color: AppColors.accentOrange, - size: 16, - ), - ), - const SizedBox(width: 4), - Expanded( - child: Text( - locationText, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 10, - fontWeight: FontWeight.w400, - color: AppColors.primaryDark, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - const SizedBox(height: 8), - ], - - // Expertise tags (max 3 visible + "more" pill) - if (expertiseTags.isNotEmpty) ...[ - const Text( - 'Expertise:', - style: TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - ), - const SizedBox(height: 6), - Wrap( - alignment: WrapAlignment.start, - spacing: 6, - runSpacing: 6, - children: [ - ...expertiseTags.take(3).map((tag) => _buildTag(tag)), - if (expertiseTags.length > 3) - Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: AppColors.accentOrange.withValues(alpha: 0.1), - border: Border.all(color: AppColors.accentOrange, width: 0.7), - ), - child: Text( - '+${expertiseTags.length - 3} more', - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 13, - fontWeight: FontWeight.w600, - color: AppColors.accentOrange, - ), - ), - ), - ], - ), - const SizedBox(height: 12), - ], - - // Experience - if (experience.isNotEmpty) ...[ - RichText( - text: TextSpan( - children: [ - const TextSpan( - text: 'Experience: ', - style: TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - ), - TextSpan( - text: experience, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.primaryDark, - ), - ), - ], - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 8), - ], - - // Star Rating - Row( - children: List.generate(rating.round().clamp(0, 5), (i) { - return Padding( - padding: const EdgeInsets.only(right: 4), - child: SvgPicture.asset( - 'assets/icons/star_filled_icon.svg', - width: 18, - height: 17, - ), - ); - }), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), + const SizedBox(height: 8), ], - ), - ), + + // Star Rating + Row( + children: List.generate(rating.round().clamp(0, 5), (i) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: SvgPicture.asset( + 'assets/icons/star_filled_icon.svg', + width: 18, + height: 17, + ), + ); + }), + ), + ], ), - ), - ], - ), + ), + ], + ), ); } @@ -603,7 +626,6 @@ class _TopProfessionalsSectionState ); } - Widget _buildAvatarPlaceholder() { return Container( width: double.infinity, @@ -615,11 +637,9 @@ class _TopProfessionalsSectionState Widget _buildTag(String label) { return Container( - height: 28, - padding: const EdgeInsets.symmetric(horizontal: 10), - alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), + borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.primaryDark, width: 0.7), ), child: Text( @@ -629,7 +649,6 @@ class _TopProfessionalsSectionState fontSize: 13, fontWeight: FontWeight.w400, color: AppColors.primaryDark, - height: 1, ), ), ); @@ -638,8 +657,9 @@ class _TopProfessionalsSectionState Widget _buildProgressIndicator(int totalCards) { return LayoutBuilder( builder: (context, constraints) { - final indicatorWidth = - totalCards > 0 ? constraints.maxWidth / totalCards : 103.0; + final indicatorWidth = totalCards > 0 + ? constraints.maxWidth / totalCards + : 103.0; final offset = totalCards > 0 ? (_currentPage * constraints.maxWidth / totalCards) : 0.0; @@ -731,11 +751,8 @@ class _TopProfessionalsSectionState 'assets/icons/building_icon.png', width: 50, height: 38, - errorBuilder: (_, __, ___) => const Icon( - Icons.business, - color: Color(0xFF5BA4A4), - size: 38, - ), + errorBuilder: (_, __, ___) => + const Icon(Icons.business, color: Color(0xFF5BA4A4), size: 38), ), const SizedBox(height: 12), Text(