accessibility: remove animations (WIP)
This commit is contained in:
parent
c5942ba344
commit
8d096e5e9b
27 changed files with 237 additions and 53 deletions
|
@ -59,6 +59,6 @@ class AccessibilityHandler(private val context: Activity) : MethodCallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL = "deckers.thibault/aves/a11y"
|
const val CHANNEL = "deckers.thibault/aves/accessibility"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -200,6 +200,11 @@
|
||||||
"keepScreenOnAlways": "Always",
|
"keepScreenOnAlways": "Always",
|
||||||
"@keepScreenOnAlways": {},
|
"@keepScreenOnAlways": {},
|
||||||
|
|
||||||
|
"accessibilityAnimationsRemove": "Prevent screen effects",
|
||||||
|
"@accessibilityAnimationsRemove": {},
|
||||||
|
"accessibilityAnimationsKeep": "Keep screen effects",
|
||||||
|
"@accessibilityAnimationsKeep": {},
|
||||||
|
|
||||||
"albumTierNew": "New",
|
"albumTierNew": "New",
|
||||||
"@albumTierNew": {},
|
"@albumTierNew": {},
|
||||||
"albumTierPinned": "Pinned",
|
"albumTierPinned": "Pinned",
|
||||||
|
@ -799,6 +804,10 @@
|
||||||
|
|
||||||
"settingsSectionAccessibility": "Accessibility",
|
"settingsSectionAccessibility": "Accessibility",
|
||||||
"@settingsSectionAccessibility": {},
|
"@settingsSectionAccessibility": {},
|
||||||
|
"settingsRemoveAnimationsTile": "Remove animations",
|
||||||
|
"@settingsRemoveAnimationsTile": {},
|
||||||
|
"settingsRemoveAnimationsTitle": "Remove Animations",
|
||||||
|
"@settingsRemoveAnimationsTitle": {},
|
||||||
"settingsTimeToTakeActionTile": "Time to take action",
|
"settingsTimeToTakeActionTile": "Time to take action",
|
||||||
"@settingsTimeToTakeActionTile": {},
|
"@settingsTimeToTakeActionTile": {},
|
||||||
"settingsTimeToTakeActionTitle": "Time to Take Action",
|
"settingsTimeToTakeActionTitle": "Time to Take Action",
|
||||||
|
|
|
@ -101,6 +101,9 @@
|
||||||
"keepScreenOnViewerOnly": "뷰어 이용 시 작동",
|
"keepScreenOnViewerOnly": "뷰어 이용 시 작동",
|
||||||
"keepScreenOnAlways": "항상 켜짐",
|
"keepScreenOnAlways": "항상 켜짐",
|
||||||
|
|
||||||
|
"accessibilityAnimationsRemove": "화면 효과 제한",
|
||||||
|
"accessibilityAnimationsKeep": "화면 효과 유지",
|
||||||
|
|
||||||
"albumTierNew": "신규",
|
"albumTierNew": "신규",
|
||||||
"albumTierPinned": "고정",
|
"albumTierPinned": "고정",
|
||||||
"albumTierSpecial": "기본",
|
"albumTierSpecial": "기본",
|
||||||
|
@ -389,6 +392,8 @@
|
||||||
"settingsStorageAccessRevokeTooltip": "취소",
|
"settingsStorageAccessRevokeTooltip": "취소",
|
||||||
|
|
||||||
"settingsSectionAccessibility": "접근성",
|
"settingsSectionAccessibility": "접근성",
|
||||||
|
"settingsRemoveAnimationsTile": "애니메이션 삭제",
|
||||||
|
"settingsRemoveAnimationsTitle": "애니메이션 삭제",
|
||||||
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
|
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
|
||||||
"settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간",
|
"settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간",
|
||||||
|
|
||||||
|
|
17
lib/model/settings/accessibility_animations.dart
Normal file
17
lib/model/settings/accessibility_animations.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,9 +57,7 @@ class SettingsDefaults {
|
||||||
static const showOverlayMinimap = false;
|
static const showOverlayMinimap = false;
|
||||||
static const showOverlayInfo = true;
|
static const showOverlayInfo = true;
|
||||||
static const showOverlayShootingDetails = false;
|
static const showOverlayShootingDetails = false;
|
||||||
|
static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value
|
||||||
// `enableOverlayBlurEffect` has a contextual default value
|
|
||||||
static const enableOverlayBlurEffect = true;
|
|
||||||
static const viewerUseCutout = true;
|
static const viewerUseCutout = true;
|
||||||
|
|
||||||
// video
|
// video
|
||||||
|
@ -80,8 +78,7 @@ class SettingsDefaults {
|
||||||
static const subtitleBackgroundColor = Colors.transparent;
|
static const subtitleBackgroundColor = Colors.transparent;
|
||||||
|
|
||||||
// info
|
// info
|
||||||
// `infoMapStyle` has a contextual default value
|
static const infoMapStyle = EntryMapStyle.stamenWatercolor; // `infoMapStyle` has a contextual default value
|
||||||
static const infoMapStyle = EntryMapStyle.stamenWatercolor;
|
|
||||||
static const infoMapZoom = 12.0;
|
static const infoMapZoom = 12.0;
|
||||||
static const coordinateFormat = CoordinateFormat.dms;
|
static const coordinateFormat = CoordinateFormat.dms;
|
||||||
|
|
||||||
|
@ -92,6 +89,6 @@ class SettingsDefaults {
|
||||||
static const saveSearchHistory = true;
|
static const saveSearchHistory = true;
|
||||||
|
|
||||||
// accessibility
|
// accessibility
|
||||||
// `timeToTakeAction` has a contextual default value
|
static const accessibilityAnimations = AccessibilityAnimations.system;
|
||||||
static const timeToTakeAction = AccessibilityTimeout.appDefault;
|
static const timeToTakeAction = AccessibilityTimeout.appDefault; // `timeToTakeAction` has a contextual default value
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
enum CoordinateFormat { dms, decimal }
|
enum CoordinateFormat { dms, decimal }
|
||||||
|
|
||||||
|
enum AccessibilityAnimations { system, disabled, enabled }
|
||||||
|
|
||||||
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
|
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
|
||||||
|
|
||||||
enum EntryBackground { black, white, checkered }
|
enum EntryBackground { black, white, checkered }
|
||||||
|
|
|
@ -10,7 +10,7 @@ import 'package:aves/model/settings/defaults.dart';
|
||||||
import 'package:aves/model/settings/enums.dart';
|
import 'package:aves/model/settings/enums.dart';
|
||||||
import 'package:aves/model/settings/map_style.dart';
|
import 'package:aves/model/settings/map_style.dart';
|
||||||
import 'package:aves/model/source/enums.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:aves/services/common/services.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -106,6 +106,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const searchHistoryKey = 'search_history';
|
static const searchHistoryKey = 'search_history';
|
||||||
|
|
||||||
// accessibility
|
// accessibility
|
||||||
|
static const accessibilityAnimationsKey = 'accessibility_animations';
|
||||||
static const timeToTakeActionKey = 'time_to_take_action';
|
static const timeToTakeActionKey = 'time_to_take_action';
|
||||||
|
|
||||||
// version
|
// version
|
||||||
|
@ -115,6 +116,8 @@ class Settings extends ChangeNotifier {
|
||||||
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
|
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
|
||||||
static const platformAccelerometerRotationKey = 'accelerometer_rotation';
|
static const platformAccelerometerRotationKey = 'accelerometer_rotation';
|
||||||
|
|
||||||
|
bool get initialized => _prefs != null;
|
||||||
|
|
||||||
Future<void> init({bool isRotationLocked = false}) async {
|
Future<void> init({bool isRotationLocked = false}) async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
_isRotationLocked = isRotationLocked;
|
_isRotationLocked = isRotationLocked;
|
||||||
|
@ -382,6 +385,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
// accessibility
|
// 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);
|
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
|
||||||
|
|
||||||
set timeToTakeAction(AccessibilityTimeout newValue) => setAndNotify(timeToTakeActionKey, newValue.toString());
|
set timeToTakeAction(AccessibilityTimeout newValue) => setAndNotify(timeToTakeActionKey, newValue.toString());
|
||||||
|
@ -538,6 +545,7 @@ class Settings extends ChangeNotifier {
|
||||||
case infoMapStyleKey:
|
case infoMapStyleKey:
|
||||||
case coordinateFormatKey:
|
case coordinateFormatKey:
|
||||||
case imageBackgroundKey:
|
case imageBackgroundKey:
|
||||||
|
case accessibilityAnimationsKey:
|
||||||
case timeToTakeActionKey:
|
case timeToTakeActionKey:
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
_prefs!.setString(key, value);
|
_prefs!.setString(key, value);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/services/common/services.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class AccessibilityService {
|
class AccessibilityService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/a11y');
|
static const platform = MethodChannel('deckers.thibault/aves/accessibility');
|
||||||
|
|
||||||
static Future<bool> hasRecommendedTimeouts() async {
|
static Future<bool> hasRecommendedTimeouts() async {
|
||||||
try {
|
try {
|
|
@ -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 {
|
class Durations {
|
||||||
// Flutter animations (with margin)
|
// Flutter animations (with margin)
|
||||||
|
@ -14,8 +17,8 @@ class Durations {
|
||||||
static const sweeperOpacityAnimation = Duration(milliseconds: 150);
|
static const sweeperOpacityAnimation = Duration(milliseconds: 150);
|
||||||
static const sweepingAnimation = Duration(milliseconds: 650);
|
static const sweepingAnimation = Duration(milliseconds: 650);
|
||||||
|
|
||||||
static const staggeredAnimation = Duration(milliseconds: 375);
|
// static const staggeredAnimation = Duration(milliseconds: 375);
|
||||||
static const staggeredAnimationPageTarget = Duration(milliseconds: 800);
|
// static const staggeredAnimationPageTarget = Duration(milliseconds: 800);
|
||||||
static const dialogFieldReachAnimation = Duration(milliseconds: 300);
|
static const dialogFieldReachAnimation = Duration(milliseconds: 300);
|
||||||
|
|
||||||
static const appBarTitleAnimation = Duration(milliseconds: 300);
|
static const appBarTitleAnimation = Duration(milliseconds: 300);
|
||||||
|
@ -64,7 +67,8 @@ class Durations {
|
||||||
static const highlightScrollInitDelay = Duration(milliseconds: 800);
|
static const highlightScrollInitDelay = Duration(milliseconds: 800);
|
||||||
static const videoOverlayHideDelay = Duration(milliseconds: 500);
|
static const videoOverlayHideDelay = Duration(milliseconds: 500);
|
||||||
static const videoProgressTimerInterval = Duration(milliseconds: 300);
|
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 doubleBackTimerDelay = Duration(milliseconds: 1000);
|
||||||
static const softKeyboardDisplayDelay = Duration(milliseconds: 300);
|
static const softKeyboardDisplayDelay = Duration(milliseconds: 300);
|
||||||
static const searchDebounceDelay = Duration(milliseconds: 250);
|
static const searchDebounceDelay = Duration(milliseconds: 250);
|
||||||
|
@ -75,3 +79,44 @@ class Durations {
|
||||||
// app life
|
// app life
|
||||||
static const lastVersionCheckInterval = Duration(days: 7);
|
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<Settings, DurationsData>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ class AIcons {
|
||||||
static const IconData video = Icons.movie_outlined;
|
static const IconData video = Icons.movie_outlined;
|
||||||
static const IconData vector = Icons.code_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 android = Icons.android;
|
||||||
static const IconData broken = Icons.broken_image_outlined;
|
static const IconData broken = Icons.broken_image_outlined;
|
||||||
static const IconData checked = Icons.done_outlined;
|
static const IconData checked = Icons.done_outlined;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/utils/debouncer.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/route_tracker.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:overlay_support/overlay_support.dart';
|
import 'package:overlay_support/overlay_support.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class AvesApp extends StatefulWidget {
|
class AvesApp extends StatefulWidget {
|
||||||
const AvesApp({Key? key}) : super(key: key);
|
const AvesApp({Key? key}) : super(key: key);
|
||||||
|
@ -31,7 +33,7 @@ class AvesApp extends StatefulWidget {
|
||||||
_AvesAppState createState() => _AvesAppState();
|
_AvesAppState createState() => _AvesAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AvesAppState extends State<AvesApp> {
|
class _AvesAppState extends State<AvesApp> with AccessibilityMixin {
|
||||||
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
|
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
|
||||||
late Future<void> _appSetup;
|
late Future<void> _appSetup;
|
||||||
final _mediaStoreSource = MediaStoreSource();
|
final _mediaStoreSource = MediaStoreSource();
|
||||||
|
@ -68,6 +70,7 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
value: appModeNotifier,
|
value: appModeNotifier,
|
||||||
child: Provider<CollectionSource>.value(
|
child: Provider<CollectionSource>.value(
|
||||||
value: _mediaStoreSource,
|
value: _mediaStoreSource,
|
||||||
|
child: DurationsProvider(
|
||||||
child: HighlightInfoProvider(
|
child: HighlightInfoProvider(
|
||||||
child: OverlaySupport(
|
child: OverlaySupport(
|
||||||
child: FutureBuilder<void>(
|
child: FutureBuilder<void>(
|
||||||
|
@ -79,13 +82,27 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
: Scaffold(
|
: Scaffold(
|
||||||
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
|
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
|
||||||
);
|
);
|
||||||
return Selector<Settings, Locale?>(
|
return Selector<Settings, Tuple2<Locale?, bool>>(
|
||||||
selector: (context, s) => s.locale,
|
selector: (context, s) => Tuple2(s.locale, s.initialized ? areAnimationsEnabled() : true),
|
||||||
builder: (context, settingsLocale, child) {
|
builder: (context, s, child) {
|
||||||
|
final settingsLocale = s.item1;
|
||||||
|
final areAnimationsEnabled = s.item2;
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
navigatorKey: _navigatorKey,
|
navigatorKey: _navigatorKey,
|
||||||
home: home,
|
home: home,
|
||||||
navigatorObservers: _navigatorObservers,
|
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,
|
onGenerateTitle: (context) => context.l10n.appName,
|
||||||
darkTheme: Themes.darkTheme,
|
darkTheme: Themes.darkTheme,
|
||||||
themeMode: ThemeMode.dark,
|
themeMode: ThemeMode.dark,
|
||||||
|
@ -97,8 +114,10 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
// checkerboardRasterCacheImages: true,
|
// checkerboardRasterCacheImages: true,
|
||||||
// checkerboardOffscreenLayers: true,
|
// checkerboardOffscreenLayers: true,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -86,7 +86,8 @@ class _CollectionGridContent extends StatelessWidget {
|
||||||
final columnCount = c.item2;
|
final columnCount = c.item2;
|
||||||
final tileSpacing = c.item3;
|
final tileSpacing = c.item3;
|
||||||
// do not listen for animation delay change
|
// do not listen for animation delay change
|
||||||
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
|
||||||
|
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
|
||||||
return GridTheme(
|
return GridTheme(
|
||||||
extent: tileExtent,
|
extent: tileExtent,
|
||||||
child: SectionedEntryListLayoutProvider(
|
child: SectionedEntryListLayoutProvider(
|
||||||
|
|
|
@ -173,7 +173,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
await Future.delayed(Durations.staggeredAnimationPageTarget);
|
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
|
||||||
|
await Future.delayed(delayDuration);
|
||||||
}
|
}
|
||||||
await Future.delayed(Durations.highlightScrollInitDelay);
|
await Future.delayed(Durations.highlightScrollInitDelay);
|
||||||
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/settings/enums.dart';
|
import 'package:aves/model/settings/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.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:aves/theme/durations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
17
lib/widgets/common/behaviour/accessibility_mixin.dart
Normal file
17
lib/widgets/common/behaviour/accessibility_mixin.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,17 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DirectPageTransitionsTheme extends PageTransitionsTheme {
|
||||||
|
@override
|
||||||
|
Widget buildTransitions<T>(
|
||||||
|
PageRoute<T> route,
|
||||||
|
BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
Widget child,
|
||||||
|
) =>
|
||||||
|
child;
|
||||||
|
}
|
||||||
|
|
||||||
class DirectMaterialPageRoute<T> extends PageRouteBuilder<T> {
|
class DirectMaterialPageRoute<T> extends PageRouteBuilder<T> {
|
||||||
DirectMaterialPageRoute({
|
DirectMaterialPageRoute({
|
||||||
RouteSettings? settings,
|
RouteSettings? settings,
|
||||||
|
|
|
@ -108,7 +108,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
) {
|
) {
|
||||||
if (sectionChildIndex == 0) {
|
if (sectionChildIndex == 0) {
|
||||||
final header = headerExtent > 0 ? buildHeader(context, sectionKey, headerExtent) : const SizedBox.shrink();
|
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--;
|
sectionChildIndex--;
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
final item = RepaintBoundary(
|
final item = RepaintBoundary(
|
||||||
child: tileBuilder(section[i]),
|
child: tileBuilder(section[i]),
|
||||||
);
|
);
|
||||||
children.add(animate ? _buildAnimation(itemGridIndex, item) : item);
|
children.add(animate ? _buildAnimation(context, itemGridIndex, item) : item);
|
||||||
}
|
}
|
||||||
return _GridRow(
|
return _GridRow(
|
||||||
width: tileWidth,
|
width: tileWidth,
|
||||||
|
@ -132,11 +132,12 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAnimation(int index, Widget child) {
|
Widget _buildAnimation(BuildContext context, int index, Widget child) {
|
||||||
|
final durations = context.watch<DurationsData>();
|
||||||
return AnimationConfiguration.staggeredGrid(
|
return AnimationConfiguration.staggeredGrid(
|
||||||
position: index,
|
position: index,
|
||||||
columnCount: columnCount,
|
columnCount: columnCount,
|
||||||
duration: Durations.staggeredAnimation,
|
duration: durations.staggeredAnimation,
|
||||||
delay: tileAnimationDelay,
|
delay: tileAnimationDelay,
|
||||||
child: SlideAnimation(
|
child: SlideAnimation(
|
||||||
verticalOffset: 50.0,
|
verticalOffset: 50.0,
|
||||||
|
|
|
@ -245,7 +245,8 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final columnCount = c.item2;
|
final columnCount = c.item2;
|
||||||
final tileSpacing = c.item3;
|
final tileSpacing = c.item3;
|
||||||
// do not listen for animation delay change
|
// do not listen for animation delay change
|
||||||
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
|
||||||
|
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
|
||||||
return Selector<MediaQueryData, double>(
|
return Selector<MediaQueryData, double>(
|
||||||
selector: (context, mq) => mq.textScaleFactor,
|
selector: (context, mq) => mq.textScaleFactor,
|
||||||
builder: (context, textScaleFactor, child) {
|
builder: (context, textScaleFactor, child) {
|
||||||
|
|
|
@ -2,7 +2,8 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/color_utils.dart';
|
import 'package:aves/utils/color_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.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:aves/widgets/settings/common/tile_leading.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
@ -18,13 +19,14 @@ class AccessibilitySection extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.a11y,
|
icon: AIcons.accessibility,
|
||||||
color: stringToColor('Accessibility'),
|
color: stringToColor('Accessibility'),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionAccessibility,
|
title: context.l10n.settingsSectionAccessibility,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
showHighlight: false,
|
showHighlight: false,
|
||||||
children: const [
|
children: const [
|
||||||
|
RemoveAnimationsTile(),
|
||||||
TimeToTakeActionTile(),
|
TimeToTakeActionTile(),
|
||||||
],
|
],
|
||||||
);
|
);
|
38
lib/widgets/settings/accessibility/remove_animations.dart
Normal file
38
lib/widgets/settings/accessibility/remove_animations.dart
Normal file
|
@ -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<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(context.l10n.settingsRemoveAnimationsTile),
|
||||||
|
subtitle: Text(currentAnimations.getName(context)),
|
||||||
|
onTap: () async {
|
||||||
|
final value = await showDialog<AccessibilityAnimations>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AvesSelectionDialog<AccessibilityAnimations>(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.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/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/basic/menu.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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/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/language/language.dart';
|
||||||
import 'package:aves/widgets/settings/navigation/navigation.dart';
|
import 'package:aves/widgets/settings/navigation/navigation.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/privacy.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/scheduler.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatefulWidget {
|
class SettingsPage extends StatefulWidget {
|
||||||
static const routeName = '/settings';
|
static const routeName = '/settings';
|
||||||
|
@ -38,6 +39,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final durations = context.watch<DurationsData>();
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -78,8 +80,8 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
children: AnimationConfiguration.toStaggeredList(
|
children: AnimationConfiguration.toStaggeredList(
|
||||||
duration: Durations.staggeredAnimation,
|
duration: durations.staggeredAnimation,
|
||||||
delay: Durations.staggeredAnimationDelay,
|
delay: durations.staggeredAnimationDelay * timeDilation,
|
||||||
childAnimationBuilder: (child) => SlideAnimation(
|
childAnimationBuilder: (child) => SlideAnimation(
|
||||||
verticalOffset: 50.0,
|
verticalOffset: 50.0,
|
||||||
child: FadeInAnimation(
|
child: FadeInAnimation(
|
||||||
|
|
|
@ -233,7 +233,8 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
),
|
),
|
||||||
(route) => false,
|
(route) => false,
|
||||||
));
|
));
|
||||||
await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay);
|
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
|
||||||
|
await Future.delayed(delayDuration + Durations.highlightScrollInitDelay);
|
||||||
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
||||||
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri));
|
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri));
|
||||||
if (targetEntry != null) {
|
if (targetEntry != null) {
|
||||||
|
|
|
@ -15,7 +15,9 @@ import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MetadataSectionSliver extends StatefulWidget {
|
class MetadataSectionSliver extends StatefulWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
|
@ -90,10 +92,11 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
|
||||||
if (metadata.isEmpty) {
|
if (metadata.isEmpty) {
|
||||||
content = const SizedBox.shrink();
|
content = const SizedBox.shrink();
|
||||||
} else {
|
} else {
|
||||||
|
final durations = context.watch<DurationsData>();
|
||||||
content = Column(
|
content = Column(
|
||||||
children: AnimationConfiguration.toStaggeredList(
|
children: AnimationConfiguration.toStaggeredList(
|
||||||
duration: Durations.staggeredAnimation,
|
duration: durations.staggeredAnimation,
|
||||||
delay: Durations.staggeredAnimationDelay,
|
delay: durations.staggeredAnimationDelay * timeDilation,
|
||||||
childAnimationBuilder: (child) => SlideAnimation(
|
childAnimationBuilder: (child) => SlideAnimation(
|
||||||
verticalOffset: 50.0,
|
verticalOffset: 50.0,
|
||||||
child: FadeInAnimation(
|
child: FadeInAnimation(
|
||||||
|
|
|
@ -116,7 +116,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
),
|
),
|
||||||
(route) => false,
|
(route) => false,
|
||||||
));
|
));
|
||||||
await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay);
|
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
|
||||||
|
await Future.delayed(delayDuration + Durations.highlightScrollInitDelay);
|
||||||
final newUri = newFields['uri'] as String?;
|
final newUri = newFields['uri'] as String?;
|
||||||
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => entry.uri == newUri);
|
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => entry.uri == newUri);
|
||||||
if (targetEntry != null) {
|
if (targetEntry != null) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
@ -44,11 +45,12 @@ class _WelcomePageState extends State<WelcomePage> {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||||
final terms = snapshot.data!;
|
final terms = snapshot.data!;
|
||||||
|
final durations = context.watch<DurationsData>();
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: _toStaggeredList(
|
children: _toStaggeredList(
|
||||||
duration: Durations.staggeredAnimation,
|
duration: durations.staggeredAnimation,
|
||||||
delay: Durations.staggeredAnimationDelay,
|
delay: durations.staggeredAnimationDelay * timeDilation,
|
||||||
childAnimationBuilder: (child) => SlideAnimation(
|
childAnimationBuilder: (child) => SlideAnimation(
|
||||||
verticalOffset: 50.0,
|
verticalOffset: 50.0,
|
||||||
child: FadeInAnimation(
|
child: FadeInAnimation(
|
||||||
|
|
Loading…
Reference in a new issue