From bf6de8162cf274fa6454bce9101ceae1aefe6d27 Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Sat, 14 Sep 2024 22:14:54 +0900 Subject: [PATCH] Refactor cancelable task logic --- lib/src/components/dialogs.dart | 42 ++++++++++++++++++++++++++ lib/src/pages/document/encryption.dart | 28 +++++------------ lib/src/pages/document/links.dart | 38 ++++++++--------------- 3 files changed, 62 insertions(+), 46 deletions(-) diff --git a/lib/src/components/dialogs.dart b/lib/src/components/dialogs.dart index d1de507..f1f7ff6 100644 --- a/lib/src/components/dialogs.dart +++ b/lib/src/components/dialogs.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:org_flutter/org_flutter.dart'; +import 'package:orgro/src/debug.dart'; import 'package:orgro/src/preferences.dart'; import 'package:orgro/src/serialization.dart'; import 'package:share_plus/share_plus.dart'; @@ -164,6 +165,47 @@ class InputPasswordDialog extends StatelessWidget { } } +Future<({bool succeeded, T? result})> cancelableProgressTask( + BuildContext context, { + required Future task, + required String dialogTitle, +}) async { + var canceled = false; + + final dialogFuture = showDialog<(T, Object?)>( + context: context, + builder: (context) => ProgressIndicatorDialog( + title: dialogTitle, + dismissable: true, + ), + ); + + task.then((result) { + if (!canceled && context.mounted) Navigator.pop(context, (result, null)); + }).onError((error, stackTrace) { + if (context.mounted) showErrorSnackBar(context, error); + logError(error, stackTrace); + if (!canceled && context.mounted) Navigator.pop(context, (null, error)); + }); + + // Popped will be one of: + // - null if the user closed the dialog by tapping outside or using the back + // button/gesture + // - (task result, null) if completed normally + // - (null, error) if completed with error + final popped = await dialogFuture; + + if (popped == null) { + canceled = true; + return (succeeded: false, result: null); + } + + final (result, error) = popped; + return error == null + ? (succeeded: true, result: result) + : (succeeded: false, result: null); +} + class ProgressIndicatorDialog extends StatelessWidget { const ProgressIndicatorDialog({ required this.title, diff --git a/lib/src/pages/document/encryption.dart b/lib/src/pages/document/encryption.dart index b47adcc..8bf8d4b 100644 --- a/lib/src/pages/document/encryption.dart +++ b/lib/src/pages/document/encryption.dart @@ -24,28 +24,14 @@ extension EncryptionHandler on DocumentPageState { ); if (password == null) return; if (!mounted) return; - var canceled = false; - time('decrypt', () => compute(decrypt, (blocks, password))) - .then((decrypted) { - if (!canceled && mounted) Navigator.pop(context, decrypted); - }).onError((error, stackTrace) { - if (mounted) showErrorSnackBar(context, error); - logError(error, stackTrace); - if (!canceled && mounted) Navigator.pop(context); - }); - final result = await showDialog>( - context: context, - builder: (context) => ProgressIndicatorDialog( - title: AppLocalizations.of(context)!.decryptingProgressDialogTitle, - dismissable: true, - ), + + final (succeeded: succeeded, result: result) = await cancelableProgressTask( + context, + task: time('decrypt', () => compute(decrypt, (blocks, password))), + dialogTitle: AppLocalizations.of(context)!.decryptingProgressDialogTitle, ); - if (!mounted) return; - if (result == null) { - // Canceled - canceled = true; - return; - } + if (!succeeded || result == null) return; + OrgTree newDoc = doc; final toRemember = []; for (final (i, cleartext) in result.indexed) { diff --git a/lib/src/pages/document/links.dart b/lib/src/pages/document/links.dart index c8f4035..9b3b351 100644 --- a/lib/src/pages/document/links.dart +++ b/lib/src/pages/document/links.dart @@ -61,39 +61,27 @@ extension LinkHandler on DocumentPageState { final targetId = parseOrgIdUrl(url); final requestId = Object().hashCode.toString(); - var canceled = false; - time( - 'find file with ID', - () => findFileForId( - requestId: requestId, - orgId: targetId, - dirIdentifier: dataSource.rootDirIdentifier!, - ), - ).then((found) { - if (!canceled && mounted) Navigator.pop(context, (found, true)); - }).onError((error, stackTrace) { - if (mounted) showErrorSnackBar(context, error); - logError(error, stackTrace); - if (!canceled && mounted) Navigator.pop(context); - }); - - final result = await showDialog<(NativeDataSource?, bool)>( - context: context, - builder: (context) => ProgressIndicatorDialog( - title: AppLocalizations.of(context)!.searchingProgressDialogTitle, - dismissable: true, + final (succeeded: succeeded, result: foundFile) = + await cancelableProgressTask( + context, + task: time( + 'find file with ID', + () => findFileForId( + requestId: requestId, + orgId: targetId, + dirIdentifier: dataSource.rootDirIdentifier!, + ), ), + dialogTitle: AppLocalizations.of(context)!.searchingProgressDialogTitle, ); - if (result == null) { - canceled = true; + if (!succeeded) { cancelFindFileForId(requestId: requestId); return false; } + if (!mounted) return false; - final (foundFile, searchCompleted) = result; - assert(searchCompleted); if (foundFile == null) { showErrorSnackBar(context, AppLocalizations.of(context)!.errorExternalIdNotFound(targetId));