From d8a1d21f6c8eedfbf8515cba9ea5ed9e803abf80 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 14 Jan 2023 23:47:23 +0100 Subject: [PATCH] handle TransactionTooLargeException when sharing --- .../aves/channel/calls/AppAdapterHandler.kt | 23 ++++--- lib/l10n/app_en.arb | 2 + lib/services/android_app_service.dart | 44 ++++++------ .../collection/entry_set_action_delegate.dart | 69 ++++++++++--------- .../common/action_mixins/entry_editor.dart | 25 +++---- .../common/action_mixins/feedback.dart | 29 ++++---- .../action_mixins/permission_aware.dart | 27 ++------ .../common/action_mixins/size_aware.dart | 7 +- lib/widgets/common/basic/color_list_tile.dart | 5 +- lib/widgets/dialogs/add_shortcut_dialog.dart | 5 +- .../dialogs/aves_confirmation_dialog.dart | 5 +- lib/widgets/dialogs/aves_dialog.dart | 43 ++++++++---- .../dialogs/aves_selection_dialog.dart | 5 +- lib/widgets/dialogs/duration_dialog.dart | 5 +- .../entry_editors/edit_date_dialog.dart | 5 +- .../edit_description_dialog.dart | 5 +- .../entry_editors/edit_location_dialog.dart | 5 +- .../entry_editors/edit_rating_dialog.dart | 5 +- .../entry_editors/remove_metadata_dialog.dart | 5 +- .../entry_editors/rename_entry_dialog.dart | 5 +- lib/widgets/dialogs/export_entry_dialog.dart | 5 +- .../cover_selection_dialog.dart | 5 +- .../filter_editors/create_album_dialog.dart | 5 +- .../filter_editors/rename_album_dialog.dart | 5 +- lib/widgets/dialogs/tile_view_dialog.dart | 5 +- lib/widgets/dialogs/video_speed_dialog.dart | 5 +- .../video_stream_selection_dialog.dart | 5 +- .../dialogs/wallpaper_settings_dialog.dart | 5 +- .../common/action_delegates/album_set.dart | 25 +++---- .../common/action_delegates/chip.dart | 25 +++---- .../common/action_delegates/chip_set.dart | 25 +++---- .../settings/app_export/selection_dialog.dart | 5 +- lib/widgets/settings/display/display.dart | 33 ++++----- .../action/entry_info_action_delegate.dart | 25 +++---- lib/widgets/viewer/video/controller.dart | 28 ++++---- untranslated.json | 49 +++++++++++++ 36 files changed, 272 insertions(+), 307 deletions(-) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index bf68b220b..c044f710f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -10,6 +10,7 @@ import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper +import android.os.TransactionTooLargeException import android.util.Log import androidx.core.content.FileProvider import androidx.core.content.pm.ShortcutInfoCompat @@ -280,7 +281,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { val title = call.argument("title") val urisByMimeType = call.argument>>("urisByMimeType") if (urisByMimeType == null) { - result.error("setAs-args", "missing arguments", null) + result.error("share-args", "missing arguments", null) return } @@ -288,15 +289,14 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { val mimeTypes = urisByMimeType.keys.toTypedArray() // simplify share intent for a single item, as some apps can handle one item but not more - val started = if (uriList.size == 1) { + val intent = if (uriList.size == 1) { val uri = uriList.first() val mimeType = mimeTypes.first() - val intent = Intent(Intent.ACTION_SEND) + Intent(Intent.ACTION_SEND) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setType(mimeType) .putExtra(Intent.EXTRA_STREAM, getShareableUri(context, uri)) - safeStartActivityChooser(title, intent) } else { var mimeType = "*/*" if (mimeTypes.size == 1) { @@ -311,14 +311,21 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { } } - val intent = Intent(Intent.ACTION_SEND_MULTIPLE) + Intent(Intent.ACTION_SEND_MULTIPLE) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList) .setType(mimeType) - safeStartActivityChooser(title, intent) } - - result.success(started) + try { + val started = safeStartActivityChooser(title, intent) + result.success(started) + } catch (e: Exception) { + if (e is TransactionTooLargeException || e.cause is TransactionTooLargeException) { + result.error("share-large", "transaction too large with ${uriList.size} URIs", e) + } else { + result.error("share-exception", "failed to share ${uriList.size} URIs", e) + } + } } private fun safeStartActivity(intent: Intent): Boolean { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 931eb573a..52c9ab346 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -438,6 +438,8 @@ "genericFailureFeedback": "Failed", "genericDangerWarningDialogMessage": "Are you sure?", + "tooManyItemsErrorDialogMessage": "Try again with fewer items.", + "menuActionConfigureView": "View", "menuActionSelect": "Select", "menuActionSelectAll": "Select all", diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index 5152c9ad1..1db5487ad 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -148,32 +148,34 @@ class PlatformAndroidAppService implements AndroidAppService { } @override - Future shareEntries(Iterable entries) async { - // loosen MIME type to a generic one, so we can share with badly defined apps - // e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats - final urisByMimeType = groupBy(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList())); + Future shareEntries(Iterable entries) { + return _share(groupBy( + entries, + // loosen MIME type to a generic one, so we can share with badly defined apps + // e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats + (e) => e.mimeTypeAnySubtype, + ).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()))); + } + + @override + Future shareSingle(String uri, String mimeType) { + return _share({ + mimeType: [uri] + }); + } + + Future _share(Map> urisByMimeType) async { try { final result = await _platform.invokeMethod('share', { 'urisByMimeType': urisByMimeType, }); if (result != null) return result as bool; } on PlatformException catch (e, stack) { - await reportService.recordError(e, stack); - } - return false; - } - - @override - Future shareSingle(String uri, String mimeType) async { - try { - final result = await _platform.invokeMethod('share', { - 'urisByMimeType': { - mimeType: [uri] - }, - }); - if (result != null) return result as bool; - } on PlatformException catch (e, stack) { - await reportService.recordError(e, stack); + if (e.code == 'share-large') { + throw TooManyItemsException(); + } else { + await reportService.recordError(e, stack); + } } return false; } @@ -207,3 +209,5 @@ class PlatformAndroidAppService implements AndroidAppService { } } } + +class TooManyItemsException implements Exception {} diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 72e8a5d06..3198fd9f1 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -17,6 +17,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; @@ -252,11 +253,21 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware return groupedEntries.expand((entry) => entry.burstEntries ?? {entry}).toSet(); } - void _share(BuildContext context) { + Future _share(BuildContext context) async { final entries = _getTargetItems(context); - androidAppService.shareEntries(entries).then((success) { - if (!success) showNoMatchingAppDialog(context); - }); + try { + if (!await androidAppService.shareEntries(entries)) { + await showNoMatchingAppDialog(context); + } + } on TooManyItemsException catch (_) { + await showDialog( + context: context, + builder: (context) => AvesDialog( + content: Text(context.l10n.tooManyItemsErrorDialogMessage), + actions: const [OkButton()], + ), + ); + } } void _rescan(BuildContext context) { @@ -447,25 +458,20 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware if (unsupported.isEmpty) return supported; final unsupportedTypes = unsupported.map((entry) => entry.mimeType).toSet().map(MimeUtils.displayType).toList()..sort(); + final l10n = context.l10n; final confirmed = await showDialog( context: context, - builder: (context) { - final l10n = context.l10n; - return AvesDialog( - content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))), - actions: [ + builder: (context) => AvesDialog( + content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))), + actions: [ + const CancelButton(), + if (supported.isNotEmpty) TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + onPressed: () => Navigator.pop(context, true), + child: Text(l10n.continueButtonLabel), ), - if (supported.isNotEmpty) - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(l10n.continueButtonLabel), - ), - ], - ); - }, + ], + ), ); if (confirmed == null || !confirmed) return null; @@ -536,21 +542,16 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware Future removeLocation(BuildContext context, Set entries) async { final confirmed = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.genericDangerWarningDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.applyButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.genericDangerWarningDialogMessage), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return; diff --git a/lib/widgets/common/action_mixins/entry_editor.dart b/lib/widgets/common/action_mixins/entry_editor.dart index 1f7efb28b..49c411271 100644 --- a/lib/widgets/common/action_mixins/entry_editor.dart +++ b/lib/widgets/common/action_mixins/entry_editor.dart @@ -123,21 +123,16 @@ mixin EntryEditorMixin { if (entries.any((entry) => entry.isMotionPhoto) && types.contains(MetadataType.xmp)) { final confirmed = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.removeEntryMetadataMotionPhotoXmpWarningDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.applyButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.removeEntryMetadataMotionPhotoXmpWarningDialogMessage), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return null; } diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index 2ccd754c4..06352fa9d 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -136,21 +136,20 @@ mixin FeedbackMixin { int? itemCount, VoidCallback? onCancel, void Function(Set processed)? onDone, - }) { - return showDialog( - context: context, - barrierDismissible: false, - builder: (context) => ReportOverlay( - opStream: opStream, - itemCount: itemCount, - onCancel: onCancel, - onDone: (processed) { - Navigator.pop(context); - onDone?.call(processed); - }, - ), - ); - } + }) => + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => ReportOverlay( + opStream: opStream, + itemCount: itemCount, + onCancel: onCancel, + onDone: (processed) { + Navigator.pop(context); + onDone?.call(processed); + }, + ), + ); } class ReportOverlay extends StatefulWidget { diff --git a/lib/widgets/common/action_mixins/permission_aware.dart b/lib/widgets/common/action_mixins/permission_aware.dart index 61a8a918b..b54f2411b 100644 --- a/lib/widgets/common/action_mixins/permission_aware.dart +++ b/lib/widgets/common/action_mixins/permission_aware.dart @@ -54,10 +54,7 @@ mixin PermissionAwareMixin { return AvesDialog( content: Text(l10n.storageAccessDialogMessage(directory, volume)), actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () => Navigator.pop(context, true), child: Text(MaterialLocalizations.of(context).okButtonLabel), @@ -72,17 +69,10 @@ mixin PermissionAwareMixin { if (!await deviceService.isSystemFilePickerEnabled()) { await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.missingSystemFilePickerDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).okButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.missingSystemFilePickerDialogMessage), + actions: const [OkButton()], + ), ); return false; } @@ -103,12 +93,7 @@ mixin PermissionAwareMixin { final volume = dir.getVolumeDescription(context); return AvesDialog( content: Text(context.l10n.restrictedAccessDialogMessage(directory, volume)), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).okButtonLabel), - ), - ], + actions: const [OkButton()], ); }, ); diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart index c6fb9c59a..5e57bb021 100644 --- a/lib/widgets/common/action_mixins/size_aware.dart +++ b/lib/widgets/common/action_mixins/size_aware.dart @@ -85,12 +85,7 @@ mixin SizeAwareMixin { final volume = destinationVolume.getDescription(context); return AvesDialog( content: Text(l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).okButtonLabel), - ), - ], + actions: const [OkButton()], ); }, ); diff --git a/lib/widgets/common/basic/color_list_tile.dart b/lib/widgets/common/basic/color_list_tile.dart index bcda737ec..ae44a47fb 100644 --- a/lib/widgets/common/basic/color_list_tile.dart +++ b/lib/widgets/common/basic/color_list_tile.dart @@ -94,10 +94,7 @@ class _ColorPickerDialogState extends State { ) ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () => Navigator.pop(context, color), child: Text(context.l10n.applyButtonLabel), diff --git a/lib/widgets/dialogs/add_shortcut_dialog.dart b/lib/widgets/dialogs/add_shortcut_dialog.dart index 7cd2a0c69..1d0e8d081 100644 --- a/lib/widgets/dialogs/add_shortcut_dialog.dart +++ b/lib/widgets/dialogs/add_shortcut_dialog.dart @@ -87,10 +87,7 @@ class _AddShortcutDialogState extends State { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/aves_confirmation_dialog.dart b/lib/widgets/dialogs/aves_confirmation_dialog.dart index 8fcedc0b6..621627172 100644 --- a/lib/widgets/dialogs/aves_confirmation_dialog.dart +++ b/lib/widgets/dialogs/aves_confirmation_dialog.dart @@ -110,10 +110,7 @@ class _AvesConfirmationDialogState extends State<_AvesConfirmationDialog> { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () { if (_skip.value) { diff --git a/lib/widgets/dialogs/aves_dialog.dart b/lib/widgets/dialogs/aves_dialog.dart index 411c700a0..2cf3c01e9 100644 --- a/lib/widgets/dialogs/aves_dialog.dart +++ b/lib/widgets/dialogs/aves_dialog.dart @@ -152,19 +152,34 @@ class DialogTitle extends StatelessWidget { } } -void showNoMatchingAppDialog(BuildContext context) { - showDialog( - context: context, - builder: (context) { - return AvesDialog( +Future showNoMatchingAppDialog(BuildContext context) => showDialog( + context: context, + builder: (context) => AvesDialog( content: Text(context.l10n.noMatchingAppDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).okButtonLabel), - ), - ], - ); - }, - ); + actions: const [OkButton()], + ), + ); + +class CancelButton extends StatelessWidget { + const CancelButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + ); + } +} + +class OkButton extends StatelessWidget { + const OkButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).okButtonLabel), + ); + } } diff --git a/lib/widgets/dialogs/aves_selection_dialog.dart b/lib/widgets/dialogs/aves_selection_dialog.dart index 15dce1fe0..95352649d 100644 --- a/lib/widgets/dialogs/aves_selection_dialog.dart +++ b/lib/widgets/dialogs/aves_selection_dialog.dart @@ -86,10 +86,7 @@ class _AvesSelectionDialogState extends State> { if (verticalPadding != 0) SizedBox(height: verticalPadding), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), if (needConfirmation) TextButton( onPressed: () => Navigator.pop(context, _selectedValue), diff --git a/lib/widgets/dialogs/duration_dialog.dart b/lib/widgets/dialogs/duration_dialog.dart index 48ae0ad4c..91236ce0a 100644 --- a/lib/widgets/dialogs/duration_dialog.dart +++ b/lib/widgets/dialogs/duration_dialog.dart @@ -88,10 +88,7 @@ class _DurationDialogState extends State { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), AnimatedBuilder( animation: Listenable.merge([_minutes, _seconds]), builder: (context, child) { diff --git a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart index 21bb629df..bc531ee54 100644 --- a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart @@ -112,10 +112,7 @@ class _EditEntryDateDialogState extends State { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () => _submit(context), child: Text(l10n.applyButtonLabel), diff --git a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart index 7e9c67473..a080cc870 100644 --- a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart @@ -44,10 +44,7 @@ class _EditEntryTitleDescriptionDialogState extends State Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: fields.isEmpty ? null : () => _submit(context), child: Text(context.l10n.applyButtonLabel), diff --git a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart index 12c80eba5..31223fd29 100644 --- a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart @@ -126,10 +126,7 @@ class _EditEntryLocationDialogState extends State { const SizedBox(height: 8), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart index 2e07e0751..d7b8399a3 100644 --- a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart @@ -99,10 +99,7 @@ class _EditEntryRatingDialogState extends State { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: isValid ? () => _submit(context) : null, child: Text(l10n.applyButtonLabel), diff --git a/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart b/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart index 18c2a1e83..a2f212008 100644 --- a/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart @@ -80,10 +80,7 @@ class _RemoveEntryMetadataDialogState extends State { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart b/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart index b68aa54d2..e99be396d 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_dialog.dart @@ -58,10 +58,7 @@ class _RenameEntryDialogState extends State { onSubmitted: (_) => _submit(context), ), actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/export_entry_dialog.dart b/lib/widgets/dialogs/export_entry_dialog.dart index 5b59c90d2..e0e51f3c5 100644 --- a/lib/widgets/dialogs/export_entry_dialog.dart +++ b/lib/widgets/dialogs/export_entry_dialog.dart @@ -114,10 +114,7 @@ class _ExportEntryDialogState extends State { const SizedBox(height: 16), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart index d2bd5b693..3bcf76bc7 100644 --- a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart @@ -155,10 +155,7 @@ class _CoverSelectionDialogState extends State { spacing: AvesDialog.buttonPadding.horizontal / 2, overflowAlignment: OverflowBarAlignment.end, children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () { final entry = _isCustomEntry ? _customEntry : null; diff --git a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart index 5bf025ba2..f0839e8fe 100644 --- a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart @@ -90,10 +90,7 @@ class _CreateAlbumDialogState extends State { ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart b/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart index f270af325..991df0d64 100644 --- a/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart @@ -57,10 +57,7 @@ class _RenameAlbumDialogState extends State { ); }), actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { diff --git a/lib/widgets/dialogs/tile_view_dialog.dart b/lib/widgets/dialogs/tile_view_dialog.dart index 9e0386225..7f5272c0c 100644 --- a/lib/widgets/dialogs/tile_view_dialog.dart +++ b/lib/widgets/dialogs/tile_view_dialog.dart @@ -145,10 +145,7 @@ class _TileViewDialogState extends State> with ), ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( key: const Key('button-apply'), onPressed: () { diff --git a/lib/widgets/dialogs/video_speed_dialog.dart b/lib/widgets/dialogs/video_speed_dialog.dart index 4b44b8cb9..f835e1026 100644 --- a/lib/widgets/dialogs/video_speed_dialog.dart +++ b/lib/widgets/dialogs/video_speed_dialog.dart @@ -57,10 +57,7 @@ class _VideoSpeedDialogState extends State { ], ), actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () => _submit(context), child: Text(context.l10n.applyButtonLabel), diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index b89950b49..eef7d88d9 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -80,10 +80,7 @@ class _VideoStreamSelectionDialogState extends State ] : null, actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), if (canSelect) TextButton( onPressed: () => _submit(context), diff --git a/lib/widgets/dialogs/wallpaper_settings_dialog.dart b/lib/widgets/dialogs/wallpaper_settings_dialog.dart index ba8d6ce73..5dc85114d 100644 --- a/lib/widgets/dialogs/wallpaper_settings_dialog.dart +++ b/lib/widgets/dialogs/wallpaper_settings_dialog.dart @@ -39,10 +39,7 @@ class _WallpaperSettingsDialogState extends State { ) ], actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: () => Navigator.pop(context, Tuple2(_selectedTarget, _useScrollEffect)), child: Text(context.l10n.applyButtonLabel), diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index a14b93833..843a5eda3 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -234,21 +234,16 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with final confirmed = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(filters.length == 1 ? l10n.deleteSingleAlbumConfirmationDialogMessage(todoCount) : l10n.deleteMultiAlbumConfirmationDialogMessage(todoCount)), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(l10n.deleteButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(filters.length == 1 ? l10n.deleteSingleAlbumConfirmationDialogMessage(todoCount) : l10n.deleteMultiAlbumConfirmationDialogMessage(todoCount)), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(l10n.deleteButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index 1d75b73cd..988e30792 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -53,21 +53,16 @@ class ChipActionDelegate { Future _hide(BuildContext context, CollectionFilter filter) async { final confirmed = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.hideFilterConfirmationDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.hideButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.hideFilterConfirmationDialogMessage), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.hideButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index cf7335043..310d2b382 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -304,21 +304,16 @@ abstract class ChipSetActionDelegate with FeedbackMi Future _hide(BuildContext context, Set filters) async { final confirmed = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.hideFilterConfirmationDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.hideButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.hideFilterConfirmationDialogMessage), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.hideButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return; diff --git a/lib/widgets/settings/app_export/selection_dialog.dart b/lib/widgets/settings/app_export/selection_dialog.dart index 986b83603..167dfbca7 100644 --- a/lib/widgets/settings/app_export/selection_dialog.dart +++ b/lib/widgets/settings/app_export/selection_dialog.dart @@ -54,10 +54,7 @@ class _AppExportItemSelectionDialogState extends State Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), + const CancelButton(), TextButton( onPressed: _selectedItems.isEmpty ? null : () => Navigator.pop(context, _selectedItems), child: Text(context.l10n.applyButtonLabel), diff --git a/lib/widgets/settings/display/display.dart b/lib/widgets/settings/display/display.dart index bda058a5f..222319665 100644 --- a/lib/widgets/settings/display/display.dart +++ b/lib/widgets/settings/display/display.dart @@ -114,27 +114,22 @@ class SettingsTileDisplayForceTvLayout extends SettingsTile { selector: (context, s) => s.forceTvLayout, onChanged: (v) async { if (v) { + final l10n = context.l10n; final confirmed = await showDialog( context: context, - builder: (context) { - final l10n = context.l10n; - return AvesDialog( - content: Text([ - l10n.settingsModificationWarningDialogMessage, - l10n.genericDangerWarningDialogMessage, - ].join('\n\n')), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(l10n.applyButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text([ + l10n.settingsModificationWarningDialogMessage, + l10n.genericDangerWarningDialogMessage, + ].join('\n\n')), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(l10n.applyButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return; } diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index 42d865f9f..48ed09938 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -229,21 +229,16 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi Future _convertMotionPhotoToStillImage(BuildContext context, AvesEntry targetEntry) async { final confirmed = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.genericDangerWarningDialogMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.applyButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.genericDangerWarningDialogMessage), + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ), ); if (confirmed == null || !confirmed) return; diff --git a/lib/widgets/viewer/video/controller.dart b/lib/widgets/viewer/video/controller.dart index 9c9ee8032..0e0f9f452 100644 --- a/lib/widgets/viewer/video/controller.dart +++ b/lib/widgets/viewer/video/controller.dart @@ -66,21 +66,19 @@ abstract class AvesVideoController { final resume = await showDialog( context: context, - builder: (context) { - return AvesDialog( - content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(context.l10n.videoStartOverButtonLabel), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(context.l10n.videoResumeButtonLabel), - ), - ], - ); - }, + builder: (context) => AvesDialog( + content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.videoStartOverButtonLabel), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.l10n.videoResumeButtonLabel), + ), + ], + ), ); if (resume == null || !resume) return 0; return resumeTime; diff --git a/untranslated.json b/untranslated.json index 2d7603fb3..57a3ff5c8 100644 --- a/untranslated.json +++ b/untranslated.json @@ -226,6 +226,7 @@ "genericSuccessFeedback", "genericFailureFeedback", "genericDangerWarningDialogMessage", + "tooManyItemsErrorDialogMessage", "menuActionConfigureView", "menuActionSelect", "menuActionSelectAll", @@ -572,6 +573,7 @@ "cs": [ "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsDisplayUseTvInterface" @@ -581,6 +583,7 @@ "columnCount", "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives", @@ -590,11 +593,16 @@ "el": [ "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsDisplayUseTvInterface" ], + "es": [ + "tooManyItemsErrorDialogMessage" + ], + "fa": [ "clearTooltip", "videoActionPause", @@ -708,6 +716,7 @@ "genericSuccessFeedback", "genericFailureFeedback", "genericDangerWarningDialogMessage", + "tooManyItemsErrorDialogMessage", "menuActionConfigureView", "menuActionSelect", "menuActionSelectAll", @@ -1035,6 +1044,10 @@ "filePickerUseThisFolder" ], + "fr": [ + "tooManyItemsErrorDialogMessage" + ], + "gl": [ "columnCount", "entryActionShareImageOnly", @@ -1151,6 +1164,7 @@ "genericSuccessFeedback", "genericFailureFeedback", "genericDangerWarningDialogMessage", + "tooManyItemsErrorDialogMessage", "menuActionConfigureView", "menuActionSelect", "menuActionSelectAll", @@ -1507,9 +1521,14 @@ "filePickerUseThisFolder" ], + "id": [ + "tooManyItemsErrorDialogMessage" + ], + "it": [ "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsDisplayUseTvInterface" @@ -1526,6 +1545,7 @@ "keepScreenOnVideoPlayback", "subtitlePositionTop", "subtitlePositionBottom", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives", @@ -1533,11 +1553,16 @@ "settingsWidgetDisplayedItem" ], + "ko": [ + "tooManyItemsErrorDialogMessage" + ], + "lt": [ "columnCount", "filterLocatedLabel", "filterTaggedLabel", "keepScreenOnVideoPlayback", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives", @@ -1552,6 +1577,7 @@ "filterLocatedLabel", "filterTaggedLabel", "keepScreenOnVideoPlayback", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives", @@ -1574,6 +1600,7 @@ "subtitlePositionBottom", "widgetDisplayedItemRandom", "widgetDisplayedItemMostRecent", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowRatingTags", "settingsViewerShowDescription", @@ -1603,6 +1630,7 @@ "editEntryLocationDialogSetCustom", "locationPickerUseThisLocationButton", "removeEntryMetadataMotionPhotoXmpWarningDialogMessage", + "tooManyItemsErrorDialogMessage", "viewDialogSortSectionTitle", "viewDialogReverseSortOrder", "aboutLinkPolicy", @@ -1870,20 +1898,30 @@ "wallpaperUseScrollEffect" ], + "pl": [ + "tooManyItemsErrorDialogMessage" + ], + "pt": [ "columnCount", "filterLocatedLabel", "filterTaggedLabel", "widgetDisplayedItemMostRecent", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowRatingTags", "settingsAccessibilityShowPinchGestureAlternatives", "settingsDisplayUseTvInterface" ], + "ro": [ + "tooManyItemsErrorDialogMessage" + ], + "ru": [ "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsDisplayUseTvInterface" ], @@ -1922,6 +1960,7 @@ "editEntryDateDialogExtractFromTitle", "editEntryDateDialogShift", "removeEntryMetadataDialogTitle", + "tooManyItemsErrorDialogMessage", "collectionActionShowTitleSearch", "collectionActionHideTitleSearch", "collectionActionAddShortcut", @@ -2235,9 +2274,18 @@ "filePickerUseThisFolder" ], + "tr": [ + "tooManyItemsErrorDialogMessage" + ], + + "uk": [ + "tooManyItemsErrorDialogMessage" + ], + "zh": [ "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives", @@ -2248,6 +2296,7 @@ "columnCount", "filterLocatedLabel", "filterTaggedLabel", + "tooManyItemsErrorDialogMessage", "settingsModificationWarningDialogMessage", "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives",