diff --git a/lib/features/agents/presentation/screens/agent_search_screen.dart b/lib/features/agents/presentation/screens/agent_search_screen.dart index 0279d01..954b1e7 100644 --- a/lib/features/agents/presentation/screens/agent_search_screen.dart +++ b/lib/features/agents/presentation/screens/agent_search_screen.dart @@ -96,6 +96,11 @@ class _AgentSearchScreenState extends ConsumerState { onClearAll: () { ref.read(searchAgentsProvider.notifier).clearFilters(); }, + onListingTypeChange: (agentTypeId) { + ref + .read(searchAgentsProvider.notifier) + .filterByAgentType(agentTypeId); + }, ), ); } diff --git a/lib/features/agents/presentation/widgets/agent_filter_sheet.dart b/lib/features/agents/presentation/widgets/agent_filter_sheet.dart index 4028528..91594b8 100644 --- a/lib/features/agents/presentation/widgets/agent_filter_sheet.dart +++ b/lib/features/agents/presentation/widgets/agent_filter_sheet.dart @@ -10,6 +10,7 @@ class AgentFilterSheet extends StatefulWidget { final String? selectedAgentTypeId; final void Function(Map> filters) onApply; final VoidCallback onClearAll; + final void Function(String? agentTypeId)? onListingTypeChange; const AgentFilterSheet({ super.key, @@ -19,6 +20,7 @@ class AgentFilterSheet extends StatefulWidget { this.selectedAgentTypeId, required this.onApply, required this.onClearAll, + this.onListingTypeChange, }); @override @@ -28,6 +30,7 @@ class AgentFilterSheet extends StatefulWidget { class _AgentFilterSheetState extends State { late Map> _filters; final Map _expandedSections = {}; + String? _selectedAgentTypeId; @override void initState() { @@ -37,6 +40,7 @@ class _AgentFilterSheetState extends State { for (final entry in widget.currentFilters.entries) { _filters[entry.key] = List.from(entry.value); } + _selectedAgentTypeId = widget.selectedAgentTypeId; // Expand all sections by default for (final field in widget.filterFields) { _expandedSections[field.slug] = true; @@ -231,33 +235,47 @@ class _AgentFilterSheetState extends State { ); } - List _buildListingTypeChips() { - // Find "Professional" and "Lender" agent types - final types = <_ListingChip>[]; - for (final at in widget.agentTypes) { - final name = at.name.toLowerCase(); - if (name == 'professional' || name == 'agent') { - types.add(_ListingChip(label: 'Agents', id: at.id)); - } else if (name == 'lender') { - types.add(_ListingChip(label: 'Lenders', id: at.id)); - } - } - if (types.isEmpty) { - types.add(const _ListingChip(label: 'Agents', id: null)); - types.add(const _ListingChip(label: 'Lenders', id: null)); + /// Pluralize an agent type name for display (e.g. "Agent" → "Agents", + /// "Lender" → "Lenders", "Property Manager" → "Property Managers"). + /// Falls back to the raw name if it's already plural-looking. + String _displayLabel(String name) { + final trimmed = name.trim(); + if (trimmed.isEmpty) return trimmed; + final lower = trimmed.toLowerCase(); + if (lower.endsWith('s')) return trimmed; // already plural + if (lower.endsWith('y')) { + return '${trimmed.substring(0, trimmed.length - 1)}ies'; } + return '${trimmed}s'; + } - return types.map((chip) { - final isSelected = chip.id == widget.selectedAgentTypeId; + List _buildListingTypeChips() { + // Render one chip per backend agent type — fully driven by API data + if (widget.agentTypes.isEmpty) return const []; + + return widget.agentTypes.map((at) { + final isSelected = at.id == _selectedAgentTypeId; return Padding( padding: const EdgeInsets.only(left: 12), - child: Text( - chip.label, - style: TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 18, - fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400, - color: AppColors.primaryDark, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + // Toggle off when tapping the already-selected chip + _selectedAgentTypeId = isSelected ? null : at.id; + }); + widget.onListingTypeChange?.call(_selectedAgentTypeId); + }, + child: Text( + _displayLabel(at.name), + style: TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 18, + fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400, + color: isSelected + ? AppColors.accentOrange + : AppColors.primaryDark, + ), ), ), ); @@ -322,12 +340,6 @@ class _AgentFilterSheetState extends State { } } -class _ListingChip { - final String label; - final String? id; - const _ListingChip({required this.label, required this.id}); -} - class _FilterOptionsList extends StatefulWidget { final FilterableField field; final bool Function(String value) isSelected; diff --git a/lib/features/home/presentation/widgets/top_professionals_section.dart b/lib/features/home/presentation/widgets/top_professionals_section.dart index a567317..5852dc4 100644 --- a/lib/features/home/presentation/widgets/top_professionals_section.dart +++ b/lib/features/home/presentation/widgets/top_professionals_section.dart @@ -105,7 +105,7 @@ class _TopProfessionalsSectionState else ...[ // Professional Cards - horizontal scroll SizedBox( - height: 430, + height: 540, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, @@ -295,7 +295,6 @@ class _TopProfessionalsSectionState final rating = item.rating > 0 ? item.rating : 5.0; return Container( - constraints: const BoxConstraints(maxHeight: 410), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), @@ -335,203 +334,208 @@ class _TopProfessionalsSectionState ), // Content - Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // Name - Text( - item.name, - 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( - 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 - Row( - children: [ - SvgPicture.asset( - 'assets/icons/verified_badge_icon.svg', - width: 16, - height: 16, - placeholderBuilder: (_) => const Icon( - Icons.verified, - color: Color(0xFF638559), - size: 16, - ), + Flexible( + child: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Name + Text( + item.name, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, ), - const SizedBox(width: 6), - const Text( - '\u201CVerified local agent\u201D', - style: TextStyle( + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + + // Subtitle (agent type) + if (subtitle.isNotEmpty) + Text( + subtitle, + style: const TextStyle( fontFamily: 'SourceSerif4', - fontSize: 14, - fontWeight: FontWeight.w500, - color: Color(0xFF638559), + fontSize: 10, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ], - ), - const SizedBox(height: 8), + const SizedBox(height: 4), - // 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( + // Verified badge + Row( children: [ - const TextSpan( - text: 'Experience: ', - style: TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, + SvgPicture.asset( + 'assets/icons/verified_badge_icon.svg', + width: 16, + height: 16, + placeholderBuilder: (_) => const Icon( + Icons.verified, + color: Color(0xFF638559), + size: 16, ), ), - TextSpan( - text: experience, - style: const TextStyle( + const SizedBox(width: 6), + const Text( + '\u201CVerified local agent\u201D', + style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.primaryDark, + fontWeight: FontWeight.w500, + color: Color(0xFF638559), ), ), ], ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 8), - ], + 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, + // 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, + ), + ); + }), + ), + ], ), - ], + ), ), ), ],