diff --git a/.gitignore b/.gitignore
index 3820a95..ce633d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,7 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
+
+# Signing
+/android/keystores/
+/android/key.properties
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index dd59616..b766aaf 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -1,3 +1,6 @@
+import java.util.Properties
+import java.io.FileInputStream
+
plugins {
id("com.android.application")
id("kotlin-android")
@@ -6,6 +9,12 @@ plugins {
id("com.google.gms.google-services")
}
+val keyProperties = Properties()
+val keyPropertiesFile = rootProject.file("key.properties")
+if (keyPropertiesFile.exists()) {
+ keyProperties.load(FileInputStream(keyPropertiesFile))
+}
+
android {
namespace = "com.requestn.real_estate_mobile"
compileSdk = flutter.compileSdkVersion
@@ -29,6 +38,21 @@ android {
versionName = flutter.versionName
}
+ signingConfigs {
+ create("devRelease") {
+ storeFile = file(keyProperties.getProperty("devStoreFile", ""))
+ storePassword = keyProperties.getProperty("devStorePassword", "")
+ keyAlias = keyProperties.getProperty("devKeyAlias", "")
+ keyPassword = keyProperties.getProperty("devKeyPassword", "")
+ }
+ create("demoRelease") {
+ storeFile = file(keyProperties.getProperty("demoStoreFile", ""))
+ storePassword = keyProperties.getProperty("demoStorePassword", "")
+ keyAlias = keyProperties.getProperty("demoKeyAlias", "")
+ keyPassword = keyProperties.getProperty("demoKeyPassword", "")
+ }
+ }
+
flavorDimensions += "flavor-type"
productFlavors {
@@ -41,11 +65,13 @@ android {
dimension = "flavor-type"
applicationIdSuffix = ".demo"
resValue("string", "app_name", "RE-QuestN Demo")
+ signingConfig = signingConfigs.getByName("demoRelease")
}
create("dev") {
dimension = "flavor-type"
applicationIdSuffix = ".dev"
resValue("string", "app_name", "RE-QuestN Dev")
+ signingConfig = signingConfigs.getByName("devRelease")
}
}
diff --git a/assets/icons/diamond_icon.svg b/assets/icons/diamond_icon.svg
new file mode 100644
index 0000000..a035434
--- /dev/null
+++ b/assets/icons/diamond_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/feature_check_icon.svg b/assets/icons/feature_check_icon.svg
new file mode 100644
index 0000000..e1ecff4
--- /dev/null
+++ b/assets/icons/feature_check_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/need_help_illustration.jpg b/assets/icons/need_help_illustration.jpg
new file mode 100644
index 0000000..8e3affb
Binary files /dev/null and b/assets/icons/need_help_illustration.jpg differ
diff --git a/assets/icons/wallet_illustration.jpg b/assets/icons/wallet_illustration.jpg
new file mode 100644
index 0000000..47d9768
Binary files /dev/null and b/assets/icons/wallet_illustration.jpg differ
diff --git a/lib/features/agents/presentation/screens/agent_edit_profile_screen.dart b/lib/features/agents/presentation/screens/agent_edit_profile_screen.dart
index 95d563a..a4b025f 100644
--- a/lib/features/agents/presentation/screens/agent_edit_profile_screen.dart
+++ b/lib/features/agents/presentation/screens/agent_edit_profile_screen.dart
@@ -343,11 +343,13 @@ class _AgentEditProfileScreenState
return Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
- body: Column(
- children: [
- // Header with circular back button (SVG) matching Figma
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ body: SafeArea(
+ bottom: false,
+ child: Column(
+ children: [
+ // Header with circular back button (SVG) matching Figma
+ Padding(
+ padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
child: Row(
children: [
GestureDetector(
@@ -371,8 +373,9 @@ class _AgentEditProfileScreenState
],
),
),
- Expanded(child: _buildBody(asyncData)),
- ],
+ Expanded(child: _buildBody(asyncData)),
+ ],
+ ),
),
);
}
diff --git a/lib/features/profile/presentation/screens/profile_settings_screen.dart b/lib/features/profile/presentation/screens/profile_settings_screen.dart
index 71182a6..dfef6ac 100644
--- a/lib/features/profile/presentation/screens/profile_settings_screen.dart
+++ b/lib/features/profile/presentation/screens/profile_settings_screen.dart
@@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
+import 'package:flutter_svg/flutter_svg.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/core/widgets/s3_image.dart';
@@ -1085,7 +1086,7 @@ class _ProfileSettingsScreenState extends ConsumerState {
'Subscription & Payments',
style: TextStyle(
fontFamily: 'Fractul',
- fontSize: 22,
+ fontSize: 25,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
@@ -1112,7 +1113,7 @@ class _ProfileSettingsScreenState extends ConsumerState {
'Current Plan',
style: TextStyle(
fontFamily: 'Fractul',
- fontSize: 18,
+ fontSize: 20,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
@@ -1265,132 +1266,206 @@ class _ProfileSettingsScreenState extends ConsumerState {
'Annual Membership Plans',
style: TextStyle(
fontFamily: 'Fractul',
- fontSize: 18,
+ fontSize: 20,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
const SizedBox(height: 16),
// Plan card matching Figma
- Container(
- padding: const EdgeInsets.all(20),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(10),
- border: Border.all(
- color: AppColors.primaryDark.withValues(alpha: 0.5),
- width: 0.5,
+ Stack(
+ clipBehavior: Clip.none,
+ children: [
+ Container(
+ padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(10),
+ border: Border.all(
+ color: AppColors.primaryDark.withValues(alpha: 0.5),
+ width: 0.5,
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Annual Subscription badge
+ Container(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
+ decoration: BoxDecoration(
+ color: AppColors.accentOrange,
+ borderRadius: BorderRadius.circular(15),
+ ),
+ child: const Text(
+ 'Annual Subscription',
+ style: TextStyle(
+ fontFamily: 'SourceSerif4',
+ fontSize: 14,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ const SizedBox(height: 24),
+ // Professional Annual Plan
+ const Text(
+ 'Professional Annual Plan',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 20,
+ fontWeight: FontWeight.w700,
+ color: AppColors.primaryDark,
+ ),
+ ),
+ const SizedBox(height: 8),
+ const Text(
+ 'Complete solutions for real estate\nagents and lenders to grow their\nbusiness.',
+ style: TextStyle(
+ fontFamily: 'SourceSerif4',
+ fontSize: 14,
+ color: AppColors.primaryDark,
+ ),
+ ),
+ const SizedBox(height: 20),
+ // Price
+ RichText(
+ text: TextSpan(
+ children: [
+ const TextSpan(
+ text: '\$499',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 35,
+ fontWeight: FontWeight.w700,
+ color: Colors.black,
+ ),
+ ),
+ const TextSpan(
+ text: ' / ',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 20,
+ fontWeight: FontWeight.w700,
+ color: Colors.black,
+ ),
+ ),
+ TextSpan(
+ text: 'Year',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 20,
+ fontWeight: FontWeight.w700,
+ color: Colors.black.withValues(alpha: 0.5),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 20),
+ // Get Premium Access button + illustration
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: 155,
+ height: 43,
+ child: ElevatedButton(
+ onPressed: _startCheckout,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: AppColors.accentOrange,
+ side: const BorderSide(color: AppColors.accentOrange),
+ padding: EdgeInsets.zero,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(15)),
+ ),
+ child: const Text('Get Premium Access',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ fontWeight: FontWeight.w700,
+ color: Colors.white)),
+ ),
+ ),
+ const Spacer(),
+ // Wallet illustration
+ ClipRRect(
+ borderRadius: BorderRadius.circular(70),
+ child: Image.asset(
+ 'assets/icons/wallet_illustration.jpg',
+ width: 120,
+ height: 120,
+ fit: BoxFit.cover,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ // Include Features
+ const Text(
+ 'Include Features',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 20,
+ fontWeight: FontWeight.w700,
+ color: AppColors.primaryDark,
+ ),
+ ),
+ const SizedBox(height: 16),
+ ..._buildPlanFeatures(),
+ ],
+ ),
),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // Annual Subscription badge
- Container(
- padding:
- const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
- decoration: BoxDecoration(
- color: AppColors.accentOrange,
- borderRadius: BorderRadius.circular(15),
+ // "Best value" ribbon on top-right corner
+ Positioned(
+ top: 0,
+ right: 0,
+ child: ClipRRect(
+ borderRadius: const BorderRadius.only(
+ topRight: Radius.circular(10),
),
- child: const Text(
- 'Annual Subscription',
- style: TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 14,
- color: Colors.white,
+ child: SizedBox(
+ width: 62,
+ height: 134,
+ child: Stack(
+ children: [
+ CustomPaint(
+ size: const Size(62, 134),
+ painter: _RibbonPainter(),
+ ),
+ // Orange circle at top of ribbon
+ Positioned(
+ top: 6,
+ left: 12,
+ child: Container(
+ width: 15,
+ height: 14,
+ decoration: const BoxDecoration(
+ color: AppColors.accentOrange,
+ shape: BoxShape.circle,
+ ),
+ ),
+ ),
+ // "Best value" text rotated ~67 degrees
+ Positioned(
+ top: 24,
+ left: 6,
+ child: Transform.rotate(
+ angle: 1.17,
+ child: const Text(
+ 'Best value',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ ),
+ ],
),
),
),
- const SizedBox(height: 24),
- // Professional Annual Plan
- const Text(
- 'Professional Annual Plan',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 20,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark,
- ),
- ),
- const SizedBox(height: 8),
- const Text(
- 'Complete solutions for real estate agents and lenders to grow their business.',
- style: TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 14,
- color: AppColors.primaryDark,
- ),
- ),
- const SizedBox(height: 20),
- // Price
- RichText(
- text: TextSpan(
- children: [
- const TextSpan(
- text: '\$499',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 35,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark,
- ),
- ),
- const TextSpan(
- text: ' / ',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 20,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark,
- ),
- ),
- TextSpan(
- text: 'Year',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 20,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark.withValues(alpha: 0.5),
- ),
- ),
- ],
- ),
- ),
- const SizedBox(height: 20),
- // Get Premium Access button
- SizedBox(
- width: 155,
- child: ElevatedButton(
- onPressed: _startCheckout,
- style: ElevatedButton.styleFrom(
- backgroundColor: AppColors.accentOrange,
- padding: const EdgeInsets.symmetric(vertical: 12),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(15)),
- ),
- child: const Text('Get Premium Access',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 13,
- fontWeight: FontWeight.w700,
- color: Colors.white)),
- ),
- ),
- const SizedBox(height: 24),
- // Include Features
- const Text(
- 'Include Features',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 20,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark,
- ),
- ),
- const SizedBox(height: 16),
- ..._buildPlanFeatures(),
- ],
- ),
+ ),
+ ],
),
],
@@ -1398,7 +1473,8 @@ class _ProfileSettingsScreenState extends ConsumerState {
// Need Help section matching Figma
Container(
- padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
+ clipBehavior: Clip.antiAlias,
+ padding: const EdgeInsets.fromLTRB(20, 30, 20, 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
@@ -1419,7 +1495,7 @@ class _ProfileSettingsScreenState extends ConsumerState {
),
const SizedBox(height: 10),
const Text(
- 'Our billing experts are here to help you\nwith any questions about your plan.',
+ 'Our billing experts are here to help you\nwith any questions\n about your plan.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'SourceSerif4',
@@ -1439,7 +1515,7 @@ class _ProfileSettingsScreenState extends ConsumerState {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
- 'support@re-quest.co',
+ 'support@example.com',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
@@ -1449,15 +1525,26 @@ class _ProfileSettingsScreenState extends ConsumerState {
decorationColor: AppColors.accentOrange,
),
),
- const SizedBox(width: 6),
+ const SizedBox(width: 4),
Transform.rotate(
- angle: 0.7, // ~40 degrees
+ angle: 0.72, // ~41 degrees matching Figma
child: const Icon(Icons.arrow_upward,
size: 16, color: AppColors.accentOrange),
),
],
),
),
+ const SizedBox(height: 20),
+ // Businessman illustration
+ ClipRRect(
+ borderRadius: BorderRadius.circular(57),
+ child: Image.asset(
+ 'assets/icons/need_help_illustration.jpg',
+ width: 261,
+ height: 261,
+ fit: BoxFit.cover,
+ ),
+ ),
],
),
),
@@ -1467,29 +1554,25 @@ class _ProfileSettingsScreenState extends ConsumerState {
List _buildPlanFeatures() {
const features = [
- 'Agent Co-Marketing',
- 'Priority 24/7 Support',
- 'Whitelabel Client Portals',
- 'Advanced CRM Tools & Analytics',
+ ('Agent -Co-Marketing', true), // first uses diamond icon
+ ('Priority 24/7 Support', false),
+ ('Whitelabel Client Portals', false),
+ ('Advanced CRM Tools & Analytics', false),
];
return features
.map((f) => Padding(
- padding: const EdgeInsets.only(bottom: 12),
+ padding: const EdgeInsets.only(bottom: 20),
child: Row(
children: [
- Container(
- width: 22,
- height: 22,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- border: Border.all(
- color: AppColors.accentOrange, width: 1.5),
- ),
- child: const Icon(Icons.check,
- size: 14, color: AppColors.accentOrange),
+ SvgPicture.asset(
+ f.$2
+ ? 'assets/icons/diamond_icon.svg'
+ : 'assets/icons/feature_check_icon.svg',
+ width: 18,
+ height: 18,
),
const SizedBox(width: 12),
- Text(f,
+ Text(f.$1,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
@@ -1634,121 +1717,156 @@ class _ProfileSettingsScreenState extends ConsumerState {
return ListView(
padding: const EdgeInsets.fromLTRB(24, 10, 24, 30),
children: [
- const Text(
- 'Add Testimonials',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 15,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark,
+ // Title matching Figma
+ const Center(
+ child: Text(
+ 'Add Testimonials',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 25,
+ fontWeight: FontWeight.w700,
+ color: AppColors.primaryDark,
+ ),
),
),
- const SizedBox(height: 8),
- const Text(
- 'Generate a link for your clients to submit testimonials. Share it via email or messaging.',
- style: TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 14,
- fontWeight: FontWeight.w400,
- color: AppColors.primaryDark,
+ const SizedBox(height: 10),
+ const Center(
+ child: Text(
+ 'What people are saying about you',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontFamily: 'SourceSerif4',
+ fontSize: 14,
+ fontWeight: FontWeight.w400,
+ color: AppColors.primaryDark,
+ ),
),
),
- const SizedBox(height: 24),
+ const SizedBox(height: 20),
- // Generate link section
- _buildSectionCard(
- title: 'Testimonial Collection Link',
- subtitle: 'Share this link with your clients',
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (_testimonialLink != null) ...[
+ // Create Testimonials section
+ const Center(
+ child: Text(
+ 'Create Testimonials',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 25,
+ fontWeight: FontWeight.w700,
+ color: AppColors.primaryDark,
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // Generate Link button (full-width, orange)
+ SizedBox(
+ width: double.infinity,
+ height: 43,
+ child: ElevatedButton(
+ onPressed: _isGeneratingLink ? null : _generateTestimonialLink,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: AppColors.accentOrange,
+ padding: const EdgeInsets.symmetric(vertical: 10),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(15)),
+ ),
+ child: _isGeneratingLink
+ ? const SizedBox(
+ width: 20,
+ height: 20,
+ child: CircularProgressIndicator(
+ strokeWidth: 2, color: Colors.white))
+ : const Text('Generate Link',
+ style: TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ fontWeight: FontWeight.w700,
+ color: Colors.white)),
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // Link URL field with Copy button
+ if (_testimonialLink != null)
+ Container(
+ height: 43,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(15),
+ border: Border.all(
+ color: AppColors.primaryDark.withValues(alpha: 0.1),
+ ),
+ ),
+ child: Row(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ child: Text(
+ _testimonialLink!,
+ overflow: TextOverflow.ellipsis,
+ style: const TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ fontWeight: FontWeight.w300,
+ color: Colors.black,
+ ),
+ ),
+ ),
+ ),
Container(
- width: double.infinity,
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: AppColors.primaryDark.withValues(alpha: 0.05),
- borderRadius: BorderRadius.circular(10),
- border: Border.all(
- color: AppColors.primaryDark.withValues(alpha: 0.1)),
- ),
- child: Text(
- _testimonialLink!,
- style: const TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 13,
- color: AppColors.primaryDark,
- ),
- ),
+ width: 1,
+ height: 43,
+ color: AppColors.primaryDark.withValues(alpha: 0.1),
),
- const SizedBox(height: 12),
- SizedBox(
- width: double.infinity,
- child: OutlinedButton.icon(
- onPressed: () {
- Clipboard.setData(
- ClipboardData(text: _testimonialLink!));
- setState(() => _linkCopied = true);
- Future.delayed(const Duration(seconds: 2), () {
- if (mounted) setState(() => _linkCopied = false);
- });
- },
- icon: Icon(
- _linkCopied ? Icons.check : Icons.copy, size: 16),
- label: Text(_linkCopied ? 'Copied!' : 'Copy Link'),
- style: OutlinedButton.styleFrom(
- foregroundColor: AppColors.accentOrange,
- side: const BorderSide(color: AppColors.accentOrange),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10)),
+ GestureDetector(
+ onTap: () {
+ Clipboard.setData(
+ ClipboardData(text: _testimonialLink!));
+ setState(() => _linkCopied = true);
+ Future.delayed(const Duration(seconds: 2), () {
+ if (mounted) setState(() => _linkCopied = false);
+ });
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10),
+ child: Row(
+ children: [
+ Icon(
+ _linkCopied ? Icons.check : Icons.copy_outlined,
+ size: 18,
+ color: Colors.black,
+ ),
+ const SizedBox(width: 4),
+ Text(
+ _linkCopied ? 'Copied!' : 'Copy',
+ style: const TextStyle(
+ fontFamily: 'SourceSerif4',
+ fontSize: 14,
+ color: Colors.black,
+ ),
+ ),
+ ],
),
),
),
- ] else ...[
- SizedBox(
- width: double.infinity,
- child: ElevatedButton(
- onPressed: _isGeneratingLink ? null : _generateTestimonialLink,
- style: ElevatedButton.styleFrom(
- backgroundColor: AppColors.accentOrange,
- foregroundColor: AppColors.primaryDark,
- padding: const EdgeInsets.symmetric(vertical: 14),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(15)),
- ),
- child: _isGeneratingLink
- ? const SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(
- strokeWidth: 2, color: AppColors.primaryDark))
- : const Text('Generate Link',
- style: TextStyle(
- fontFamily: 'Fractul',
- fontSize: 14,
- fontWeight: FontWeight.w700)),
- ),
- ),
],
- ],
+ ),
),
- ),
- const SizedBox(height: 24),
+ const SizedBox(height: 30),
- // Testimonials list header
+ // Reviews header with sort
Row(
children: [
const Text(
- 'Received Testimonials',
+ 'Reviews',
style: TextStyle(
fontFamily: 'Fractul',
- fontSize: 15,
- fontWeight: FontWeight.w700,
- color: AppColors.primaryDark,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ color: Colors.black,
),
),
const Spacer(),
- // Sort toggle
GestureDetector(
onTap: () {
setState(() {
@@ -1759,25 +1877,26 @@ class _ProfileSettingsScreenState extends ConsumerState {
},
child: Row(
children: [
- Icon(Icons.sort,
- size: 16,
- color: AppColors.primaryDark.withValues(alpha: 0.6)),
- const SizedBox(width: 4),
Text(
- _testimonialSort == 'latest' ? 'Latest' : 'Oldest',
- style: TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 13,
- color: AppColors.primaryDark.withValues(alpha: 0.6),
+ 'Sort By : ${_testimonialSort == 'latest' ? 'Latest' : 'Oldest'}',
+ style: const TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ color: Colors.black,
),
),
+ const SizedBox(width: 4),
+ const Icon(Icons.arrow_downward,
+ size: 14, color: Colors.black),
],
),
),
],
),
- const SizedBox(height: 12),
+ const SizedBox(height: 16),
+ // Reviews list in grey container
if (_isLoadingTestimonials)
const Center(
child: Padding(
@@ -1786,7 +1905,11 @@ class _ProfileSettingsScreenState extends ConsumerState {
))
else if (_testimonials.isEmpty)
Container(
- padding: const EdgeInsets.symmetric(vertical: 40),
+ padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 16),
+ decoration: BoxDecoration(
+ color: const Color(0xFFD9D9D9).withValues(alpha: 0.25),
+ borderRadius: BorderRadius.circular(7),
+ ),
child: Center(
child: Column(
children: [
@@ -1818,8 +1941,18 @@ class _ProfileSettingsScreenState extends ConsumerState {
),
)
else
- ..._testimonials
- .map((t) => _buildTestimonialCard(t)),
+ Container(
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(
+ color: const Color(0xFFD9D9D9).withValues(alpha: 0.25),
+ borderRadius: BorderRadius.circular(7),
+ ),
+ child: Column(
+ children: _testimonials
+ .map((t) => _buildTestimonialCard(t))
+ .toList(),
+ ),
+ ),
],
);
}
@@ -1836,76 +1969,62 @@ class _ProfileSettingsScreenState extends ConsumerState {
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
- borderRadius: BorderRadius.circular(15),
- border: Border.all(
- color: AppColors.primaryDark.withValues(alpha: 0.1)),
- boxShadow: [
- BoxShadow(
- color: const Color(0xFFD9D9D9).withValues(alpha: 0.3),
- offset: const Offset(0, 4),
- blurRadius: 10,
- ),
- ],
+ borderRadius: BorderRadius.circular(7),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // Stars + date
+ // Stars
Row(
- children: [
- ...List.generate(
- 5,
- (i) => Icon(
- i < rating ? Icons.star : Icons.star_border,
- size: 16,
- color: i < rating
- ? AppColors.accentOrange
- : AppColors.primaryDark.withValues(alpha: 0.3),
- ),
+ children: List.generate(
+ 5,
+ (i) => Icon(
+ i < rating ? Icons.star : Icons.star_border,
+ size: 16,
+ color: i < rating
+ ? AppColors.accentOrange
+ : AppColors.primaryDark.withValues(alpha: 0.3),
),
- const Spacer(),
- if (createdAt != null)
- Text(
- _formatRelativeTime(createdAt),
- style: TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 12,
- color: AppColors.primaryDark.withValues(alpha: 0.5),
- ),
- ),
- ],
+ ),
),
const SizedBox(height: 10),
- // Text
+ // Review text
Text(
text,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
- color: AppColors.primaryDark,
+ color: Colors.black,
height: 1.5,
),
),
const SizedBox(height: 12),
- // Author
- Text(
- authorName,
- style: const TextStyle(
- fontFamily: 'Fractul',
- fontSize: 14,
- fontWeight: FontWeight.w600,
- color: AppColors.primaryDark,
+ // Author line: "Name , Role · time ago"
+ RichText(
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: authorName,
+ style: const TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ fontWeight: FontWeight.w700,
+ color: AppColors.primaryDark,
+ ),
+ ),
+ if (authorRole.isNotEmpty || createdAt != null)
+ TextSpan(
+ text: ' , ${authorRole.isNotEmpty ? authorRole : ''}${createdAt != null ? ' · ${_formatRelativeTime(createdAt)}' : ''}',
+ style: const TextStyle(
+ fontFamily: 'Fractul',
+ fontSize: 14,
+ fontWeight: FontWeight.w300,
+ color: AppColors.primaryDark,
+ ),
+ ),
+ ],
),
),
- if (authorRole.isNotEmpty)
- Text(
- authorRole,
- style: TextStyle(
- fontFamily: 'SourceSerif4',
- fontSize: 13,
- color: AppColors.primaryDark.withValues(alpha: 0.6),
- ),
- ),
],
),
);
@@ -2526,3 +2645,27 @@ class _TabItem {
final IconData icon;
const _TabItem({required this.label, required this.icon});
}
+
+/// Custom painter for the "Best value" ribbon triangle (matches Figma SVG).
+/// Ribbon painter matching Figma SVG: M28.5 0H0L62 134V73.99L28.5 0Z
+/// Shape: a pennant/flag anchored top-right of the plan card.
+class _RibbonPainter extends CustomPainter {
+ @override
+ void paint(Canvas canvas, Size size) {
+ final paint = Paint()..color = AppColors.accentOrange;
+ // Figma SVG path within 62×134 viewport:
+ // M28.5,0 → H0 → L62,134 → V73.99 → L28.5,0 → Z
+ final w = size.width; // 62
+ final h = size.height; // 134
+ final path = Path()
+ ..moveTo(w * (28.5 / 62), 0) // (28.5, 0) top point of ribbon
+ ..lineTo(0, 0) // (0, 0) top-left corner
+ ..lineTo(w, h) // (62, 134) bottom-right tip
+ ..lineTo(w, h * (73.99 / 134)) // (62, ~74) right edge notch
+ ..close();
+ canvas.drawPath(path, paint);
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+}