From 84e9cd96807f5b5a6d79001d92019f0dcbc40d31 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sun, 15 Mar 2026 00:57:14 +0530 Subject: [PATCH] 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. --- ios/Runner/Info.plist | 4 + .../screens/agent_edit_profile_screen.dart | 104 ++++++++++-------- .../faq/presentation/screens/faq_screen.dart | 8 +- .../presentation/screens/chat_screen.dart | 2 +- .../presentation/widgets/billing_tab.dart | 51 +++++---- 5 files changed, 99 insertions(+), 70 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 2d821e5..8054142 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -47,6 +47,10 @@ + LSApplicationQueriesSchemes + + mailto + NSPhotoLibraryUsageDescription We need access to your photo library to set your profile photo. NSCameraUsageDescription 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 a4b025f..9479350 100644 --- a/lib/features/agents/presentation/screens/agent_edit_profile_screen.dart +++ b/lib/features/agents/presentation/screens/agent_edit_profile_screen.dart @@ -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 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? 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? validation, - ) { - final key = '${slug}_number'; + Map? 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.from((getValue() ?? []) 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(); diff --git a/lib/features/faq/presentation/screens/faq_screen.dart b/lib/features/faq/presentation/screens/faq_screen.dart index c5da0a5..83b04c1 100644 --- a/lib/features/faq/presentation/screens/faq_screen.dart +++ b/lib/features/faq/presentation/screens/faq_screen.dart @@ -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); }, ), ], diff --git a/lib/features/messaging/presentation/screens/chat_screen.dart b/lib/features/messaging/presentation/screens/chat_screen.dart index 4c0c7c4..46c0f06 100644 --- a/lib/features/messaging/presentation/screens/chat_screen.dart +++ b/lib/features/messaging/presentation/screens/chat_screen.dart @@ -1158,7 +1158,7 @@ class _ChatScreenState extends ConsumerState { 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, diff --git a/lib/features/profile/presentation/widgets/billing_tab.dart b/lib/features/profile/presentation/widgets/billing_tab.dart index a9c53a7..77637bd 100644 --- a/lib/features/profile/presentation/widgets/billing_tab.dart +++ b/lib/features/profile/presentation/widgets/billing_tab.dart @@ -173,7 +173,7 @@ class _BillingTabState extends ConsumerState { 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 { 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 { final repo = ref.read(profileRepositoryProvider); final url = await repo.createPortalSession(); if (!mounted) return; - await Navigator.of(context).push?>( + await Navigator.of(context, rootNavigator: true).push?>( MaterialPageRoute( builder: (_) => StripeCheckoutScreen( url: url, @@ -583,7 +582,7 @@ class _BillingTabState extends ConsumerState { final planId = plans.first['id'] as String; final url = await repo.createCheckoutSession(planId); if (!mounted) return; - final result = await Navigator.of(context).push?>( + final result = await Navigator.of(context, rootNavigator: true).push?>( MaterialPageRoute( builder: (_) => StripeCheckoutScreen( url: url, @@ -643,6 +642,16 @@ class _BillingTabState extends ConsumerState { } } + /// 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);