feat: Enhance billing display with currency formatting, improve dynamic form field management by clearing cached controllers and using unique keys, and add mailto URL scheme support.

This commit is contained in:
pradeepkumar
2026-03-15 00:57:14 +05:30
parent d5a92e0b94
commit 84e9cd9680
5 changed files with 99 additions and 70 deletions

View File

@@ -47,6 +47,10 @@
</array>
</dict>
</dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mailto</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library to set your profile photo.</string>
<key>NSCameraUsageDescription</key>

View File

@@ -341,42 +341,36 @@ class _AgentEditProfileScreenState
Widget build(BuildContext context) {
final asyncData = ref.watch(_editProfileDataProvider);
return Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
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(
onTap: () => context.pop(),
child: SvgPicture.asset(
'assets/icons/back_circle_icon.svg',
width: 32,
height: 32,
),
return Column(
children: [
// Header with circular back button (SVG) matching Figma
Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
child: Row(
children: [
GestureDetector(
onTap: () => context.pop(),
child: SvgPicture.asset(
'assets/icons/back_circle_icon.svg',
width: 32,
height: 32,
),
const SizedBox(width: 12),
const Text(
'Edit Profile',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
const SizedBox(width: 12),
const Text(
'Edit Profile',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
],
),
),
],
),
Expanded(child: _buildBody(asyncData)),
],
),
),
Expanded(child: _buildBody(asyncData)),
],
);
}
@@ -667,6 +661,13 @@ class _AgentEditProfileScreenState
onTap: () {
setState(() {
entries.removeAt(i);
// Clear cached controllers for this section's entries
// so they get recreated with correct values after
// re-indexing
final fieldSlugs =
fields.map((f) => f['slug'] as String? ?? '');
_tagControllers.removeWhere((key, _) =>
fieldSlugs.any((s) => key.startsWith('${s}_entry')));
});
},
child: const Icon(
@@ -688,6 +689,7 @@ class _AgentEditProfileScreenState
entries[i][slug] = val;
});
},
controllerKeySuffix: 'entry$i',
);
}),
],
@@ -731,6 +733,7 @@ class _AgentEditProfileScreenState
Map<String, dynamic> field, {
dynamic Function()? getValue,
void Function(dynamic)? setValue,
String? controllerKeySuffix,
}) {
final slug = field['slug'] as String? ?? '';
final fieldType = field['fieldType'] as String? ?? 'TEXT';
@@ -770,6 +773,7 @@ class _AgentEditProfileScreenState
placeholder,
validation,
false,
controllerKeySuffix: controllerKeySuffix,
);
break;
case 'TEXTAREA':
@@ -780,6 +784,7 @@ class _AgentEditProfileScreenState
placeholder,
validation,
true,
controllerKeySuffix: controllerKeySuffix,
);
break;
case 'NUMBER':
@@ -789,6 +794,7 @@ class _AgentEditProfileScreenState
updateValue,
placeholder,
validation,
controllerKeySuffix: controllerKeySuffix,
);
break;
case 'DATE':
@@ -851,6 +857,7 @@ class _AgentEditProfileScreenState
currentValue,
updateValue,
placeholder,
controllerKeySuffix: controllerKeySuffix,
);
break;
case 'RANGE':
@@ -870,6 +877,7 @@ class _AgentEditProfileScreenState
placeholder,
validation,
false,
controllerKeySuffix: controllerKeySuffix,
);
}
@@ -937,9 +945,12 @@ class _AgentEditProfileScreenState
void Function(dynamic) setValue,
String placeholder,
Map<String, dynamic>? validation,
bool multiline,
) {
final key = '${slug}_text';
bool multiline, {
String? controllerKeySuffix,
}) {
final key = controllerKeySuffix != null
? '${slug}_${controllerKeySuffix}_text'
: '${slug}_text';
if (!_tagControllers.containsKey(key)) {
final controller = TextEditingController(text: '${getValue() ?? ''}');
_tagControllers[key] = controller;
@@ -974,9 +985,12 @@ class _AgentEditProfileScreenState
dynamic Function() getValue,
void Function(dynamic) setValue,
String placeholder,
Map<String, dynamic>? validation,
) {
final key = '${slug}_number';
Map<String, dynamic>? validation, {
String? controllerKeySuffix,
}) {
final key = controllerKeySuffix != null
? '${slug}_${controllerKeySuffix}_number'
: '${slug}_number';
if (!_tagControllers.containsKey(key)) {
final val = getValue();
final controller = TextEditingController(text: val != null ? '$val' : '');
@@ -1377,16 +1391,20 @@ class _AgentEditProfileScreenState
String slug,
dynamic Function() getValue,
void Function(dynamic) setValue,
String placeholder,
) {
String placeholder, {
String? controllerKeySuffix,
}) {
final tags = List<String>.from((getValue() ?? <String>[]) as List);
if (!_tagControllers.containsKey(slug)) {
final tagKey = controllerKeySuffix != null
? '${slug}_${controllerKeySuffix}'
: slug;
if (!_tagControllers.containsKey(tagKey)) {
final controller = TextEditingController();
_tagControllers[slug] = controller;
_tagControllers[tagKey] = controller;
_controllers.add(controller);
}
final controller = _tagControllers[slug]!;
final controller = _tagControllers[tagKey]!;
void addTag(String text) {
final trimmed = text.trim();

View File

@@ -400,17 +400,15 @@ class _StillNeedHelpSection extends ConsumerWidget {
_buildSupportButton(
icon: Icons.email_outlined,
label: 'Email Support',
onTap: () async {
onTap: () {
final uri = Uri(
scheme: 'mailto',
path: 'support@requesn.com',
path: 'support@re-quest.co',
queryParameters: {
'subject': 'Support Request',
},
);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
launchUrl(uri, mode: LaunchMode.externalApplication);
},
),
],

View File

@@ -1158,7 +1158,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
void _showImageFullScreen(BuildContext context, String imageUrl) {
final isDirectUrl = imageUrl.startsWith('http://') || imageUrl.startsWith('https://');
Navigator.of(context).push(
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (_) => Scaffold(
backgroundColor: Colors.black,

View File

@@ -173,7 +173,7 @@ class _BillingTabState extends ConsumerState<BillingTab> {
text: TextSpan(
children: [
TextSpan(
text: '\$${plan!['amount']}',
text: '\$${_formatAmount(plan!['amount'])}',
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 35,
@@ -340,25 +340,24 @@ class _BillingTabState extends ConsumerState<BillingTab> {
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)),
ElevatedButton(
onPressed: _startCheckout,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.accentOrange,
side: const BorderSide(color: AppColors.accentOrange),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
),
child: const Text('Get Premium Access',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white)),
),
const Spacer(),
ClipRRect(
@@ -556,7 +555,7 @@ class _BillingTabState extends ConsumerState<BillingTab> {
final repo = ref.read(profileRepositoryProvider);
final url = await repo.createPortalSession();
if (!mounted) return;
await Navigator.of(context).push<Map<String, dynamic>?>(
await Navigator.of(context, rootNavigator: true).push<Map<String, dynamic>?>(
MaterialPageRoute(
builder: (_) => StripeCheckoutScreen(
url: url,
@@ -583,7 +582,7 @@ class _BillingTabState extends ConsumerState<BillingTab> {
final planId = plans.first['id'] as String;
final url = await repo.createCheckoutSession(planId);
if (!mounted) return;
final result = await Navigator.of(context).push<Map<String, dynamic>?>(
final result = await Navigator.of(context, rootNavigator: true).push<Map<String, dynamic>?>(
MaterialPageRoute(
builder: (_) => StripeCheckoutScreen(
url: url,
@@ -643,6 +642,16 @@ class _BillingTabState extends ConsumerState<BillingTab> {
}
}
/// Stripe amounts are in cents — convert to dollars.
String _formatAmount(dynamic amount) {
final cents = amount is num ? amount : num.tryParse('$amount') ?? 0;
final dollars = cents / 100;
// Show whole dollars if no cents, otherwise 2 decimal places
return dollars == dollars.roundToDouble()
? dollars.toInt().toString()
: dollars.toStringAsFixed(2);
}
String _formatDate(String dateStr) {
try {
final date = DateTime.parse(dateStr);