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

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