diff --git a/lib/src/services/key_verification.service.dart b/lib/src/services/key_verification.service.dart index 62ee6a21..df656698 100644 --- a/lib/src/services/key_verification.service.dart +++ b/lib/src/services/key_verification.service.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:cryptography_plus/cryptography_plus.dart'; +import 'package:flutter/material.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; @@ -17,6 +18,24 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart'; class KeyVerificationService { + static final StreamController _verificationSuccessCloseController = + StreamController.broadcast(); + + static Stream get onVerificationSuccessClose => + _verificationSuccessCloseController.stream; + + static void notifyVerificationSuccessClose() { + _verificationSuccessCloseController.add(null); + } + + static void closeVerificationFlows(BuildContext context) { + Navigator.popUntil(context, (route) { + final name = route.settings.name; + if (name == null) return false; + return !name.contains('qr_scanner') && !name.contains('verifybadge'); + }); + } + static Future> getNewSecretVerificationToken() async { final token = getRandomUint8List(16); await twonlyDB.keyVerificationDao.insertVerificationToken(token); diff --git a/lib/src/visual/components/profile_qr_code.comp.dart b/lib/src/visual/components/profile_qr_code.comp.dart index c10eb852..3d113a07 100644 --- a/lib/src/visual/components/profile_qr_code.comp.dart +++ b/lib/src/visual/components/profile_qr_code.comp.dart @@ -1,9 +1,11 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/key_verification.service.dart'; import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.utils.dart'; @@ -22,59 +24,73 @@ class ProfileQrCodeComp extends StatefulWidget { BuildContext context, { Contact? contact, bool openToVerify = false, - }) { - return showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) { - return Container( - decoration: BoxDecoration( - color: context.color.surface, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), + }) async { + StreamSubscription? subscription; + + if (openToVerify) { + subscription = KeyVerificationService.onVerificationSuccessClose.listen((_) { + if (context.mounted) { + KeyVerificationService.closeVerificationFlows(context); + } + }); + } + + try { + await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return Container( + decoration: BoxDecoration( + color: context.color.surface, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), ), - ), - padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(width: double.infinity), - Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: context.color.onSurface.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(2), + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: double.infinity), + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: context.color.onSurface.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(2), + ), ), - ), - const SizedBox(height: 24), - Text( - openToVerify - ? (contact != null - ? context.lang.letUserScanQrCode( - getContactDisplayName(contact), - ) - : context.lang.letFriendScanQrToVerify) - : (contact != null - ? context.lang.letUserScanQrCode( - getContactDisplayName(contact), - ) - : context.lang.addContactQrSheetSubtext), - style: TextStyle( - fontSize: 14, - color: context.color.onSurface.withValues(alpha: 0.6), + const SizedBox(height: 24), + Text( + openToVerify + ? (contact != null + ? context.lang.letUserScanQrCode( + getContactDisplayName(contact), + ) + : context.lang.letFriendScanQrToVerify) + : (contact != null + ? context.lang.letUserScanQrCode( + getContactDisplayName(contact), + ) + : context.lang.addContactQrSheetSubtext), + style: TextStyle( + fontSize: 14, + color: context.color.onSurface.withValues(alpha: 0.6), + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - const ProfileQrCodeComp(), - const SizedBox(height: 34), - ], - ), - ); - }, - ); + const SizedBox(height: 24), + const ProfileQrCodeComp(), + const SizedBox(height: 34), + ], + ), + ); + }, + ); + } finally { + await subscription?.cancel(); + } } @override diff --git a/lib/src/visual/components/verification_success_dialog.comp.dart b/lib/src/visual/components/verification_success_dialog.comp.dart index ee48d804..0c3be280 100644 --- a/lib/src/visual/components/verification_success_dialog.comp.dart +++ b/lib/src/visual/components/verification_success_dialog.comp.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/key_verification.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/verification_success_animation.comp.dart'; @@ -18,11 +19,16 @@ class VerificationSuccessDialog extends StatelessWidget { final String? message; /// Utility method to easily present this dialog. - static Future show(BuildContext context, Contact contact, {String? message}) { + static Future show( + BuildContext context, + Contact contact, { + String? message, + }) { return showDialog( context: context, barrierDismissible: false, - builder: (context) => VerificationSuccessDialog(contact: contact, message: message), + builder: (context) => + VerificationSuccessDialog(contact: contact, message: message), ); } @@ -75,7 +81,10 @@ class VerificationSuccessDialog extends StatelessWidget { ), const SizedBox(height: 28), MyButton( - onPressed: () => Navigator.pop(context), + onPressed: () { + KeyVerificationService.notifyVerificationSuccessClose(); + Navigator.pop(context); + }, child: Text(context.lang.close), ), ], diff --git a/lib/src/visual/views/camera/camera_qr_scanner.view.dart b/lib/src/visual/views/camera/camera_qr_scanner.view.dart index d23b1b7d..c5f45f9f 100644 --- a/lib/src/visual/views/camera/camera_qr_scanner.view.dart +++ b/lib/src/visual/views/camera/camera_qr_scanner.view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/key_verification.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; @@ -34,11 +35,7 @@ class QrCodeScannerViewState extends State { if (widget.openToVerify && widget.contact != null) { _mainCameraController.onVerificationSuccessDismissed = (contact) { if (mounted) { - Navigator.popUntil(context, (route) { - final name = route.settings.name; - if (name == null) return true; - return !name.contains('qr_scanner') && !name.contains('verifybadge'); - }); + KeyVerificationService.closeVerificationFlows(context); } }; }