a11y: disabling animations also applies to pop up menus

This commit is contained in:
Thibault Deckers 2024-02-24 18:38:15 +01:00
parent 786335ede3
commit 535936666c
15 changed files with 78 additions and 23 deletions

View file

@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Changed
- check Media Store changes when resuming app
- disabling animations also applies to pop up menus
## <a id="v1.10.5"></a>[v1.10.5] - 2024-02-22
### Added

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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<CollectionAppBar> with SingleTickerPr
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
);
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [
...quickActionButtons,
PopupMenuButton<EntrySetAction>(
@ -432,9 +434,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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,
),
];
}

View file

@ -21,6 +21,7 @@ class AvesPopupMenuButton<T> extends PopupMenuButton<T> {
super.enableFeedback,
super.iconSize,
this.onMenuOpened,
super.popUpAnimationStyle,
});
@override

View file

@ -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<Settings>().accessibilityAnimations;
final selectedAction = await showMenu<ChipAction>(
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);
}
}

View file

@ -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<Settings, AccessibilityAnimations>((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,
),
),
],

View file

@ -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<RenameEntrySetPage> {
final l10n = context.l10n;
final textScaler = MediaQuery.textScalerOf(context);
final effectiveThumbnailExtent = max(thumbnailExtent, textScaler.scale(thumbnailExtent));
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return AvesScaffold(
appBar: AppBar(
title: Text(l10n.renameEntrySetPageTitle),
@ -104,11 +107,12 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
},
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,
),
),
],

View file

@ -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<Settings, AccessibilityAnimations>((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,
),
];
}

View file

@ -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<T extends CollectionFilter, CSAD extends ChipSetAct
(action) => _buildButtonIcon(context, actionDelegate, action, enabled: canApply(action)),
);
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [
...quickActionButtons,
PopupMenuButton<ChipSetAction>(
@ -366,9 +368,10 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
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(context, action, actionDelegate);
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
];
}

View file

@ -7,6 +7,7 @@ import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/geotiff.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
@ -462,6 +463,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
) async {
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
final animations = context.read<Settings>().accessibilityAnimations;
final selectedAction = await showMenu<MapClusterAction>(
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:

View file

@ -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<FilePickerPage> {
return !isHidden;
}
}).toList();
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return PopScope(
canPop: _directory.relativeDir.isEmpty,
onPopInvoked: (didPop) {
@ -82,13 +85,14 @@ class _FilePickerPageState extends State<FilePickerPage> {
},
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,
),
),
],

View file

@ -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<SettingsMobilePage> with FeedbackMi
@override
Widget build(BuildContext context) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return AvesScaffold(
appBar: AppBar(
title: InteractiveAppBarTitle(
@ -72,9 +74,10 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> 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(),
),

View file

@ -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<Settings, AccessibilityAnimations>((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,

View file

@ -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<ViewerButtonRowContent> {
final exportActions = widget.exportActions;
final videoActions = widget.videoActions;
final hasOverflowMenu = pageEntry.canRotate || pageEntry.canFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty || videoActions.isNotEmpty;
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return Selector<VideoConductor, AvesVideoController?>(
selector: (context, vc) => vc.getController(pageEntry),
builder: (context, videoController, child) {
@ -301,10 +302,11 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
]
];
},
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<ViewerButtonRowContent> {
// so we make sure overlay stays visible
const ToggleOverlayNotification(visible: true).dispatch(context);
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
),
),