feat: Redesign home screen sections with updated testimonial card styles, refined trust stats layout, and spacing adjustments.

This commit is contained in:
pradeepkumar
2026-03-14 22:44:48 +05:30
parent 4475965780
commit 29c9277bb7
6 changed files with 78 additions and 177 deletions

View File

@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 21V7L9 3L15 7V9H21V21H3ZM5 19H7V17H5V19ZM5 15H7V13H5V15ZM5 11H7V9H5V11ZM9 19H11V17H9V19ZM9 15H11V13H9V15ZM9 11H11V9H9V11ZM9 7H11V5.85L9 4.7V7ZM13 19H19V11H13V19ZM15 17V15H17V17H15ZM15 13V11H17V13H15Z" fill="currentColor"/>
<svg width="27" height="27" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.30556 9.09722L6.31944 9.08194M11.8611 9.09722L11.875 9.08194M6.30556 14.6528L6.31944 14.6375M11.8611 14.6528L11.875 14.6375M6.30556 20.2083L6.31944 20.1931M11.8611 20.2083L11.875 20.1931M17.4167 25.75H1.58333C1.36232 25.75 1.15036 25.6622 0.994078 25.5059C0.837797 25.3496 0.75 25.1377 0.75 24.9167V4.36111C0.75 4.1401 0.837797 3.92814 0.994078 3.77186C1.15036 3.61558 1.36232 3.52778 1.58333 3.52778H9.08333V1.58333C9.08333 1.36232 9.17113 1.15036 9.32741 0.994078C9.48369 0.837797 9.69565 0.75 9.91667 0.75H16.5833C16.8043 0.75 17.0163 0.837797 17.1726 0.994078C17.3289 1.15036 17.4167 1.36232 17.4167 1.58333V9.08333M17.4167 25.75H24.9167C25.1377 25.75 25.3496 25.6622 25.5059 25.5059C25.6622 25.3496 25.75 25.1377 25.75 24.9167V9.91667C25.75 9.69565 25.6622 9.48369 25.5059 9.32741C25.3496 9.17113 25.1377 9.08333 24.9167 9.08333H17.4167M17.4167 25.75V20.1944M17.4167 9.08333V14.6389M17.4167 20.1944V14.6389M17.4167 20.1944H20.1944M17.4167 14.6389H20.1944" stroke="#E58625" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,3 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20V14H14V20H19V12H22L12 3L2 12H5V20H10Z" fill="currentColor"/>
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.46 6.47656L23.1484 23.4053H1.85156L0.540039 6.47656H24.46Z" stroke="#E58625"/>
<mask id="path-2-inside-1_49_6415" fill="white">
<path d="M6.94531 8.63233V0H18.0563V8.63233"/>
</mask>
<path d="M6.94531 0V-1H5.94531V0H6.94531ZM18.0563 0H19.0563V-1H18.0563V0ZM6.94531 8.63233H7.94531V0H6.94531H5.94531V8.63233H6.94531ZM6.94531 0V1H18.0563V0V-1H6.94531V0ZM18.0563 0H17.0563V8.63233H18.0563H19.0563V0H18.0563Z" fill="#E58625" mask="url(#path-2-inside-1_49_6415)"/>
</svg>

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 576 B

View File

@@ -119,6 +119,14 @@ class PushNotificationService {
Future<void> _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;

View File

@@ -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(),

View File

@@ -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,
),
),
],

View File

@@ -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;
}
}