From eb774b72f6826e8b80c37dd81412153ffc3975b7 Mon Sep 17 00:00:00 2001 From: Vusa Date: Fri, 26 Jun 2026 13:56:17 +0200 Subject: [PATCH] adding delete url --- ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Podfile | 43 ++ lib/routes.dart | 6 + .../delete_account/delete_account_screen.dart | 541 ++++++++++++++++++ lib/screens/more/more_screen.dart | 7 + 6 files changed, 599 insertions(+) create mode 100644 ios/Podfile create mode 100644 lib/screens/delete_account/delete_account_screen.dart diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/routes.dart b/lib/routes.dart index f84c886..56ff6f8 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -34,6 +34,7 @@ import 'package:qpay/screens/users/screens/user_list_screen.dart'; import 'package:qpay/screens/more/more_screen.dart'; import 'package:qpay/screens/uptime/uptime_status_screen.dart'; import 'package:qpay/screens/contact/contact_screen.dart'; +import 'package:qpay/screens/delete_account/delete_account_screen.dart'; /// Tracks whether the splash animation is complete so that /// GoRouter's redirect can show the splash first, then release @@ -156,6 +157,11 @@ GoRouter buildRouter() { path: '/splash', builder: (context, state) => const SplashScreen(), ), + // Publicly accessible account deletion screen (Play Store requirement) + GoRoute( + path: '/delete-account', + builder: (context, state) => const DeleteAccountScreen(), + ), ShellRoute( builder: (context, state, child) { final location = GoRouterState.of(context).uri.toString(); diff --git a/lib/screens/delete_account/delete_account_screen.dart b/lib/screens/delete_account/delete_account_screen.dart new file mode 100644 index 0000000..20d6e96 --- /dev/null +++ b/lib/screens/delete_account/delete_account_screen.dart @@ -0,0 +1,541 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../models/responsive_policy.dart'; + +/// A publicly accessible screen that allows users to request account +/// deletion in compliance with Google Play Store Data Deletion +/// requirements. +/// +/// This screen: +/// - Explains what data is deleted and retained. +/// - Provides a clear call-to-action for deletion. +/// - Includes a contact fallback for support. +class DeleteAccountScreen extends StatelessWidget { + const DeleteAccountScreen({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; + + return Scaffold( + backgroundColor: isDark + ? const Color(0xFF0D0D0D) + : const Color(0xFFF8F9FA), + appBar: AppBar( + leading: context.canPop() + ? IconButton( + onPressed: () => context.pop(), + icon: const Icon(Icons.arrow_back_rounded), + color: theme.colorScheme.primary, + ) + : const SizedBox.shrink(), + title: Text( + 'Request Account Deletion', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 18, + color: isDark ? Colors.white : Colors.black87, + ), + ), + centerTitle: true, + surfaceTintColor: Colors.transparent, + backgroundColor: isDark + ? const Color(0xFF0D0D0D) + : const Color(0xFFF8F9FA), + ), + body: Center( + child: LayoutBuilder( + builder: (context, constraints) { + final maxWidth = constraints.maxWidth > ResponsivePolicy.md + ? ResponsivePolicy.md.toDouble() + : constraints.maxWidth; + + return SizedBox( + width: maxWidth, + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + // Header / intro card + _buildHeaderCard(theme, isDark), + const SizedBox(height: 16), + + // What happens to your data + _buildDataDeletionCard(theme, isDark), + const SizedBox(height: 16), + + // Action card + _buildActionCard(theme, isDark, context), + const SizedBox(height: 16), + + // Contact fallback + _buildContactCard(theme, isDark), + const SizedBox(height: 32), + ], + ), + ); + }, + ), + ), + ); + } + + Widget _buildHeaderCard(ThemeData theme, bool isDark) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 28, horizontal: 24), + decoration: _cardDecoration(theme, isDark), + child: Column( + children: [ + Container( + width: 72, + height: 72, + decoration: BoxDecoration( + color: Colors.red.shade50, + shape: BoxShape.circle, + border: Border.all(color: Colors.red.shade200, width: 1.5), + ), + child: Icon( + Icons.delete_forever_rounded, + size: 36, + color: Colors.red.shade600, + ), + ), + const SizedBox(height: 20), + Text( + 'Delete Your Account', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + Text( + 'We\'re sorry to see you go. Please read the information ' + 'below before proceeding with your deletion request.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white60 : Colors.grey.shade600, + height: 1.5, + ), + ), + ], + ), + ); + } + + Widget _buildDataDeletionCard(ThemeData theme, bool isDark) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: _cardDecoration(theme, isDark), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.info_outline_rounded, + size: 22, + color: theme.colorScheme.primary, + ), + const SizedBox(width: 10), + Text( + 'What happens when you delete your account?', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 20), + _buildInfoPoint( + isDark: isDark, + icon: Icons.person_remove_rounded, + title: 'Profile & Personal Data', + description: + 'Your profile information, including your name, phone number, ' + 'email address, and profile picture will be permanently erased.', + ), + const SizedBox(height: 14), + _buildInfoPoint( + isDark: isDark, + icon: Icons.history_rounded, + title: 'Transaction History', + description: + 'All transaction records, payment history, and receipts ' + 'associated with your account will be deleted.', + ), + const SizedBox(height: 14), + _buildInfoPoint( + isDark: isDark, + icon: Icons.group_remove_rounded, + title: 'Group & Recipient Data', + description: + 'Any groups you have created and recipient data you have ' + 'stored will be removed from our systems.', + ), + const SizedBox(height: 14), + _buildInfoPoint( + isDark: isDark, + icon: Icons.schedule_rounded, + title: 'Retention Period', + description: + 'Some data may be retained for up to 90 days as required by ' + 'legal and regulatory obligations. After this period, all ' + 'remaining data will be permanently deleted.', + ), + const SizedBox(height: 14), + _buildInfoPoint( + isDark: isDark, + icon: Icons.undo_rounded, + title: 'Irreversible Action', + description: + 'Account deletion is permanent and cannot be undone once ' + 'processed. Please ensure you have exported any data you ' + 'wish to keep before submitting your request.', + ), + ], + ), + ); + } + + Widget _buildInfoPoint({ + required bool isDark, + required IconData icon, + required String title, + required String description, + }) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 2), + child: Icon(icon, size: 20, color: Colors.red.shade400), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 4), + Text( + description, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white54 : Colors.grey.shade600, + height: 1.4, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildActionCard(ThemeData theme, bool isDark, BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: _cardDecoration(theme, isDark), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.send_rounded, + size: 22, + color: theme.colorScheme.primary, + ), + const SizedBox(width: 10), + Text( + 'How to Request Deletion', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 16), + Text( + 'To ensure the security of your account, all deletion ' + 'requests must be verified. Please use one of the ' + 'following methods to submit your request.', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white60 : Colors.grey.shade600, + height: 1.5, + ), + ), + const SizedBox(height: 20), + + // Email deletion request + _buildActionButton( + context: context, + isDark: isDark, + icon: Icons.email_rounded, + iconColor: const Color(0xFFEA4335), + title: 'Request via Email', + subtitle: 'Send us a deletion request from your registered email', + onTap: () { + launchUrl( + Uri( + scheme: 'mailto', + path: 'support@velocityafrica.net', + queryParameters: { + 'subject': 'Account Deletion Request', + 'body': + 'Please delete my QPay account. My registered phone ' + 'number is: [enter your phone number]', + }, + ), + mode: LaunchMode.externalApplication, + ); + }, + ), + const SizedBox(height: 12), + + // Processing note + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon( + Icons.access_time_rounded, + size: 18, + color: isDark ? Colors.white60 : Colors.grey.shade700, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + 'We process deletion requests within 7 business days. ' + 'You will receive a confirmation email once your ' + 'account has been deleted.', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white54 : Colors.grey.shade600, + height: 1.4, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildActionButton({ + required BuildContext context, + required bool isDark, + required IconData icon, + required Color iconColor, + required String title, + required String subtitle, + required VoidCallback onTap, + }) { + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: isDark + ? Colors.white.withValues(alpha: 0.03) + : Colors.grey.shade50, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isDark + ? Colors.white.withValues(alpha: 0.04) + : Colors.grey.shade100, + width: 1, + ), + ), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: iconColor.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(14), + ), + child: Center(child: Icon(icon, color: iconColor, size: 24)), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 2), + Text( + subtitle, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white54 : Colors.grey.shade600, + ), + ), + ], + ), + ), + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: iconColor.withValues(alpha: 0.1), + ), + child: const Icon( + Icons.arrow_forward_rounded, + size: 16, + color: Colors.grey, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildContactCard(ThemeData theme, bool isDark) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: _cardDecoration(theme, isDark), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.help_outline_rounded, + size: 22, + color: theme.colorScheme.primary, + ), + const SizedBox(width: 10), + Text( + 'Need Help?', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + 'If you have any questions about the deletion process or ' + 'would like assistance, our support team is here to help.', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white60 : Colors.grey.shade600, + height: 1.5, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Icon( + Icons.phone_rounded, + size: 18, + color: theme.colorScheme.primary, + ), + const SizedBox(width: 8), + Text( + '+263 78 777 0295', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.email_outlined, + size: 18, + color: theme.colorScheme.primary, + ), + const SizedBox(width: 8), + Text( + 'support@qpay.co.zw', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + ], + ), + ); + } + + BoxDecoration _cardDecoration(ThemeData theme, bool isDark) { + return BoxDecoration( + color: isDark ? const Color(0xFF1A1A2E) : Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: isDark + ? Colors.white.withValues(alpha: 0.06) + : Colors.grey.shade200, + width: 1, + ), + boxShadow: [ + BoxShadow( + color: isDark + ? Colors.black.withValues(alpha: 0.3) + : Colors.black.withValues(alpha: 0.04), + blurRadius: 16, + offset: const Offset(0, 4), + ), + ], + ); + } +} diff --git a/lib/screens/more/more_screen.dart b/lib/screens/more/more_screen.dart index b9b7897..0286c4c 100644 --- a/lib/screens/more/more_screen.dart +++ b/lib/screens/more/more_screen.dart @@ -97,6 +97,13 @@ class _MoreScreenState extends State { subtitle: 'Get in touch with our team', onTap: () => context.push('/contact'), ), + _NavOptionTile( + icon: Icons.delete_forever_outlined, + title: 'Request Account Deletion', + subtitle: 'Permanently delete your account and data', + onTap: () => context.push('/delete-account'), + ), + const SizedBox(height: 8), _NavOptionTile( icon: Icons.logout, title: 'Logout',