diff --git a/CHANGELOG.md b/CHANGELOG.md index 72005807b..08e2f89df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- check Media Store changes when resuming app +- disabling animations also applies to pop up menus + ## [v1.10.5] - 2024-02-22 ### Added diff --git a/lib/model/settings/enums/accessibility_animations.dart b/lib/model/settings/enums/accessibility_animations.dart index a336c2601..25f9e0fc0 100644 --- a/lib/model/settings/enums/accessibility_animations.dart +++ b/lib/model/settings/enums/accessibility_animations.dart @@ -1,5 +1,7 @@ import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/durations.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:flutter/widgets.dart'; extension ExtraAccessibilityAnimations on AccessibilityAnimations { bool get animate { @@ -14,4 +16,17 @@ extension ExtraAccessibilityAnimations on AccessibilityAnimations { return true; } } + + Duration get popUpAnimationDuration => animate ? ADurations.popupMenuAnimation : Duration.zero; + + Duration get popUpAnimationDelay => popUpAnimationDuration + const Duration(milliseconds: ADurations.transitionMarginMillis); + + AnimationStyle get popUpAnimationStyle { + return animate + ? AnimationStyle( + curve: Curves.easeInOutCubic, + duration: popUpAnimationDuration, + ) + : AnimationStyle.noAnimation; + } } diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index 486c51874..56e852d78 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -2,12 +2,13 @@ import 'package:flutter/foundation.dart'; class ADurations { // Flutter animations (with margin) - static const popupMenuAnimation = Duration(milliseconds: 300 + 20); // ref `_kMenuDuration` used in `_PopupMenuRoute` + static const transitionMarginMillis = 20; + // page transition duration also available via `ModalRoute.of(context)!.transitionDuration * timeDilation` - static const pageTransitionAnimation = Duration(milliseconds: 300 + 20); // ref `transitionDuration` used in `MaterialRouteTransitionMixin` - static const dialogTransitionAnimation = Duration(milliseconds: 150 + 20); // ref `transitionDuration` used in `DialogRoute` - static const drawerTransitionAnimation = Duration(milliseconds: 246 + 20); // ref `_kBaseSettleDuration` used in `DrawerControllerState` - static const toggleableTransitionAnimation = Duration(milliseconds: 200 + 20); // ref `_kToggleDuration` used in `ToggleableStateMixin` + static const pageTransitionAnimation = Duration(milliseconds: 300 + transitionMarginMillis); // ref `transitionDuration` used in `MaterialRouteTransitionMixin` + static const dialogTransitionAnimation = Duration(milliseconds: 150 + transitionMarginMillis); // ref `transitionDuration` used in `DialogRoute` + static const drawerTransitionAnimation = Duration(milliseconds: 246 + transitionMarginMillis); // ref `_kBaseSettleDuration` used in `DrawerControllerState` + static const toggleableTransitionAnimation = Duration(milliseconds: 200 + transitionMarginMillis); // ref `_kToggleDuration` used in `ToggleableStateMixin` // common animations static const sweeperOpacityAnimation = Duration(milliseconds: 150); @@ -16,6 +17,7 @@ class ADurations { static const appBarTitleAnimation = Duration(milliseconds: 300); static const appBarActionChangeAnimation = Duration(milliseconds: 200); + static const popupMenuAnimation = Duration(milliseconds: 300); // filter grids animations static const chipDecorationAnimation = Duration(milliseconds: 200); diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index abe5553f9..2fb992ffe 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -7,6 +7,7 @@ import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; @@ -382,6 +383,7 @@ class _CollectionAppBarState extends State with SingleTickerPr (action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection), ); + final animations = context.select((s) => s.accessibilityAnimations); return [ ...quickActionButtons, PopupMenuButton( @@ -432,9 +434,10 @@ class _CollectionAppBarState extends State with SingleTickerPr }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); await _onActionSelected(action); }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ]; } diff --git a/lib/widgets/common/basic/popup/menu_button.dart b/lib/widgets/common/basic/popup/menu_button.dart index 36d18607f..6e69368fd 100644 --- a/lib/widgets/common/basic/popup/menu_button.dart +++ b/lib/widgets/common/basic/popup/menu_button.dart @@ -21,6 +21,7 @@ class AvesPopupMenuButton extends PopupMenuButton { super.enableFeedback, super.iconSize, this.onMenuOpened, + super.popUpAnimationStyle, }); @override diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 59ddfb083..e8d230fce 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -11,7 +11,6 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/collection/filter_bar.dart'; @@ -117,6 +116,7 @@ class AvesFilterChip extends StatefulWidget { final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension); final actionDelegate = ChipActionDelegate(); + final animations = context.read().accessibilityAnimations; final selectedAction = await showMenu( context: context, position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size), @@ -149,10 +149,11 @@ class AvesFilterChip extends StatefulWidget { ); }), ], + popUpAnimationStyle: animations.popUpAnimationStyle, ); if (selectedAction != null) { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); actionDelegate.onActionSelected(context, filter, selectedAction); } } diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index b625314ce..9b8296419 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -4,9 +4,9 @@ import 'package:aves/model/favourites.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/tag.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/basic/font_size_icon_theme.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; @@ -25,6 +25,7 @@ import 'package:aves/widgets/debug/media_store_scan_dialog.dart'; import 'package:aves/widgets/debug/report.dart'; import 'package:aves/widgets/debug/settings.dart'; import 'package:aves/widgets/debug/storage.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; @@ -36,6 +37,7 @@ class AppDebugPage extends StatelessWidget { @override Widget build(BuildContext context) { + final animations = context.select((s) => s.accessibilityAnimations); return Directionality( textDirection: TextDirection.ltr, child: AvesScaffold( @@ -56,9 +58,10 @@ class AppDebugPage extends StatelessWidget { .toList(), onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); unawaited(_onActionSelected(context, action)); }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ), ], diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart index 6e55b82c2..8f09dc16b 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/naming_pattern.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/styles.dart'; import 'package:aves/widgets/collection/collection_grid.dart'; @@ -14,8 +14,10 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/common/thumbnail/decorated.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; class RenameEntrySetPage extends StatefulWidget { static const routeName = '/rename_entry_set'; @@ -62,6 +64,7 @@ class _RenameEntrySetPageState extends State { final l10n = context.l10n; final textScaler = MediaQuery.textScalerOf(context); final effectiveThumbnailExtent = max(thumbnailExtent, textScaler.scale(thumbnailExtent)); + final animations = context.select((s) => s.accessibilityAnimations); return AvesScaffold( appBar: AppBar( title: Text(l10n.renameEntrySetPageTitle), @@ -104,11 +107,12 @@ class _RenameEntrySetPageState extends State { }, onSelected: (key) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); _insertProcessor(key); }, tooltip: l10n.renameEntrySetPageInsertTooltip, icon: const Icon(AIcons.add), + popUpAnimationStyle: animations.popUpAnimationStyle, ), ), ], diff --git a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart index 769f14f80..634341c88 100644 --- a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart @@ -2,6 +2,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/selection.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; @@ -205,6 +206,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { required bool Function(ChipSetAction action) isVisible, required void Function(ChipSetAction action) onActionSelected, }) { + final animations = context.select((s) => s.accessibilityAnimations); return [ if (widget.moveType != null) ..._quickActions.where(isVisible).map( @@ -227,9 +229,10 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { FocusManager.instance.primaryFocus?.unfocus(); // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); onActionSelected(action); }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ]; } diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index f39bba04e..a9cf68144 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -4,6 +4,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/durations.dart'; @@ -329,6 +330,7 @@ class _FilterGridAppBarState _buildButtonIcon(context, actionDelegate, action, enabled: canApply(action)), ); + final animations = context.select((s) => s.accessibilityAnimations); return [ ...quickActionButtons, PopupMenuButton( @@ -366,9 +368,10 @@ class _FilterGridAppBarState with SingleTickerProviderStateMixin ) async { final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension); + final animations = context.read().accessibilityAnimations; final selectedAction = await showMenu( context: context, position: RelativeRect.fromRect(tapLocalPosition & touchArea, Offset.zero & overlay.size), @@ -482,10 +484,11 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin MapClusterAction.removeLocation, ].map(_buildMenuItem), ], + popUpAnimationStyle: animations.popUpAnimationStyle, ); if (selectedAction != null) { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); final delegate = EntrySetActionDelegate(); switch (selectedAction) { case MapClusterAction.editLocation: diff --git a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart index a458578b0..668f3d2d8 100644 --- a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart +++ b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; @@ -18,6 +19,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; class FilePickerPage extends StatefulWidget { static const routeName = '/file_picker'; @@ -57,6 +59,7 @@ class _FilePickerPageState extends State { return !isHidden; } }).toList(); + final animations = context.select((s) => s.accessibilityAnimations); return PopScope( canPop: _directory.relativeDir.isEmpty, onPopInvoked: (didPop) { @@ -82,13 +85,14 @@ class _FilePickerPageState extends State { }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); switch (action) { case _PickerAction.toggleHiddenView: settings.filePickerShowHiddenFiles = !showHidden; setState(() {}); } }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ), ], diff --git a/lib/widgets/settings/settings_mobile_page.dart b/lib/widgets/settings/settings_mobile_page.dart index 27633b15b..c4ee713fd 100644 --- a/lib/widgets/settings/settings_mobile_page.dart +++ b/lib/widgets/settings/settings_mobile_page.dart @@ -1,10 +1,11 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; @@ -45,6 +46,7 @@ class _SettingsMobilePageState extends State with FeedbackMi @override Widget build(BuildContext context) { + final animations = context.select((s) => s.accessibilityAnimations); return AvesScaffold( appBar: AppBar( title: InteractiveAppBarTitle( @@ -72,9 +74,10 @@ class _SettingsMobilePageState extends State with FeedbackMi }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); _onActionSelected(action); }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ].map((v) => FontSizeIconTheme(child: v)).toList(), ), diff --git a/lib/widgets/viewer/info/info_app_bar.dart b/lib/widgets/viewer/info/info_app_bar.dart index 178cf8bc1..c3c06e24d 100644 --- a/lib/widgets/viewer/info/info_app_bar.dart +++ b/lib/widgets/viewer/info/info_app_bar.dart @@ -2,9 +2,9 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/selection.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/view/view.dart'; @@ -49,6 +49,7 @@ class InfoAppBar extends StatelessWidget { final commonActions = EntryActions.commonMetadataActions.where(isVisible); final formatSpecificActions = EntryActions.formatSpecificMetadataActions.where(isVisible); final useTvLayout = settings.useTvLayout; + final animations = context.select((s) => s.accessibilityAnimations); return SliverAppBar( leading: useTvLayout ? null @@ -91,9 +92,10 @@ class InfoAppBar extends StatelessWidget { ], onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(ADurations.popupMenuAnimation * timeDilation); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); actionDelegate.onActionSelected(context, entry, collection, action); }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ].map((v) => FontSizeIconTheme(child: v)).toList(), floating: true, diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart index 4c3069986..88a8508e5 100644 --- a/lib/widgets/viewer/overlay/viewer_buttons.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -4,9 +4,9 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/move_button.dart'; @@ -250,6 +250,7 @@ class _ViewerButtonRowContentState extends State { final exportActions = widget.exportActions; final videoActions = widget.videoActions; final hasOverflowMenu = pageEntry.canRotate || pageEntry.canFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty || videoActions.isNotEmpty; + final animations = context.select((s) => s.accessibilityAnimations); return Selector( selector: (context, vc) => vc.getController(pageEntry), builder: (context, videoController, child) { @@ -301,10 +302,11 @@ class _ViewerButtonRowContentState extends State { ] ]; }, - onSelected: (action) { + onSelected: (action) async { _popupExpandedNotifier.value = null; // wait for the popup menu to hide before proceeding with the action - Future.delayed(ADurations.popupMenuAnimation * timeDilation, () => widget.actionDelegate.onActionSelected(context, action)); + await Future.delayed(animations.popUpAnimationDelay * timeDilation); + widget.actionDelegate.onActionSelected(context, action); }, onCanceled: () { _popupExpandedNotifier.value = null; @@ -316,6 +318,7 @@ class _ViewerButtonRowContentState extends State { // so we make sure overlay stays visible const ToggleOverlayNotification(visible: true).dispatch(context); }, + popUpAnimationStyle: animations.popUpAnimationStyle, ), ), ),