From 29c9277bb71e010442f81ac24e6eced8f7154fbb Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sat, 14 Mar 2026 22:44:48 +0530 Subject: [PATCH] feat: Redesign home screen sections with updated testimonial card styles, refined trust stats layout, and spacing adjustments. --- assets/icons/cities_icon.svg | 4 +- assets/icons/properties_icon.svg | 8 +- .../services/push_notification_service.dart | 8 + .../presentation/screens/home_screen.dart | 2 +- .../widgets/testimonials_section.dart | 84 ++++------ .../widgets/trust_stats_section.dart | 149 ++++-------------- 6 files changed, 78 insertions(+), 177 deletions(-) diff --git a/assets/icons/cities_icon.svg b/assets/icons/cities_icon.svg index 39d9007..b3d7be3 100644 --- a/assets/icons/cities_icon.svg +++ b/assets/icons/cities_icon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/properties_icon.svg b/assets/icons/properties_icon.svg index 244b275..6d62461 100644 --- a/assets/icons/properties_icon.svg +++ b/assets/icons/properties_icon.svg @@ -1,3 +1,7 @@ - - + + + + + + diff --git a/lib/core/services/push_notification_service.dart b/lib/core/services/push_notification_service.dart index 1e89e77..45d9c51 100644 --- a/lib/core/services/push_notification_service.dart +++ b/lib/core/services/push_notification_service.dart @@ -119,6 +119,14 @@ class PushNotificationService { Future _getAndRegisterToken() async { try { + // On iOS, wait for APNS token before requesting FCM token + if (Platform.isIOS) { + final apnsToken = await _messaging!.getAPNSToken(); + if (apnsToken == null) { + debugPrint('[FCM] APNS token not available yet (iOS simulator?), skipping FCM token registration'); + return; + } + } final token = await _messaging!.getToken(); if (token != null) { _currentToken = token; diff --git a/lib/features/home/presentation/screens/home_screen.dart b/lib/features/home/presentation/screens/home_screen.dart index d291d7f..de33005 100644 --- a/lib/features/home/presentation/screens/home_screen.dart +++ b/lib/features/home/presentation/screens/home_screen.dart @@ -35,7 +35,7 @@ class HomeScreen extends ConsumerWidget { // Trust & Stats Section const TrustStatsSection(), - const SizedBox(height: 40), + const SizedBox(height: 24), // Testimonials Section const TestimonialsSection(), diff --git a/lib/features/home/presentation/widgets/testimonials_section.dart b/lib/features/home/presentation/widgets/testimonials_section.dart index ae7dcab..cf5ece3 100644 --- a/lib/features/home/presentation/widgets/testimonials_section.dart +++ b/lib/features/home/presentation/widgets/testimonials_section.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:real_estate_mobile/core/constants/app_colors.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'; @@ -68,48 +69,14 @@ class TestimonialsSection extends ConsumerWidget { ? cmsTestimonials : _defaultContent; - final title = data.title.isNotEmpty ? data.title : _defaultContent.title; - final subtitle = - data.subtitle.isNotEmpty ? data.subtitle : _defaultContent.subtitle; final testimonials = data.testimonials.isNotEmpty ? data.testimonials : _defaultTestimonials; return Column( children: [ - // Section header - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - children: [ - Text( - title, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 25, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - ), - const SizedBox(height: 8), - Text( - subtitle, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.primaryDark, - ), - ), - ], - ), - ), - const SizedBox(height: 24), - - // Testimonial cards carousel + // Testimonial cards carousel (header is in TrustStatsSection above) SizedBox( - height: 380, + height: 420, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 24), @@ -126,37 +93,38 @@ class TestimonialsSection extends ConsumerWidget { Widget _buildTestimonialCard(TestimonialItem testimonial) { return Container( - width: 315, - padding: const EdgeInsets.all(24), + width: 300, + padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: AppColors.primaryDark, + 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: [ - // Quote icon - const Text( - '\u201C', - style: TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 48, - fontWeight: FontWeight.w700, - color: AppColors.accentOrange, - height: 0.8, - ), + // Quote icon SVG + SvgPicture.asset( + 'assets/icons/quote_icon.svg', + width: 23, + height: 13, ), - const SizedBox(height: 8), + const SizedBox(height: 12), + // Title Text( testimonial.title, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, - color: Colors.white, + color: AppColors.primaryDark, ), ), const SizedBox(height: 12), + // Content Expanded( child: Text( testimonial.content, @@ -164,12 +132,12 @@ class TestimonialsSection extends ConsumerWidget { fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, - color: Colors.white, + color: AppColors.primaryDark, ), ), ), const SizedBox(height: 16), - // Stars + // Stars (yellow like Figma) Row( children: List.generate( testimonial.rating, @@ -177,30 +145,32 @@ class TestimonialsSection extends ConsumerWidget { padding: EdgeInsets.only(right: 4), child: Icon( Icons.star, - color: AppColors.accentOrange, + color: Color(0xFFFFDE21), size: 18, ), ), ), ), const SizedBox(height: 12), - // Author info + // Author name Text( testimonial.author, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.white, + color: AppColors.primaryDark, ), ), + const SizedBox(height: 2), + // Role Text( testimonial.role, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.primaryDark, ), ), ], diff --git a/lib/features/home/presentation/widgets/trust_stats_section.dart b/lib/features/home/presentation/widgets/trust_stats_section.dart index dd33bb6..09bf602 100644 --- a/lib/features/home/presentation/widgets/trust_stats_section.dart +++ b/lib/features/home/presentation/widgets/trust_stats_section.dart @@ -47,15 +47,9 @@ class TrustStatsSection extends ConsumerWidget { final title = data.title.isNotEmpty ? data.title : _defaultContent.title; final subtitle = data.subtitle.isNotEmpty ? data.subtitle : _defaultContent.subtitle; - final ratingInfo = data.ratingInfo.isNotEmpty - ? data.ratingInfo - : _defaultContent.ratingInfo; final stats = data.stats.isNotEmpty ? data.stats : _defaultContent.stats; - // Extract rating number from ratingInfo string (e.g. "4.9" from "...4.9 out of 5...") - final ratingValue = _extractRating(ratingInfo); - return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( @@ -81,40 +75,23 @@ class TrustStatsSection extends ConsumerWidget { color: AppColors.primaryDark, ), ), - const SizedBox(height: 8), - Text( - ratingInfo, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w500, - color: AppColors.primaryDark, - ), - ), const SizedBox(height: 16), const Divider(color: AppColors.divider), const SizedBox(height: 16), - // Stats row - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Dynamic stats from CMS - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: stats - .map((stat) => Padding( - padding: const EdgeInsets.only(bottom: 12), - child: _buildStatItem(stat), - )) - .toList(), - ), + // Stats (group centered, content left-aligned) + Align( + alignment: Alignment.center, + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: stats + .map((stat) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: _buildStatItem(stat), + )) + .toList(), ), - // Rating visual - const SizedBox(width: 16), - _buildRatingVisual(ratingValue), - ], + ), ), ], ), @@ -127,32 +104,29 @@ class TrustStatsSection extends ConsumerWidget { final fallbackIcon = iconEntry?.$2 ?? Icons.info_outline; return Row( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (svgPath != null) - Padding( - padding: const EdgeInsets.only(right: 8, top: 2), - child: SvgPicture.asset( - svgPath, - width: 27, - height: 27, - placeholderBuilder: (_) => Icon( - fallbackIcon, - size: 27, - color: AppColors.accentOrange, - ), - ), - ) - else - Padding( - padding: const EdgeInsets.only(right: 8, top: 2), - child: Icon( - fallbackIcon, - size: 27, - color: AppColors.accentOrange, - ), - ), - Expanded( + Padding( + padding: const EdgeInsets.only(right: 10, top: 2), + child: svgPath != null + ? SvgPicture.asset( + svgPath, + width: 27, + height: 27, + placeholderBuilder: (_) => Icon( + fallbackIcon, + size: 27, + color: AppColors.accentOrange, + ), + ) + : Icon( + fallbackIcon, + size: 27, + color: AppColors.accentOrange, + ), + ), + Flexible( child: RichText( text: TextSpan( children: [ @@ -182,59 +156,4 @@ class TrustStatsSection extends ConsumerWidget { ); } - Widget _buildRatingVisual(double rating) { - final fullStars = rating.floor(); - final hasHalfStar = (rating - fullStars) >= 0.3; - final totalStars = 5; - - return Column( - children: [ - Text( - rating.toStringAsFixed(1), - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 32, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - ), - Row( - children: List.generate( - totalStars, - (index) { - if (index < fullStars) { - return const Icon( - Icons.star, - color: AppColors.accentOrange, - size: 18, - ); - } else if (index == fullStars && hasHalfStar) { - return const Icon( - Icons.star_half, - color: AppColors.accentOrange, - size: 18, - ); - } else { - return const Icon( - Icons.star_border, - color: AppColors.accentOrange, - size: 18, - ); - } - }, - ), - ), - ], - ); - } - - /// Extract numeric rating from ratingInfo string. - /// e.g. "Clients rate our real estate services 4.9 out of 5..." → 4.9 - double _extractRating(String ratingInfo) { - final match = RegExp(r'(\d+\.?\d*)\s*out\s*of\s*\d+').firstMatch(ratingInfo); - if (match != null) { - return double.tryParse(match.group(1)!) ?? 4.9; - } - return 4.9; - } }