diff --git a/lib/model/entry_dirs.dart b/lib/model/entry_dirs.dart index 73c206a6b..c500504a3 100644 --- a/lib/model/entry_dirs.dart +++ b/lib/model/entry_dirs.dart @@ -62,8 +62,12 @@ class EntryDir { final dir = Directory(resolved); if (dir.existsSync()) { final partLower = part.toLowerCase(); - final childrenDirs = dir.listSync().where((v) => v.absolute is Directory).toSet(); - found = childrenDirs.firstWhereOrNull((v) => pContext.basename(v.path).toLowerCase() == partLower); + try { + final childrenDirs = dir.listSync().where((v) => v.absolute is Directory).toSet(); + found = childrenDirs.firstWhereOrNull((v) => pContext.basename(v.path).toLowerCase() == partLower); + } catch (error) { + // ignore, could be IO issue when listing directory + } } resolved = found?.path ?? '$resolved${pContext.separator}$part'; } diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_metadata_edition.dart index cd592f02b..6a0eb8ff3 100644 --- a/lib/model/entry_metadata_edition.dart +++ b/lib/model/entry_metadata_edition.dart @@ -25,7 +25,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry { final appliedModifier = await _applyDateModifierToEntry(userModifier); if (appliedModifier == null) { - if (!isMissingAtPath) { + if (!isMissingAtPath && userModifier.action != DateEditAction.copyField) { await reportService.recordError('failed to get date for modifier=$userModifier, entry=$this', null); } return {}; diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 89d258ce6..e89a34383 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -255,40 +255,40 @@ class Settings extends ChangeNotifier { } } - applyTvSettings(); + if (settings.useTvLayout) { + applyTvSettings(); + } } void applyTvSettings() { - if (settings.useTvLayout) { - themeBrightness = AvesThemeBrightness.dark; - mustBackTwiceToExit = false; - // address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality - keepScreenOn = KeepScreenOn.videoPlayback; - enableBottomNavigationBar = false; - drawerTypeBookmarks = [ - null, - MimeFilter.video, - FavouriteFilter.instance, - ]; - drawerPageBookmarks = [ - AlbumListPage.routeName, - CountryListPage.routeName, - PlaceListPage.routeName, - TagListPage.routeName, - SearchPage.routeName, - ]; - showOverlayOnOpening = false; - showOverlayMinimap = false; - showOverlayThumbnailPreview = false; - viewerGestureSideTapNext = false; - viewerUseCutout = true; - viewerMaxBrightness = false; - videoControls = VideoControls.none; - videoGestureDoubleTapTogglePlay = false; - videoGestureSideDoubleTapSeek = false; - enableBin = false; - showPinchGestureAlternatives = true; - } + themeBrightness = AvesThemeBrightness.dark; + mustBackTwiceToExit = false; + // address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality + keepScreenOn = KeepScreenOn.videoPlayback; + enableBottomNavigationBar = false; + drawerTypeBookmarks = [ + null, + MimeFilter.video, + FavouriteFilter.instance, + ]; + drawerPageBookmarks = [ + AlbumListPage.routeName, + CountryListPage.routeName, + PlaceListPage.routeName, + TagListPage.routeName, + SearchPage.routeName, + ]; + showOverlayOnOpening = false; + showOverlayMinimap = false; + showOverlayThumbnailPreview = false; + viewerGestureSideTapNext = false; + viewerUseCutout = true; + viewerMaxBrightness = false; + videoControls = VideoControls.none; + videoGestureDoubleTapTogglePlay = false; + videoGestureSideDoubleTapSeek = false; + enableBin = false; + showPinchGestureAlternatives = true; } Future sanitize() async { @@ -884,7 +884,7 @@ class Settings extends ChangeNotifier { bool? getBool(String key) { try { return settingsStore.getBool(key); - } catch (e) { + } catch (error) { // ignore, could be obsolete value of different type return null; } @@ -893,7 +893,7 @@ class Settings extends ChangeNotifier { int? getInt(String key) { try { return settingsStore.getInt(key); - } catch (e) { + } catch (error) { // ignore, could be obsolete value of different type return null; } @@ -902,7 +902,7 @@ class Settings extends ChangeNotifier { double? getDouble(String key) { try { return settingsStore.getDouble(key); - } catch (e) { + } catch (error) { // ignore, could be obsolete value of different type return null; } @@ -911,7 +911,7 @@ class Settings extends ChangeNotifier { String? getString(String key) { try { return settingsStore.getString(key); - } catch (e) { + } catch (error) { // ignore, could be obsolete value of different type return null; } @@ -920,7 +920,7 @@ class Settings extends ChangeNotifier { List? getStringList(String key) { try { return settingsStore.getStringList(key); - } catch (e) { + } catch (error) { // ignore, could be obsolete value of different type return null; } @@ -934,7 +934,7 @@ class Settings extends ChangeNotifier { return v; } } - } catch (e) { + } catch (error) { // ignore, could be obsolete value of different type } return defaultValue; diff --git a/lib/model/vaults/vaults.dart b/lib/model/vaults/vaults.dart index 113ad7515..015a08ac2 100644 --- a/lib/model/vaults/vaults.dart +++ b/lib/model/vaults/vaults.dart @@ -154,7 +154,10 @@ class Vaults extends ChangeNotifier { localizedReason: context.l10n.authenticateToUnlockVault, ); } on PlatformException catch (e, stack) { - await reportService.recordError(e, stack); + if (e.code != 'auth_in_progress') { + // `auth_in_progress`: `Authentication in progress` + await reportService.recordError(e, stack); + } } break; case VaultLockType.pin: diff --git a/lib/services/common/optional_event_channel.dart b/lib/services/common/optional_event_channel.dart index 7ce14fe5a..e6a2495a8 100644 --- a/lib/services/common/optional_event_channel.dart +++ b/lib/services/common/optional_event_channel.dart @@ -27,9 +27,9 @@ class OptionalEventChannel extends EventChannel { }); try { await methodChannel.invokeMethod('listen', arguments); - } catch (exception, stack) { + } catch (error, stack) { FlutterError.reportError(FlutterErrorDetails( - exception: exception, + exception: error, stack: stack, library: 'services library', context: ErrorDescription('while activating platform stream on channel $name'), @@ -39,9 +39,9 @@ class OptionalEventChannel extends EventChannel { binaryMessenger.setMessageHandler(name, null); try { await methodChannel.invokeMethod('cancel', arguments); - } catch (exception, stack) { + } catch (error, stack) { FlutterError.reportError(FlutterErrorDetails( - exception: exception, + exception: error, stack: stack, library: 'services library', context: ErrorDescription('while de-activating platform stream on channel $name'), diff --git a/lib/utils/time_utils.dart b/lib/utils/time_utils.dart index e1b398ee2..d3d2d08a3 100644 --- a/lib/utils/time_utils.dart +++ b/lib/utils/time_utils.dart @@ -36,7 +36,7 @@ DateTime? dateTimeFromMillis(int? millis, {bool isUtc = false}) { if (millis == null || millis == 0) return null; try { return DateTime.fromMillisecondsSinceEpoch(millis, isUtc: isUtc); - } catch (e) { + } catch (error) { // `DateTime`s can represent time values that are at a distance of at most 100,000,000 // days from epoch (1970-01-01 UTC): -271821-04-20 to 275760-09-13. debugPrint('failed to build DateTime from timestamp in millis=$millis'); diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart index 83a959659..3506dad8c 100644 --- a/lib/widgets/about/bug_report.dart +++ b/lib/widgets/about/bug_report.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:ui'; import 'package:aves/app_flavor.dart'; import 'package:aves/flutter_version.dart'; @@ -141,6 +142,7 @@ class _BugReportState extends State with FeedbackMixin { } Future _getInfo(BuildContext context) async { + final accessibility = window.accessibilityFeatures; final packageInfo = await PackageInfo.fromPlatform(); final androidInfo = await DeviceInfoPlugin().androidInfo; final flavor = context.read().toString().split('.')[1]; @@ -159,6 +161,7 @@ class _BugReportState extends State with FeedbackMixin { 'System locales: ${WidgetsBinding.instance.window.locales.join(', ')}', 'Aves locale: ${settings.locale ?? 'system'} -> ${settings.appliedLocale}', 'Installer: ${packageInfo.installerStore}', + 'Accessibility: accessibleNavigation=${accessibility.accessibleNavigation}, disableAnimations=${accessibility.disableAnimations}', ].join('\n'); } diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index 0fe88c8da..2ba8fd1c0 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -26,7 +26,7 @@ mixin FeedbackMixin { ScaffoldMessengerState? scaffoldMessenger; try { scaffoldMessenger = ScaffoldMessenger.of(context); - } catch (e) { + } catch (error) { // minor issue: the page triggering this feedback likely // allows the user to navigate away and they did so debugPrint('failed to find ScaffoldMessenger in context'); diff --git a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart index 850ff57df..1b81adf64 100644 --- a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart @@ -289,7 +289,7 @@ class _EditEntryLocationDialogState extends State { double? tryParse(String text) { try { return double.tryParse(text) ?? (coordinateFormatter.parse(text).toDouble()); - } catch (e) { + } catch (error) { // ignore return null; } diff --git a/lib/widgets/settings/display/display.dart b/lib/widgets/settings/display/display.dart index 851eaa134..959249993 100644 --- a/lib/widgets/settings/display/display.dart +++ b/lib/widgets/settings/display/display.dart @@ -11,6 +11,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; import 'package:aves/widgets/settings/common/tiles.dart'; +import 'package:aves/widgets/settings/privacy/privacy.dart'; import 'package:aves/widgets/settings/settings_definition.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -134,6 +135,8 @@ class SettingsTileDisplayForceTvLayout extends SettingsTile { if (confirmed == null || !confirmed) return; } + if (v && !(await SettingsTilePrivacyEnableBin.setBinUsage(context, false))) return; + settings.forceTvLayout = v; }, title: title(context), diff --git a/lib/widgets/settings/privacy/privacy.dart b/lib/widgets/settings/privacy/privacy.dart index 38088cd6d..6fc24936e 100644 --- a/lib/widgets/settings/privacy/privacy.dart +++ b/lib/widgets/settings/privacy/privacy.dart @@ -97,49 +97,51 @@ class SettingsTilePrivacyEnableBin extends SettingsTile { @override Widget build(BuildContext context) => SettingsSwitchListTile( selector: (context, s) => s.enableBin, - onChanged: (v) async { - final l10n = context.l10n; - if (!v) { - if (vaults.all.any((v) => v.useBin)) { - await showDialog( - context: context, - builder: (context) => AvesDialog( - content: Text(l10n.vaultBinUsageDialogMessage), - actions: const [OkButton()], - ), - ); - return; - } - - final source = context.read(); - final trashedEntries = source.trashedEntries; - if (trashedEntries.isNotEmpty) { - if (!await showConfirmationDialog( - context: context, - message: l10n.settingsDisablingBinWarningDialogMessage, - confirmationButtonLabel: l10n.applyButtonLabel, - )) return; - - // delete forever trashed items - await EntrySetActionDelegate().doDelete( - context: context, - entries: trashedEntries, - enableBin: false, - ); - - // in case of failure or cancellation - if (source.trashedEntries.isNotEmpty) return; - } - } - - settings.enableBin = v; - if (!v) { - settings.searchHistory = []; - } - }, + onChanged: (v) => setBinUsage(context, v), title: title(context), subtitle: context.l10n.settingsEnableBinSubtitle, ); + + static Future setBinUsage(BuildContext context, bool enabled) async { + final l10n = context.l10n; + if (!enabled) { + if (vaults.all.any((v) => v.useBin)) { + await showDialog( + context: context, + builder: (context) => AvesDialog( + content: Text(l10n.vaultBinUsageDialogMessage), + actions: const [OkButton()], + ), + ); + return false; + } + + final source = context.read(); + final trashedEntries = source.trashedEntries; + if (trashedEntries.isNotEmpty) { + if (!await showConfirmationDialog( + context: context, + message: l10n.settingsDisablingBinWarningDialogMessage, + confirmationButtonLabel: l10n.applyButtonLabel, + )) return false; + + // delete forever trashed items + await EntrySetActionDelegate().doDelete( + context: context, + entries: trashedEntries, + enableBin: false, + ); + + // in case of failure or cancellation + if (source.trashedEntries.isNotEmpty) return false; + } + + settings.searchHistory = []; + } + + settings.enableBin = enabled; + return true; + } } class SettingsTilePrivacyHiddenItems extends SettingsTile { diff --git a/lib/widgets/viewer/action/single_entry_editor.dart b/lib/widgets/viewer/action/single_entry_editor.dart index 96565002b..3bdf65348 100644 --- a/lib/widgets/viewer/action/single_entry_editor.dart +++ b/lib/widgets/viewer/action/single_entry_editor.dart @@ -55,8 +55,8 @@ mixin SingleEntryEditorMixin on FeedbackMixin, PermissionAwareMixin { } else { showFeedback(context, l10n.genericFailureFeedback); } - } catch (e, stack) { - await reportService.recordError(e, stack); + } catch (error, stack) { + await reportService.recordError(error, stack); } source?.resumeMonitoring(); } diff --git a/lib/widgets/viewer/debug/debug_page.dart b/lib/widgets/viewer/debug/debug_page.dart index 42dce44c1..28b22af83 100644 --- a/lib/widgets/viewer/debug/debug_page.dart +++ b/lib/widgets/viewer/debug/debug_page.dart @@ -55,7 +55,7 @@ class ViewerDebugPage extends StatelessWidget { if (time != null && time > 0) { try { value += ' (${DateTime.fromMillisecondsSinceEpoch(time * factor)})'; - } catch (e) { + } catch (error) { value += ' (invalid DateTime})'; } } diff --git a/lib/widgets/viewer/debug/metadata.dart b/lib/widgets/viewer/debug/metadata.dart index 831e0c3a4..0d6e47327 100644 --- a/lib/widgets/viewer/debug/metadata.dart +++ b/lib/widgets/viewer/debug/metadata.dart @@ -62,7 +62,7 @@ class _MetadataTabState extends State { } try { value += ' (${DateTime.fromMillisecondsSinceEpoch(v)})'; - } catch (e) { + } catch (error) { value += ' (invalid DateTime})'; } } diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index 917a3b66d..2959174b0 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -39,11 +39,11 @@ class _WelcomePageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) => _initWelcomeSettings()); } - // explicitly set consent values to current defaults - // so they are not subject to future default changes void _initWelcomeSettings() { // this should be done outside of `initState`/`build` settings.setContextualDefaults(context.read()); + // explicitly set consent values to current defaults + // so they are not subject to future default changes settings.isInstalledAppAccessAllowed = SettingsDefaults.isInstalledAppAccessAllowed; settings.isErrorReportingAllowed = SettingsDefaults.isErrorReportingAllowed; }