From 344f8ec87403bfa63e60456ba1b69f0608937fa3 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Wed, 15 Jan 2025 09:53:15 +0200 Subject: [PATCH 1/4] improve phone number counter --- .../screens/identity_verification_screen.dart | 121 +++++++++++++----- 1 file changed, 91 insertions(+), 30 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index cf214ab6..232828a0 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -72,6 +72,37 @@ class _IdentityVerificationScreenState Timer? emailTimer; ValueNotifier countdownNotifier = ValueNotifier(-1); + int phoneCountdown = 120; + Timer? phoneTimer; + ValueNotifier phoneCountdownNotifier = ValueNotifier(-1); + + void startOrResumePhoneCountdown() { + int currentTime = DateTime.now().millisecondsSinceEpoch; + int lockedUntil = + Globals().smsSentOn + (Globals().smsMinutesCoolDown * 60 * 1000); + int timeLeft = ((lockedUntil - currentTime) / 1000).round(); + + if (timeLeft > 0) { + phoneCountdownNotifier.value = timeLeft; + + phoneTimer?.cancel(); + phoneTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + int remainingTime = + ((lockedUntil - DateTime.now().millisecondsSinceEpoch) / 1000) + .round(); + + if (remainingTime > 0) { + phoneCountdownNotifier.value = remainingTime; + } else { + phoneCountdownNotifier.value = -1; + timer.cancel(); + } + }); + } else { + phoneCountdownNotifier.value = -1; + } + } + void startOrResumeEmailCountdown({bool startNew = false}) { int currentTime = DateTime.now().millisecondsSinceEpoch; int lockedUntil = @@ -123,6 +154,10 @@ class _IdentityVerificationScreenState if (mounted) { setState(() { phoneVerified = Globals().phoneVerified.value; + if (phoneVerified){ + phoneCountdownNotifier.value = -1; + phoneTimer?.cancel(); + } Globals().smsSentOn = 0; }); } @@ -155,22 +190,28 @@ class _IdentityVerificationScreenState checkPhoneStatus(); getUserValues(); startOrResumeEmailCountdown(); + startOrResumePhoneCountdown(); } @override void dispose() { emailTimer?.cancel(); + phoneTimer?.cancel(); + phoneCountdownNotifier.dispose(); countdownNotifier.dispose(); super.dispose(); } checkPhoneStatus() { - if (Globals().smsSentOn + (Globals().smsMinutesCoolDown * 60 * 1000) > - DateTime.now().millisecondsSinceEpoch) { - return Globals().hidePhoneButton.value = true; - } + int currentTime = DateTime.now().millisecondsSinceEpoch; + int lockedUntil = + Globals().smsSentOn + (Globals().smsMinutesCoolDown * 60 * 1000); - return Globals().hidePhoneButton.value = false; + if (lockedUntil > currentTime) { + Globals().hidePhoneButton.value = true; + } else if (phoneCountdownNotifier.value <= 0) { + Globals().hidePhoneButton.value = false; + } } void getUserValues() { @@ -924,7 +965,7 @@ class _IdentityVerificationScreenState return _changeEmailDialog(false); } - if (step == 2) { + if (step == 2 && phoneCountdownNotifier.value == -1) { if (Globals().hidePhoneButton.value == true) { return; } @@ -1035,29 +1076,50 @@ class _IdentityVerificationScreenState step == 2 && Globals().hidePhoneButton.value == true - ? Row( - children: [ - Text( - 'SMS sent, retry in ${calculateMinutes()} minute${calculateMinutes() == '1' ? '' : 's'}', - overflow: TextOverflow.clip, - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - fontWeight: - FontWeight.bold, - color: Theme.of(context) - .colorScheme - .warning), - ) - ], - ) + ? Row(children: [ + ValueListenableBuilder( + valueListenable: + phoneCountdownNotifier, + builder: (context, remainingTime, + child) { + if (remainingTime > 0) { + String formattedTime; + if (remainingTime >= 60) { + int minutes = remainingTime ~/ + 60; + int seconds = remainingTime % + 60; + formattedTime = + '${minutes}m ${seconds}s'; + } else { + formattedTime = + '${remainingTime}s'; + } + + return Text( + 'SMS sent, retry in $formattedTime', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: + FontWeight.bold, + color: Theme.of(context) + .colorScheme + .warning, + ), + ); + } + return Container(); + }, + ) + ]) : Container(), ]))), Globals().hidePhoneButton.value == true && step == 2 ? Container() : ValueListenableBuilder( - valueListenable: countdownNotifier, + valueListenable: step == 1 ? countdownNotifier : phoneCountdownNotifier, builder: (context, countdownValue, child) { return Padding( padding: const EdgeInsets.only(left: 12), @@ -1106,13 +1168,11 @@ class _IdentityVerificationScreenState int currentTime = DateTime.now().millisecondsSinceEpoch; int lockedUntil = Globals().smsSentOn + (Globals().smsMinutesCoolDown * 60 * 1000); - String difference = - ((lockedUntil - currentTime) / 1000 / 60).round().toString(); + int remainingTime = ((lockedUntil - currentTime) / 1000).round(); - if (int.parse(difference) >= 0) { - return difference; + if (remainingTime > 0) { + return (remainingTime / 60).ceil().toString(); } - return '0'; } @@ -1876,10 +1936,10 @@ class _IdentityVerificationScreenState FlutterPkid client = await getPkidClient(); client.setPKidDoc('phone', json.encode({'phone': phone})); - startPhoneNumberCounter(); return; } else { PhoneAlertDialogState().sendPhoneVerification(); + startPhoneNumberCounter(); return; } } @@ -1938,6 +1998,7 @@ class _IdentityVerificationScreenState Globals().hidePhoneButton.value = true; Globals().smsSentOn = DateTime.now().millisecondsSinceEpoch; + startOrResumePhoneCountdown(); phoneSendDialog(context); } From b99228996ce4f1e2a427a400d51ef8cd5892b8e9 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Wed, 15 Jan 2025 09:57:18 +0200 Subject: [PATCH 2/4] add return stmt to checkPhoneStatus --- app/lib/screens/identity_verification_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index 232828a0..f3448fbc 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -208,9 +208,9 @@ class _IdentityVerificationScreenState Globals().smsSentOn + (Globals().smsMinutesCoolDown * 60 * 1000); if (lockedUntil > currentTime) { - Globals().hidePhoneButton.value = true; + return Globals().hidePhoneButton.value = true; } else if (phoneCountdownNotifier.value <= 0) { - Globals().hidePhoneButton.value = false; + return Globals().hidePhoneButton.value = false; } } From 5e8e7841412dc2ffaf0410c4b519425ba34321f0 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Sun, 19 Jan 2025 09:58:35 +0200 Subject: [PATCH 3/4] improve formatting time to be in separate function --- .../screens/identity_verification_screen.dart | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index f3448fbc..0a505845 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -154,7 +154,7 @@ class _IdentityVerificationScreenState if (mounted) { setState(() { phoneVerified = Globals().phoneVerified.value; - if (phoneVerified){ + if (phoneVerified) { phoneCountdownNotifier.value = -1; phoneTimer?.cancel(); } @@ -1084,18 +1084,8 @@ class _IdentityVerificationScreenState child) { if (remainingTime > 0) { String formattedTime; - if (remainingTime >= 60) { - int minutes = remainingTime ~/ - 60; - int seconds = remainingTime % - 60; - formattedTime = - '${minutes}m ${seconds}s'; - } else { - formattedTime = - '${remainingTime}s'; - } - + formattedTime = + _formatTime(remainingTime); return Text( 'SMS sent, retry in $formattedTime', style: Theme.of(context) @@ -1119,7 +1109,9 @@ class _IdentityVerificationScreenState Globals().hidePhoneButton.value == true && step == 2 ? Container() : ValueListenableBuilder( - valueListenable: step == 1 ? countdownNotifier : phoneCountdownNotifier, + valueListenable: step == 1 + ? countdownNotifier + : phoneCountdownNotifier, builder: (context, countdownValue, child) { return Padding( padding: const EdgeInsets.only(left: 12), @@ -1164,6 +1156,16 @@ class _IdentityVerificationScreenState ])); } + String _formatTime(int remainingTime) { + if (remainingTime >= 60) { + int minutes = remainingTime ~/ 60; + int seconds = remainingTime % 60; + return '${minutes}m ${seconds}s'; + } else { + return '${remainingTime}s'; + } + } + String calculateMinutes() { int currentTime = DateTime.now().millisecondsSinceEpoch; int lockedUntil = From 56842320e2f2af2bf4111d4b752d6e39880e979e Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Tue, 28 Jan 2025 13:06:26 +0200 Subject: [PATCH 4/4] fix bug in counter --- .../screens/identity_verification_screen.dart | 273 +++++++++--------- app/lib/widgets/phone_widget.dart | 9 +- 2 files changed, 139 insertions(+), 143 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index 866243bf..dc865083 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -225,13 +225,12 @@ class _IdentityVerificationScreenState ), ); } - + @override Widget build(BuildContext context) { return LayoutDrawer( titleText: 'Identity', - content: - FutureBuilder( + content: FutureBuilder( future: getEmail(), builder: (ctx, snapshot) { if (snapshot.connectionState == ConnectionState.done) { @@ -239,55 +238,57 @@ class _IdentityVerificationScreenState return pleaseWait(context); } return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 20), - AnimatedBuilder( - animation: Listenable.merge([ - Globals().emailVerified, - Globals().phoneVerified, - ]), - builder: (BuildContext context, _) { - return Column( - children: [ - ListTile( - leading: const Icon(Icons.person), - title: Text( - doubleName.isNotEmpty - ? doubleName.substring( - 0, doubleName.length - 5) - : 'Unknown', - ), - ), - customDivider(context: context), - FutureBuilder( - future: getPhrase(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Padding( - padding: const EdgeInsets.only(right: 2.0), - child: ListTile( - trailing: const Icon(Icons.visibility), - leading: const Icon(Icons.vpn_key), - title: const Text('Show phrase'), - onTap: () async { - _showPhrase(); - }, - ), - ); - } else { - return Container(); - } - }, - ), - customDivider(context: context), + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 20), + AnimatedBuilder( + animation: Listenable.merge([ + Globals().emailVerified, + Globals().phoneVerified, + ]), + builder: (BuildContext context, _) { + return Column( + children: [ + ListTile( + leading: const Icon(Icons.person), + title: Text( + doubleName.isNotEmpty + ? doubleName.substring( + 0, doubleName.length - 5) + : 'Unknown', + ), + ), + customDivider(context: context), + FutureBuilder( + future: getPhrase(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Padding( + padding: + const EdgeInsets.only(right: 2.0), + child: ListTile( + trailing: + const Icon(Icons.visibility), + leading: const Icon(Icons.vpn_key), + title: const Text('Show phrase'), + onTap: () async { + _showPhrase(); + }, + ), + ); + } else { + return Container(); + } + }, + ), + customDivider(context: context), // Step one: verify email _fillCard( - getCorrectState(1, emailVerified, - phoneVerified), + getCorrectState( + 1, emailVerified, phoneVerified), 1, email, Icons.email), @@ -298,8 +299,8 @@ class _IdentityVerificationScreenState (Globals().spendingLimit > 0 && spending > Globals().spendingLimit)) ? _fillCard( - getCorrectState(2, emailVerified, - phoneVerified), + getCorrectState( + 2, emailVerified, phoneVerified), 2, phone, Icons.phone) @@ -308,25 +309,21 @@ class _IdentityVerificationScreenState const ListTile( leading: Icon(Icons.info), title: Text( - 'KYC Verification has been moved to wallet page.' - ), - ), - + 'KYC Verification has been moved to wallet page.'), + ), ], ); }) ], ), - ) - - ); + )); } return pleaseWait(context); }, ), ); } - + Future copySeedPhrase() async { Clipboard.setData(ClipboardData(text: (await getPhrase()).toString())); @@ -563,9 +560,7 @@ class _IdentityVerificationScreenState children: [ Expanded( child: Text( - (text.isEmpty - ? 'Unknown' - : text), + (text.isEmpty ? 'Unknown' : text), ), ) ], @@ -601,83 +596,80 @@ class _IdentityVerificationScreenState } }, ), - step == 2 && - Globals().hidePhoneButton.value == - true - ? const SizedBox( - height: 5, - ) - : Container(), - step == 2 && - Globals().hidePhoneButton.value == - true - ? Row(children: [ - ValueListenableBuilder( - valueListenable: - phoneCountdownNotifier, - builder: (context, remainingTime, - child) { - if (remainingTime > 0) { - String formattedTime; - formattedTime = - _formatTime(remainingTime); - return Text( - 'SMS sent, retry in $formattedTime', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - fontWeight: - FontWeight.bold, - color: Theme.of(context) - .colorScheme - .warning, - ), - ); - } - return Container(); - }, - ) - ]) - : Container(), + if (step == 2) + ValueListenableBuilder( + valueListenable: + Globals().hidePhoneButton, + builder: + (context, hidePhoneButton, child) { + if (hidePhoneButton) { + return Column( + children: [ + const SizedBox(height: 5), + Row( + children: [ + ValueListenableBuilder( + valueListenable: + phoneCountdownNotifier, + builder: (context, + remainingTime, child) { + if (remainingTime > 0) { + String formattedTime = + _formatTime( + remainingTime); + return Text( + 'SMS sent, retry in $formattedTime', + style: + Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontWeight: + FontWeight + .bold, + color: Theme.of( + context) + .colorScheme + .warning, + ), + ); + } + return Container(); + }, + ), + ], + ), + ], + ); + } + return Container(); + }, + ), ]))), - Globals().hidePhoneButton.value == true && step == 2 - ? Container() - : ValueListenableBuilder( - valueListenable: step == 1 - ? countdownNotifier - : phoneCountdownNotifier, - builder: (context, countdownValue, child) { - return Padding( - padding: const EdgeInsets.only(left: 12), - child: ElevatedButton( - onPressed: countdownValue > 0 - ? null - : () async { - switch (step) { - // Verify email - case 1: - { - startOrResumeEmailCountdown( - startNew: true); - verifyEmail(); - } - break; - - // Verify phone - case 2: - { - await verifyPhone(); - } - break; - default: - {} - break; - } - }, - child: const Text('Verify'))); - } - ) + ValueListenableBuilder( + valueListenable: step == 1 + ? countdownNotifier + : phoneCountdownNotifier, + builder: (context, countdownValue, child) { + return Padding( + padding: const EdgeInsets.only(left: 12), + child: ElevatedButton( + onPressed: countdownValue > 0 + ? null + : () async { + if (step == 1) { + startOrResumeEmailCountdown( + startNew: true); + verifyEmail(); + } else if (step == 2) { + await verifyPhone(); + } + }, + child: const Text('Verify'), + ), + ); + }, + ), ], ), )) @@ -1070,6 +1062,7 @@ class _IdentityVerificationScreenState } void startPhoneNumberCounter() { + print("Hello from STart phone counter"); int currentTime = DateTime.now().millisecondsSinceEpoch; if (globals.tooManySmsAttempts && globals.lockedSmsUntil > currentTime) { globals.sendSmsAttempts = 0; @@ -1100,14 +1093,14 @@ class _IdentityVerificationScreenState showDialog( context: context, - builder: (BuildContext context) { + builder: (BuildContext childContext) { return AlertDialog( title: const Text('Too many attempts please wait one minute.'), actions: [ TextButton( child: const Text('OK'), onPressed: () { - Navigator.of(context).pop(); + Navigator.of(childContext).pop(); }, ), ], diff --git a/app/lib/widgets/phone_widget.dart b/app/lib/widgets/phone_widget.dart index f8696019..30656771 100644 --- a/app/lib/widgets/phone_widget.dart +++ b/app/lib/widgets/phone_widget.dart @@ -211,6 +211,7 @@ class PhoneAlertDialogState extends State { if (Globals().tooManySmsAttempts && Globals().lockedSmsUntil > currentTime) { + if (!mounted) return; Globals().sendSmsAttempts = 0; showDialog( context: context, @@ -238,6 +239,7 @@ class PhoneAlertDialogState extends State { Globals().tooManySmsAttempts = true; Globals().lockedSmsUntil = currentTime + 60000; + if (!mounted) return; showDialog( context: context, builder: (BuildContext context) { @@ -264,8 +266,9 @@ class PhoneAlertDialogState extends State { Globals().hidePhoneButton.value = true; Globals().smsSentOn = DateTime.now().millisecondsSinceEpoch; - phoneSendDialog(context); - - Navigator.pop(context); + if (mounted) { + phoneSendDialog(context); + Navigator.pop(context); + } } }