From 8d096e5e9b991eb9fd9c91eb6fccc413c9a7756a Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 28 Sep 2021 11:17:55 +0900 Subject: [PATCH] accessibility: remove animations (WIP) --- .../channel/calls/AccessibilityHandler.kt | 2 +- lib/l10n/app_en.arb | 9 ++++ lib/l10n/app_ko.arb | 5 ++ .../settings/accessibility_animations.dart | 17 ++++++ ...imeout.dart => accessibility_timeout.dart} | 0 lib/model/settings/defaults.dart | 11 ++-- lib/model/settings/enums.dart | 2 + lib/model/settings/settings.dart | 10 +++- ...ervice.dart => accessibility_service.dart} | 2 +- lib/theme/durations.dart | 53 +++++++++++++++++-- lib/theme/icons.dart | 2 +- lib/widgets/aves_app.dart | 53 +++++++++++++------ lib/widgets/collection/collection_grid.dart | 3 +- .../collection/entry_set_action_delegate.dart | 3 +- .../common/action_mixins/feedback.dart | 2 +- .../common/behaviour/accessibility_mixin.dart | 17 ++++++ lib/widgets/common/behaviour/routes.dart | 12 +++++ lib/widgets/common/grid/section_layout.dart | 9 ++-- .../filter_grids/common/filter_grid_page.dart | 3 +- .../accessibility.dart} | 6 ++- .../accessibility/remove_animations.dart | 38 +++++++++++++ .../time_to_take_action.dart | 4 +- lib/widgets/settings/settings_page.dart | 8 +-- lib/widgets/viewer/entry_action_delegate.dart | 3 +- .../info/metadata/metadata_section.dart | 7 ++- lib/widgets/viewer/video_action_delegate.dart | 3 +- lib/widgets/welcome_page.dart | 6 ++- 27 files changed, 237 insertions(+), 53 deletions(-) create mode 100644 lib/model/settings/accessibility_animations.dart rename lib/model/settings/{a11y_timeout.dart => accessibility_timeout.dart} (100%) rename lib/services/{a11y_service.dart => accessibility_service.dart} (98%) create mode 100644 lib/widgets/common/behaviour/accessibility_mixin.dart rename lib/widgets/settings/{a11y/a11y.dart => accessibility/accessibility.dart} (80%) create mode 100644 lib/widgets/settings/accessibility/remove_animations.dart rename lib/widgets/settings/{a11y => accessibility}/time_to_take_action.dart (94%) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt index 3c0957182..30e46b2a2 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt @@ -59,6 +59,6 @@ class AccessibilityHandler(private val context: Activity) : MethodCallHandler { } companion object { - const val CHANNEL = "deckers.thibault/aves/a11y" + const val CHANNEL = "deckers.thibault/aves/accessibility" } } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 803b849f3..9217b8d68 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -200,6 +200,11 @@ "keepScreenOnAlways": "Always", "@keepScreenOnAlways": {}, + "accessibilityAnimationsRemove": "Prevent screen effects", + "@accessibilityAnimationsRemove": {}, + "accessibilityAnimationsKeep": "Keep screen effects", + "@accessibilityAnimationsKeep": {}, + "albumTierNew": "New", "@albumTierNew": {}, "albumTierPinned": "Pinned", @@ -799,6 +804,10 @@ "settingsSectionAccessibility": "Accessibility", "@settingsSectionAccessibility": {}, + "settingsRemoveAnimationsTile": "Remove animations", + "@settingsRemoveAnimationsTile": {}, + "settingsRemoveAnimationsTitle": "Remove Animations", + "@settingsRemoveAnimationsTitle": {}, "settingsTimeToTakeActionTile": "Time to take action", "@settingsTimeToTakeActionTile": {}, "settingsTimeToTakeActionTitle": "Time to Take Action", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 887e4787a..d35bb1b32 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -101,6 +101,9 @@ "keepScreenOnViewerOnly": "뷰어 이용 시 작동", "keepScreenOnAlways": "항상 켜짐", + "accessibilityAnimationsRemove": "화면 효과 제한", + "accessibilityAnimationsKeep": "화면 효과 유지", + "albumTierNew": "신규", "albumTierPinned": "고정", "albumTierSpecial": "기본", @@ -389,6 +392,8 @@ "settingsStorageAccessRevokeTooltip": "취소", "settingsSectionAccessibility": "접근성", + "settingsRemoveAnimationsTile": "애니메이션 삭제", + "settingsRemoveAnimationsTitle": "애니메이션 삭제", "settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간", "settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간", diff --git a/lib/model/settings/accessibility_animations.dart b/lib/model/settings/accessibility_animations.dart new file mode 100644 index 000000000..a226f40c1 --- /dev/null +++ b/lib/model/settings/accessibility_animations.dart @@ -0,0 +1,17 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraAccessibilityAnimations on AccessibilityAnimations { + String getName(BuildContext context) { + switch (this) { + case AccessibilityAnimations.system: + return context.l10n.settingsSystemDefault; + case AccessibilityAnimations.disabled: + return context.l10n.accessibilityAnimationsRemove; + case AccessibilityAnimations.enabled: + return context.l10n.accessibilityAnimationsKeep; + } + } +} diff --git a/lib/model/settings/a11y_timeout.dart b/lib/model/settings/accessibility_timeout.dart similarity index 100% rename from lib/model/settings/a11y_timeout.dart rename to lib/model/settings/accessibility_timeout.dart diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 343ed3dcf..4aaa597e4 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -57,9 +57,7 @@ class SettingsDefaults { static const showOverlayMinimap = false; static const showOverlayInfo = true; static const showOverlayShootingDetails = false; - - // `enableOverlayBlurEffect` has a contextual default value - static const enableOverlayBlurEffect = true; + static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value static const viewerUseCutout = true; // video @@ -80,8 +78,7 @@ class SettingsDefaults { static const subtitleBackgroundColor = Colors.transparent; // info - // `infoMapStyle` has a contextual default value - static const infoMapStyle = EntryMapStyle.stamenWatercolor; + static const infoMapStyle = EntryMapStyle.stamenWatercolor; // `infoMapStyle` has a contextual default value static const infoMapZoom = 12.0; static const coordinateFormat = CoordinateFormat.dms; @@ -92,6 +89,6 @@ class SettingsDefaults { static const saveSearchHistory = true; // accessibility - // `timeToTakeAction` has a contextual default value - static const timeToTakeAction = AccessibilityTimeout.appDefault; + static const accessibilityAnimations = AccessibilityAnimations.system; + static const timeToTakeAction = AccessibilityTimeout.appDefault; // `timeToTakeAction` has a contextual default value } diff --git a/lib/model/settings/enums.dart b/lib/model/settings/enums.dart index 5a64623b5..1085d1107 100644 --- a/lib/model/settings/enums.dart +++ b/lib/model/settings/enums.dart @@ -1,5 +1,7 @@ enum CoordinateFormat { dms, decimal } +enum AccessibilityAnimations { system, disabled, enabled } + enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 } enum EntryBackground { black, white, checkered } diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 10f4edbd3..0c2a60dfd 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -10,7 +10,7 @@ import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/map_style.dart'; import 'package:aves/model/source/enums.dart'; -import 'package:aves/services/a11y_service.dart'; +import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -106,6 +106,7 @@ class Settings extends ChangeNotifier { static const searchHistoryKey = 'search_history'; // accessibility + static const accessibilityAnimationsKey = 'accessibility_animations'; static const timeToTakeActionKey = 'time_to_take_action'; // version @@ -115,6 +116,8 @@ class Settings extends ChangeNotifier { // cf Android `Settings.System.ACCELEROMETER_ROTATION` static const platformAccelerometerRotationKey = 'accelerometer_rotation'; + bool get initialized => _prefs != null; + Future init({bool isRotationLocked = false}) async { _prefs = await SharedPreferences.getInstance(); _isRotationLocked = isRotationLocked; @@ -382,6 +385,10 @@ class Settings extends ChangeNotifier { // accessibility + AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values); + + set accessibilityAnimations(AccessibilityAnimations newValue) => setAndNotify(accessibilityAnimationsKey, newValue.toString()); + AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values); set timeToTakeAction(AccessibilityTimeout newValue) => setAndNotify(timeToTakeActionKey, newValue.toString()); @@ -538,6 +545,7 @@ class Settings extends ChangeNotifier { case infoMapStyleKey: case coordinateFormatKey: case imageBackgroundKey: + case accessibilityAnimationsKey: case timeToTakeActionKey: if (value is String) { _prefs!.setString(key, value); diff --git a/lib/services/a11y_service.dart b/lib/services/accessibility_service.dart similarity index 98% rename from lib/services/a11y_service.dart rename to lib/services/accessibility_service.dart index cccde4741..c766552fb 100644 --- a/lib/services/a11y_service.dart +++ b/lib/services/accessibility_service.dart @@ -2,7 +2,7 @@ import 'package:aves/services/common/services.dart'; import 'package:flutter/services.dart'; class AccessibilityService { - static const platform = MethodChannel('deckers.thibault/aves/a11y'); + static const platform = MethodChannel('deckers.thibault/aves/accessibility'); static Future hasRecommendedTimeouts() async { try { diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index a27af7c77..2bdba5bfb 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -1,4 +1,7 @@ -import 'package:flutter/scheduler.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/behaviour/accessibility_mixin.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; class Durations { // Flutter animations (with margin) @@ -14,8 +17,8 @@ class Durations { static const sweeperOpacityAnimation = Duration(milliseconds: 150); static const sweepingAnimation = Duration(milliseconds: 650); - static const staggeredAnimation = Duration(milliseconds: 375); - static const staggeredAnimationPageTarget = Duration(milliseconds: 800); + // static const staggeredAnimation = Duration(milliseconds: 375); + // static const staggeredAnimationPageTarget = Duration(milliseconds: 800); static const dialogFieldReachAnimation = Duration(milliseconds: 300); static const appBarTitleAnimation = Duration(milliseconds: 300); @@ -64,7 +67,8 @@ class Durations { static const highlightScrollInitDelay = Duration(milliseconds: 800); static const videoOverlayHideDelay = Duration(milliseconds: 500); static const videoProgressTimerInterval = Duration(milliseconds: 300); - static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation; + + // static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation; static const doubleBackTimerDelay = Duration(milliseconds: 1000); static const softKeyboardDisplayDelay = Duration(milliseconds: 300); static const searchDebounceDelay = Duration(milliseconds: 250); @@ -75,3 +79,44 @@ class Durations { // app life static const lastVersionCheckInterval = Duration(days: 7); } + +class DurationsProvider extends StatelessWidget with AccessibilityMixin { + final Widget child; + + const DurationsProvider({ + Key? key, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ProxyProvider( + update: (_, settings, __) { + return areAnimationsEnabled() ? DurationsData() : DurationsData.noAnimation(); + }, + child: child, + ); + } +} + +@immutable +class DurationsData { + // common animations + final Duration staggeredAnimation; + final Duration staggeredAnimationPageTarget; + + // delays & refresh intervals + final Duration staggeredAnimationDelay; + + const DurationsData({ + this.staggeredAnimation = const Duration(milliseconds: 375), + this.staggeredAnimationPageTarget = const Duration(milliseconds: 800), + }) : staggeredAnimationDelay = staggeredAnimation ~/ 6; + + factory DurationsData.noAnimation() { + return DurationsData( + staggeredAnimation: Duration.zero, + staggeredAnimationPageTarget: Duration.zero, + ); + } +} diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index d00c22c10..8817bcf65 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -7,7 +7,7 @@ class AIcons { static const IconData video = Icons.movie_outlined; static const IconData vector = Icons.code_outlined; - static const IconData a11y = Icons.accessibility_new_outlined; + static const IconData accessibility = Icons.accessibility_new_outlined; static const IconData android = Icons.android; static const IconData broken = Icons.broken_image_outlined; static const IconData checked = Icons.done_outlined; diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 07f0d5399..a61e25886 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -10,6 +10,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/utils/debouncer.dart'; +import 'package:aves/widgets/common/behaviour/accessibility_mixin.dart'; import 'package:aves/widgets/common/behaviour/route_tracker.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -23,6 +24,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; class AvesApp extends StatefulWidget { const AvesApp({Key? key}) : super(key: key); @@ -31,7 +33,7 @@ class AvesApp extends StatefulWidget { _AvesAppState createState() => _AvesAppState(); } -class _AvesAppState extends State { +class _AvesAppState extends State with AccessibilityMixin { final ValueNotifier appModeNotifier = ValueNotifier(AppMode.main); late Future _appSetup; final _mediaStoreSource = MediaStoreSource(); @@ -68,24 +70,39 @@ class _AvesAppState extends State { value: appModeNotifier, child: Provider.value( value: _mediaStoreSource, - child: HighlightInfoProvider( - child: OverlaySupport( - child: FutureBuilder( - future: _appSetup, - builder: (context, snapshot) { - final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done; - final home = initialized - ? getFirstPage() - : Scaffold( - body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(), - ); - return Selector( - selector: (context, s) => s.locale, - builder: (context, settingsLocale, child) { + child: DurationsProvider( + child: HighlightInfoProvider( + child: OverlaySupport( + child: FutureBuilder( + future: _appSetup, + builder: (context, snapshot) { + final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done; + final home = initialized + ? getFirstPage() + : Scaffold( + body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(), + ); + return Selector>( + selector: (context, s) => Tuple2(s.locale, s.initialized ? areAnimationsEnabled() : true), + builder: (context, s, child) { + final settingsLocale = s.item1; + final areAnimationsEnabled = s.item2; return MaterialApp( navigatorKey: _navigatorKey, home: home, navigatorObservers: _navigatorObservers, + builder: (context, child) { + if (!areAnimationsEnabled) { + child = Theme( + data: Theme.of(context).copyWith( + // strip page transitions used by `MaterialPageRoute` + pageTransitionsTheme: DirectPageTransitionsTheme(), + ), + child: child!, + ); + } + return child!; + }, onGenerateTitle: (context) => context.l10n.appName, darkTheme: Themes.darkTheme, themeMode: ThemeMode.dark, @@ -97,8 +114,10 @@ class _AvesAppState extends State { // checkerboardRasterCacheImages: true, // checkerboardOffscreenLayers: true, ); - }); - }, + }, + ); + }, + ), ), ), ), diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 228a2c239..a852e3a80 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -86,7 +86,8 @@ class _CollectionGridContent extends StatelessWidget { final columnCount = c.item2; final tileSpacing = c.item3; // do not listen for animation delay change - final tileAnimationDelay = context.read().getTileAnimationDelay(Durations.staggeredAnimationPageTarget); + final target = context.read().staggeredAnimationPageTarget; + final tileAnimationDelay = context.read().getTileAnimationDelay(target); return GridTheme( extent: tileExtent, child: SectionedEntryListLayoutProvider( diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index c672758b8..bb9ede8ab 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -173,7 +173,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware ), ), )); - await Future.delayed(Durations.staggeredAnimationPageTarget); + final delayDuration = context.read().staggeredAnimationPageTarget; + await Future.delayed(delayDuration); } await Future.delayed(Durations.highlightScrollInitDelay); final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet(); diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index e9d8cf6e8..e37f655e7 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/services/a11y_service.dart'; +import 'package:aves/services/accessibility_service.dart'; import 'package:aves/theme/durations.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/lib/widgets/common/behaviour/accessibility_mixin.dart b/lib/widgets/common/behaviour/accessibility_mixin.dart new file mode 100644 index 000000000..33a8fe25f --- /dev/null +++ b/lib/widgets/common/behaviour/accessibility_mixin.dart @@ -0,0 +1,17 @@ +import 'dart:ui'; + +import 'package:aves/model/settings/enums.dart'; +import 'package:aves/model/settings/settings.dart'; + +mixin AccessibilityMixin { + bool areAnimationsEnabled() { + switch (settings.accessibilityAnimations) { + case AccessibilityAnimations.system: + return !window.accessibilityFeatures.disableAnimations; + case AccessibilityAnimations.disabled: + return false; + case AccessibilityAnimations.enabled: + return true; + } + } +} diff --git a/lib/widgets/common/behaviour/routes.dart b/lib/widgets/common/behaviour/routes.dart index 0051022bf..7b21b8e91 100644 --- a/lib/widgets/common/behaviour/routes.dart +++ b/lib/widgets/common/behaviour/routes.dart @@ -1,5 +1,17 @@ import 'package:flutter/material.dart'; +class DirectPageTransitionsTheme extends PageTransitionsTheme { + @override + Widget buildTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) => + child; +} + class DirectMaterialPageRoute extends PageRouteBuilder { DirectMaterialPageRoute({ RouteSettings? settings, diff --git a/lib/widgets/common/grid/section_layout.dart b/lib/widgets/common/grid/section_layout.dart index 727b81f95..88aaad51e 100644 --- a/lib/widgets/common/grid/section_layout.dart +++ b/lib/widgets/common/grid/section_layout.dart @@ -108,7 +108,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { ) { if (sectionChildIndex == 0) { final header = headerExtent > 0 ? buildHeader(context, sectionKey, headerExtent) : const SizedBox.shrink(); - return animate ? _buildAnimation(sectionGridIndex, header) : header; + return animate ? _buildAnimation(context, sectionGridIndex, header) : header; } sectionChildIndex--; @@ -122,7 +122,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { final item = RepaintBoundary( child: tileBuilder(section[i]), ); - children.add(animate ? _buildAnimation(itemGridIndex, item) : item); + children.add(animate ? _buildAnimation(context, itemGridIndex, item) : item); } return _GridRow( width: tileWidth, @@ -132,11 +132,12 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { ); } - Widget _buildAnimation(int index, Widget child) { + Widget _buildAnimation(BuildContext context, int index, Widget child) { + final durations = context.watch(); return AnimationConfiguration.staggeredGrid( position: index, columnCount: columnCount, - duration: Durations.staggeredAnimation, + duration: durations.staggeredAnimation, delay: tileAnimationDelay, child: SlideAnimation( verticalOffset: 50.0, diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index a82b088de..8e36ffe22 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -245,7 +245,8 @@ class _FilterGridContent extends StatelessWidget { final columnCount = c.item2; final tileSpacing = c.item3; // do not listen for animation delay change - final tileAnimationDelay = context.read().getTileAnimationDelay(Durations.staggeredAnimationPageTarget); + final target = context.read().staggeredAnimationPageTarget; + final tileAnimationDelay = context.read().getTileAnimationDelay(target); return Selector( selector: (context, mq) => mq.textScaleFactor, builder: (context, textScaleFactor, child) { diff --git a/lib/widgets/settings/a11y/a11y.dart b/lib/widgets/settings/accessibility/accessibility.dart similarity index 80% rename from lib/widgets/settings/a11y/a11y.dart rename to lib/widgets/settings/accessibility/accessibility.dart index 5b2705d8c..3d65b0417 100644 --- a/lib/widgets/settings/a11y/a11y.dart +++ b/lib/widgets/settings/accessibility/accessibility.dart @@ -2,7 +2,8 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; -import 'package:aves/widgets/settings/a11y/time_to_take_action.dart'; +import 'package:aves/widgets/settings/accessibility/remove_animations.dart'; +import 'package:aves/widgets/settings/accessibility/time_to_take_action.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; import 'package:flutter/material.dart'; @@ -18,13 +19,14 @@ class AccessibilitySection extends StatelessWidget { Widget build(BuildContext context) { return AvesExpansionTile( leading: SettingsTileLeading( - icon: AIcons.a11y, + icon: AIcons.accessibility, color: stringToColor('Accessibility'), ), title: context.l10n.settingsSectionAccessibility, expandedNotifier: expandedNotifier, showHighlight: false, children: const [ + RemoveAnimationsTile(), TimeToTakeActionTile(), ], ); diff --git a/lib/widgets/settings/accessibility/remove_animations.dart b/lib/widgets/settings/accessibility/remove_animations.dart new file mode 100644 index 000000000..bf257867f --- /dev/null +++ b/lib/widgets/settings/accessibility/remove_animations.dart @@ -0,0 +1,38 @@ +import 'package:aves/model/settings/accessibility_animations.dart'; +import 'package:aves/model/settings/enums.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; + +class RemoveAnimationsTile extends StatelessWidget { + const RemoveAnimationsTile({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final currentAnimations = context.select((s) => s.accessibilityAnimations); + + return ListTile( + title: Text(context.l10n.settingsRemoveAnimationsTile), + subtitle: Text(currentAnimations.getName(context)), + onTap: () async { + final value = await showDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentAnimations, + options: Map.fromEntries(AccessibilityAnimations.values.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsRemoveAnimationsTitle, + ), + ); + // wait for the dialog to hide as applying the change may block the UI + await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + if (value != null) { + settings.accessibilityAnimations = value; + } + }, + ); + } +} diff --git a/lib/widgets/settings/a11y/time_to_take_action.dart b/lib/widgets/settings/accessibility/time_to_take_action.dart similarity index 94% rename from lib/widgets/settings/a11y/time_to_take_action.dart rename to lib/widgets/settings/accessibility/time_to_take_action.dart index a1b3cc2a0..75ceb0cb9 100644 --- a/lib/widgets/settings/a11y/time_to_take_action.dart +++ b/lib/widgets/settings/accessibility/time_to_take_action.dart @@ -1,7 +1,7 @@ -import 'package:aves/model/settings/a11y_timeout.dart'; +import 'package:aves/model/settings/accessibility_timeout.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/services/a11y_service.dart'; +import 'package:aves/services/accessibility_service.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index 368b6fce5..d11940701 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -11,7 +11,7 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; -import 'package:aves/widgets/settings/a11y/a11y.dart'; +import 'package:aves/widgets/settings/accessibility/accessibility.dart'; import 'package:aves/widgets/settings/language/language.dart'; import 'package:aves/widgets/settings/navigation/navigation.dart'; import 'package:aves/widgets/settings/privacy/privacy.dart'; @@ -22,6 +22,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; class SettingsPage extends StatefulWidget { static const routeName = '/settings'; @@ -38,6 +39,7 @@ class _SettingsPageState extends State with FeedbackMixin { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final durations = context.watch(); return MediaQueryDataProvider( child: Scaffold( appBar: AppBar( @@ -78,8 +80,8 @@ class _SettingsPageState extends State with FeedbackMixin { child: ListView( padding: const EdgeInsets.all(8), children: AnimationConfiguration.toStaggeredList( - duration: Durations.staggeredAnimation, - delay: Durations.staggeredAnimationDelay, + duration: durations.staggeredAnimation, + delay: durations.staggeredAnimationDelay * timeDilation, childAnimationBuilder: (child) => SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( diff --git a/lib/widgets/viewer/entry_action_delegate.dart b/lib/widgets/viewer/entry_action_delegate.dart index 1433ec610..aba76e6f6 100644 --- a/lib/widgets/viewer/entry_action_delegate.dart +++ b/lib/widgets/viewer/entry_action_delegate.dart @@ -233,7 +233,8 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix ), (route) => false, )); - await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay); + final delayDuration = context.read().staggeredAnimationPageTarget; + await Future.delayed(delayDuration + Durations.highlightScrollInitDelay); final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet(); final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri)); if (targetEntry != null) { diff --git a/lib/widgets/viewer/info/metadata/metadata_section.dart b/lib/widgets/viewer/info/metadata/metadata_section.dart index 0cef684ca..72a1c0fa8 100644 --- a/lib/widgets/viewer/info/metadata/metadata_section.dart +++ b/lib/widgets/viewer/info/metadata/metadata_section.dart @@ -15,7 +15,9 @@ import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:provider/provider.dart'; class MetadataSectionSliver extends StatefulWidget { final AvesEntry entry; @@ -90,10 +92,11 @@ class _MetadataSectionSliverState extends State { if (metadata.isEmpty) { content = const SizedBox.shrink(); } else { + final durations = context.watch(); content = Column( children: AnimationConfiguration.toStaggeredList( - duration: Durations.staggeredAnimation, - delay: Durations.staggeredAnimationDelay, + duration: durations.staggeredAnimation, + delay: durations.staggeredAnimationDelay * timeDilation, childAnimationBuilder: (child) => SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( diff --git a/lib/widgets/viewer/video_action_delegate.dart b/lib/widgets/viewer/video_action_delegate.dart index 9a1b4048d..b751a774e 100644 --- a/lib/widgets/viewer/video_action_delegate.dart +++ b/lib/widgets/viewer/video_action_delegate.dart @@ -116,7 +116,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix ), (route) => false, )); - await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay); + final delayDuration = context.read().staggeredAnimationPageTarget; + await Future.delayed(delayDuration + Durations.highlightScrollInitDelay); final newUri = newFields['uri'] as String?; final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => entry.uri == newUri); if (targetEntry != null) { diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index 13365b2e0..91a3c3361 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -7,6 +7,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/home_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; @@ -44,11 +45,12 @@ class _WelcomePageState extends State { builder: (context, snapshot) { if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); final terms = snapshot.data!; + final durations = context.watch(); return Column( mainAxisSize: MainAxisSize.min, children: _toStaggeredList( - duration: Durations.staggeredAnimation, - delay: Durations.staggeredAnimationDelay, + duration: durations.staggeredAnimation, + delay: durations.staggeredAnimationDelay * timeDilation, childAnimationBuilder: (child) => SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation(