feat: enhance profile subscription UI with new assets, font adjustments, and SVG support.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -43,3 +43,7 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Signing
|
||||
/android/keystores/
|
||||
/android/key.properties
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
assets/icons/diamond_icon.svg
Normal file
3
assets/icons/diamond_icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.00025 5.49532H15.9652M12.224 1C12.4563 1 12.6854 1.05416 12.8932 1.15819C13.101 1.26223 13.2818 1.41328 13.4212 1.59938L15.6659 4.59625C15.8584 4.85336 15.9633 5.16562 15.9651 5.48696C15.9668 5.80831 15.8654 6.1217 15.6757 6.38089L9.69714 14.6118C9.55853 14.8047 9.37609 14.9619 9.16486 15.0702C8.95364 15.1786 8.7197 15.2352 8.48236 15.2352C8.24502 15.2352 8.01108 15.1786 7.79986 15.0702C7.58864 14.9619 7.40619 14.8047 7.26758 14.6118L1.28907 6.38089C1.09947 6.12162 0.99814 5.80818 1.00003 5.48684C1.00191 5.1655 1.10692 4.85328 1.29955 4.59625L3.5428 1.60162C3.68211 1.41491 3.863 1.2633 4.07109 1.15886C4.27917 1.05442 4.50873 1.00003 4.74149 1H12.224Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 856 B |
3
assets/icons/feature_check_icon.svg
Normal file
3
assets/icons/feature_check_icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.97899 0C4.02782 0 0 4.03305 0 8.99063C0 13.9482 4.02782 17.9813 8.97899 17.9813C13.9301 17.9813 17.958 13.9482 17.958 8.99063H15.9377C15.9377 12.8326 12.816 15.9584 8.97899 15.9584C5.14197 15.9584 2.02027 12.8326 2.02027 8.99063C2.02027 5.14863 5.14197 2.02289 8.97899 2.02289V0ZM14.516 1.93523L8.90042 8.95617L6.27631 6.80291L4.77832 8.62801L8.32576 11.541C8.44628 11.6405 8.58538 11.715 8.73496 11.7602C8.88453 11.8053 9.04159 11.8202 9.19697 11.804C9.35235 11.7877 9.50295 11.7407 9.63998 11.6655C9.77701 11.5904 9.89773 11.4887 9.99511 11.3664L16.3582 3.41194L14.516 1.93523Z" fill="#43A047"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 713 B |
BIN
assets/icons/need_help_illustration.jpg
Normal file
BIN
assets/icons/need_help_illustration.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/icons/wallet_illustration.jpg
Normal file
BIN
assets/icons/wallet_illustration.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 309 KiB |
@@ -343,11 +343,13 @@ class _AgentEditProfileScreenState
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F7FA),
|
||||
body: Column(
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: [
|
||||
// Header with circular back button (SVG) matching Figma
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
@@ -374,6 +376,7 @@ class _AgentEditProfileScreenState
|
||||
Expanded(child: _buildBody(asyncData)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ProfileSettingsScreen> {
|
||||
'Subscription & Payments',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 22,
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark,
|
||||
),
|
||||
@@ -1112,7 +1113,7 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
'Current Plan',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 18,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark,
|
||||
),
|
||||
@@ -1265,15 +1266,18 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
'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
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
@@ -1314,7 +1318,7 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Complete solutions for real estate agents and lenders to grow their business.',
|
||||
'Complete solutions for real estate\nagents and lenders to grow their\nbusiness.',
|
||||
style: TextStyle(
|
||||
fontFamily: 'SourceSerif4',
|
||||
fontSize: 14,
|
||||
@@ -1332,7 +1336,7 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 35,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
@@ -1341,7 +1345,7 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
@@ -1350,33 +1354,51 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark.withValues(alpha: 0.5),
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Get Premium Access button
|
||||
// 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,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
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: 13,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
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',
|
||||
@@ -1392,13 +1414,67 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// "Best value" ribbon on top-right corner
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
),
|
||||
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),
|
||||
|
||||
// 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<ProfileSettingsScreen> {
|
||||
),
|
||||
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<ProfileSettingsScreen> {
|
||||
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<ProfileSettingsScreen> {
|
||||
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<ProfileSettingsScreen> {
|
||||
|
||||
List<Widget> _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,18 +1717,23 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(24, 10, 24, 30),
|
||||
children: [
|
||||
const Text(
|
||||
// Title matching Figma
|
||||
const Center(
|
||||
child: Text(
|
||||
'Add Testimonials',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 15,
|
||||
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.',
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Center(
|
||||
child: Text(
|
||||
'What people are saying about you',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontFamily: 'SourceSerif4',
|
||||
fontSize: 14,
|
||||
@@ -1653,66 +1741,32 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
color: AppColors.primaryDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Generate link section
|
||||
_buildSectionCard(
|
||||
title: 'Testimonial Collection Link',
|
||||
subtitle: 'Share this link with your clients',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_testimonialLink != null) ...[
|
||||
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)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Create Testimonials section
|
||||
const Center(
|
||||
child: Text(
|
||||
_testimonialLink!,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'SourceSerif4',
|
||||
fontSize: 13,
|
||||
'Create Testimonials',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
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,
|
||||
foregroundColor: AppColors.primaryDark,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15)),
|
||||
),
|
||||
@@ -1721,34 +1775,98 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: AppColors.primaryDark))
|
||||
strokeWidth: 2, color: Colors.white))
|
||||
: const Text('Generate Link',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700)),
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white)),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Testimonials list header
|
||||
// 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: 1,
|
||||
height: 43,
|
||||
color: AppColors.primaryDark.withValues(alpha: 0.1),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// 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<ProfileSettingsScreen> {
|
||||
},
|
||||
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<ProfileSettingsScreen> {
|
||||
))
|
||||
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<ProfileSettingsScreen> {
|
||||
),
|
||||
)
|
||||
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,24 +1969,14 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
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(
|
||||
children: List.generate(
|
||||
5,
|
||||
(i) => Icon(
|
||||
i < rating ? Icons.star : Icons.star_border,
|
||||
@@ -1863,47 +1986,43 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||
: 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,
|
||||
// Author line: "Name , Role · time ago"
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: authorName,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryDark,
|
||||
),
|
||||
),
|
||||
if (authorRole.isNotEmpty)
|
||||
Text(
|
||||
authorRole,
|
||||
style: TextStyle(
|
||||
fontFamily: 'SourceSerif4',
|
||||
fontSize: 13,
|
||||
color: AppColors.primaryDark.withValues(alpha: 0.6),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user