diff --git a/lib/core/widgets/app_bottom_nav_bar.dart b/lib/core/widgets/app_bottom_nav_bar.dart index 968851a..9f44974 100644 --- a/lib/core/widgets/app_bottom_nav_bar.dart +++ b/lib/core/widgets/app_bottom_nav_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart'; +import 'package:real_estate_mobile/features/home/presentation/screens/home_screen.dart'; /// Determines which tab is active based on the current route. int _currentTabIndex(BuildContext context) { @@ -42,7 +43,24 @@ class AppBottomNavBar extends ConsumerWidget { svgPath: 'assets/icons/nav_home_icon.svg', fallbackIcon: Icons.home, isSelected: selectedIndex == 0, - onTap: () => context.go('/home'), + onTap: () { + if (selectedIndex == 0) { + // Already on home — scroll to top + try { + final controller = + ref.read(homeScrollControllerProvider); + if (controller.hasClients) { + controller.animateTo( + 0, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + } catch (_) {} + } else { + context.go('/home'); + } + }, ), _NavItem( svgPath: 'assets/icons/nav_list_icon.svg', diff --git a/lib/features/agents/presentation/screens/agent_home_screen.dart b/lib/features/agents/presentation/screens/agent_home_screen.dart index 56139a8..807e59d 100644 --- a/lib/features/agents/presentation/screens/agent_home_screen.dart +++ b/lib/features/agents/presentation/screens/agent_home_screen.dart @@ -9,6 +9,7 @@ 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/agent_detail_provider.dart'; import 'package:real_estate_mobile/features/profile/data/profile_repository.dart'; +import 'package:real_estate_mobile/features/home/presentation/screens/home_screen.dart'; import 'package:go_router/go_router.dart'; /// Provider that fetches the agent's own profile ID from /agents/profile/me @@ -219,7 +220,7 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> { if (state.error != null) { return _buildError(state.error!); } - return _buildContent(state); + return _buildContent(state, ref); } Widget _buildError(String error) { @@ -248,9 +249,11 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> { ); } - Widget _buildContent(AgentDetailState state) { + Widget _buildContent(AgentDetailState state, WidgetRef ref) { final agent = state.agent!; + final scrollController = ref.watch(homeScrollControllerProvider); return SingleChildScrollView( + controller: scrollController, child: Column( children: [ const SizedBox(height: 12), diff --git a/lib/features/home/data/models/landing_page_content.dart b/lib/features/home/data/models/landing_page_content.dart index 4211e2b..1dd874f 100644 --- a/lib/features/home/data/models/landing_page_content.dart +++ b/lib/features/home/data/models/landing_page_content.dart @@ -69,6 +69,7 @@ class ProfessionalItem { final String experience; final List expertise; final String imageUrl; + final double rating; const ProfessionalItem({ required this.name, @@ -77,6 +78,7 @@ class ProfessionalItem { this.experience = '', this.expertise = const [], this.imageUrl = '', + this.rating = 5.0, }); factory ProfessionalItem.fromJson(Map json) { @@ -90,6 +92,9 @@ class ProfessionalItem { .toList() ?? [], imageUrl: json['imageUrl'] as String? ?? '', + rating: (json['rating'] as num?)?.toDouble() ?? + (json['averageRating'] as num?)?.toDouble() ?? + 5.0, ); } } diff --git a/lib/features/home/presentation/screens/home_screen.dart b/lib/features/home/presentation/screens/home_screen.dart index 6575a76..729133e 100644 --- a/lib/features/home/presentation/screens/home_screen.dart +++ b/lib/features/home/presentation/screens/home_screen.dart @@ -8,12 +8,21 @@ import 'package:real_estate_mobile/features/home/presentation/widgets/testimonia import 'package:real_estate_mobile/features/home/presentation/widgets/top_professionals_section.dart'; import 'package:real_estate_mobile/features/home/presentation/widgets/trust_stats_section.dart'; +/// Global scroll controller for the home screen so the bottom nav can scroll to top. +final homeScrollControllerProvider = Provider((ref) { + final controller = ScrollController(); + ref.onDispose(() => controller.dispose()); + return controller; +}); + class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final scrollController = ref.watch(homeScrollControllerProvider); return SingleChildScrollView( + controller: scrollController, child: Column( children: [ // Hero Section with Search diff --git a/lib/features/home/presentation/widgets/hero_section.dart b/lib/features/home/presentation/widgets/hero_section.dart index 8204ff4..bee9376 100644 --- a/lib/features/home/presentation/widgets/hero_section.dart +++ b/lib/features/home/presentation/widgets/hero_section.dart @@ -55,14 +55,18 @@ class _HeroSectionState extends ConsumerState { context.push('/agents/search${queryString.isNotEmpty ? '?$queryString' : ''}'); } - void _showTypePicker() { - final topProState = ref.read(topProfessionalsProvider); - final agentTypes = topProState.agentTypes; + Future _showTypePicker() async { + var agentTypes = ref.read(topProfessionalsProvider).agentTypes; + // If types haven't loaded yet, fetch them directly if (agentTypes.isEmpty) { - // If types not loaded yet, just navigate to search - context.push('/agents/search'); - return; + try { + final repo = ref.read(agentsRepositoryProvider); + agentTypes = await repo.getAgentTypes(); + } catch (_) { + return; + } + if (!mounted || agentTypes.isEmpty) return; } showModalBottomSheet( diff --git a/lib/features/home/presentation/widgets/top_professionals_section.dart b/lib/features/home/presentation/widgets/top_professionals_section.dart index 0dd3a7e..d166258 100644 --- a/lib/features/home/presentation/widgets/top_professionals_section.dart +++ b/lib/features/home/presentation/widgets/top_professionals_section.dart @@ -103,7 +103,7 @@ class _TopProfessionalsSectionState else ...[ // Professional Cards - horizontal scroll SizedBox( - height: 480, + height: 540, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, @@ -293,8 +293,10 @@ class _TopProfessionalsSectionState child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Image - Expanded( + // Image (226px per Figma) + SizedBox( + width: double.infinity, + height: 226, child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), @@ -302,7 +304,7 @@ class _TopProfessionalsSectionState ? S3Image( imageUrl: imageUrl, width: double.infinity, - height: double.infinity, + height: 226, fit: BoxFit.cover, alignment: Alignment.topCenter, placeholder: (_) => _buildImagePlaceholder(index), @@ -313,7 +315,8 @@ class _TopProfessionalsSectionState ), // Content - Padding( + Expanded( + child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -468,9 +471,26 @@ class _TopProfessionalsSectionState ), const SizedBox(height: 8), ], + + // Star Rating + Row( + children: List.generate(5, (i) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon( + i < item.rating.round() + ? Icons.star_rounded + : Icons.star_outline_rounded, + color: const Color(0xFFE5A625), + size: 24, + ), + ); + }), + ), ], ), ), + ), ], ), ); @@ -478,7 +498,7 @@ class _TopProfessionalsSectionState Widget _buildShimmerCards() { return SizedBox( - height: 480, + height: 540, child: Shimmer.fromColors( baseColor: const Color(0xFFE0E0E0), highlightColor: const Color(0xFFF5F5F5), @@ -492,7 +512,7 @@ class _TopProfessionalsSectionState children: [ // Image placeholder Container( - height: 192, + height: 226, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(15)), @@ -558,7 +578,7 @@ class _TopProfessionalsSectionState Widget _buildAvatarPlaceholder() { return Container( width: double.infinity, - height: 192, + height: 226, color: AppColors.gradientStart, child: const Icon(Icons.person, size: 60, color: AppColors.primaryDark), );