introduced setting to request disable animations
This commit is contained in:
@@ -65,6 +65,7 @@ class _VisualSection extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
SettingSection(name: 'Visual'),
|
SettingSection(name: 'Visual'),
|
||||||
_UiZoomSection(),
|
_UiZoomSection(),
|
||||||
|
_DisableAnimationsTile(),
|
||||||
_ThemeSection(),
|
_ThemeSection(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -147,6 +148,32 @@ double _normalizeUiScale(double value) {
|
|||||||
return normalized.clamp(minUiScaleFactor, maxUiScaleFactor);
|
return normalized.clamp(minUiScaleFactor, maxUiScaleFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DisableAnimationsTile extends HookConsumerWidget {
|
||||||
|
const _DisableAnimationsTile();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final disableAnimations = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select((s) => s.disableAnimations),
|
||||||
|
);
|
||||||
|
|
||||||
|
return SwitchListTile.adaptive(
|
||||||
|
title: const Text('Disable Animations'),
|
||||||
|
subtitle: const Text('Reduce motion and turn off app animations'),
|
||||||
|
secondary: const Icon(Icons.animation),
|
||||||
|
value: disableAnimations,
|
||||||
|
onChanged: (value) async {
|
||||||
|
await ref
|
||||||
|
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||||
|
.save(
|
||||||
|
(currentSettings) =>
|
||||||
|
currentSettings.copyWith.disableAnimations(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _TabBarSection extends StatelessWidget {
|
class _TabBarSection extends StatelessWidget {
|
||||||
const _TabBarSection();
|
const _TabBarSection();
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ enum DeleteBrowsingDataType {
|
|||||||
class GeneralSettings with FastEquatable {
|
class GeneralSettings with FastEquatable {
|
||||||
final ThemeMode themeMode;
|
final ThemeMode themeMode;
|
||||||
final double uiScaleFactor;
|
final double uiScaleFactor;
|
||||||
|
final bool disableAnimations;
|
||||||
final bool enableReadability;
|
final bool enableReadability;
|
||||||
final bool enforceReadability;
|
final bool enforceReadability;
|
||||||
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
||||||
@@ -117,6 +118,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
GeneralSettings({
|
GeneralSettings({
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
required this.uiScaleFactor,
|
required this.uiScaleFactor,
|
||||||
|
required this.disableAnimations,
|
||||||
required this.enableReadability,
|
required this.enableReadability,
|
||||||
required this.enforceReadability,
|
required this.enforceReadability,
|
||||||
required this.deleteBrowsingDataOnQuit,
|
required this.deleteBrowsingDataOnQuit,
|
||||||
@@ -165,6 +167,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
GeneralSettings.withDefaults({
|
GeneralSettings.withDefaults({
|
||||||
ThemeMode? themeMode,
|
ThemeMode? themeMode,
|
||||||
double? uiScaleFactor,
|
double? uiScaleFactor,
|
||||||
|
bool? disableAnimations,
|
||||||
bool? enableReadability,
|
bool? enableReadability,
|
||||||
bool? enforceReadability,
|
bool? enforceReadability,
|
||||||
this.deleteBrowsingDataOnQuit,
|
this.deleteBrowsingDataOnQuit,
|
||||||
@@ -210,6 +213,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
String? unshortenerToken,
|
String? unshortenerToken,
|
||||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||||
uiScaleFactor = uiScaleFactor ?? defaultUiScaleFactor,
|
uiScaleFactor = uiScaleFactor ?? defaultUiScaleFactor,
|
||||||
|
disableAnimations = disableAnimations ?? false,
|
||||||
enableReadability = enableReadability ?? true,
|
enableReadability = enableReadability ?? true,
|
||||||
enforceReadability = enforceReadability ?? false,
|
enforceReadability = enforceReadability ?? false,
|
||||||
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
|
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
|
||||||
@@ -288,6 +292,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
themeMode,
|
themeMode,
|
||||||
uiScaleFactor,
|
uiScaleFactor,
|
||||||
|
disableAnimations,
|
||||||
enableReadability,
|
enableReadability,
|
||||||
enforceReadability,
|
enforceReadability,
|
||||||
deleteBrowsingDataOnQuit,
|
deleteBrowsingDataOnQuit,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
|
|
||||||
GeneralSettings uiScaleFactor(double uiScaleFactor);
|
GeneralSettings uiScaleFactor(double uiScaleFactor);
|
||||||
|
|
||||||
|
GeneralSettings disableAnimations(bool disableAnimations);
|
||||||
|
|
||||||
GeneralSettings enableReadability(bool enableReadability);
|
GeneralSettings enableReadability(bool enableReadability);
|
||||||
|
|
||||||
GeneralSettings enforceReadability(bool enforceReadability);
|
GeneralSettings enforceReadability(bool enforceReadability);
|
||||||
@@ -125,6 +127,7 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
GeneralSettings call({
|
GeneralSettings call({
|
||||||
ThemeMode themeMode,
|
ThemeMode themeMode,
|
||||||
double uiScaleFactor,
|
double uiScaleFactor,
|
||||||
|
bool disableAnimations,
|
||||||
bool enableReadability,
|
bool enableReadability,
|
||||||
bool enforceReadability,
|
bool enforceReadability,
|
||||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||||
@@ -185,6 +188,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
GeneralSettings uiScaleFactor(double uiScaleFactor) =>
|
GeneralSettings uiScaleFactor(double uiScaleFactor) =>
|
||||||
call(uiScaleFactor: uiScaleFactor);
|
call(uiScaleFactor: uiScaleFactor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
GeneralSettings disableAnimations(bool disableAnimations) =>
|
||||||
|
call(disableAnimations: disableAnimations);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GeneralSettings enableReadability(bool enableReadability) =>
|
GeneralSettings enableReadability(bool enableReadability) =>
|
||||||
call(enableReadability: enableReadability);
|
call(enableReadability: enableReadability);
|
||||||
@@ -381,6 +388,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
GeneralSettings call({
|
GeneralSettings call({
|
||||||
Object? themeMode = const $CopyWithPlaceholder(),
|
Object? themeMode = const $CopyWithPlaceholder(),
|
||||||
Object? uiScaleFactor = const $CopyWithPlaceholder(),
|
Object? uiScaleFactor = const $CopyWithPlaceholder(),
|
||||||
|
Object? disableAnimations = const $CopyWithPlaceholder(),
|
||||||
Object? enableReadability = const $CopyWithPlaceholder(),
|
Object? enableReadability = const $CopyWithPlaceholder(),
|
||||||
Object? enforceReadability = const $CopyWithPlaceholder(),
|
Object? enforceReadability = const $CopyWithPlaceholder(),
|
||||||
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
|
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
|
||||||
@@ -436,6 +444,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
? _value.uiScaleFactor
|
? _value.uiScaleFactor
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: uiScaleFactor as double,
|
: uiScaleFactor as double,
|
||||||
|
disableAnimations:
|
||||||
|
disableAnimations == const $CopyWithPlaceholder() ||
|
||||||
|
disableAnimations == null
|
||||||
|
? _value.disableAnimations
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: disableAnimations as bool,
|
||||||
enableReadability:
|
enableReadability:
|
||||||
enableReadability == const $CopyWithPlaceholder() ||
|
enableReadability == const $CopyWithPlaceholder() ||
|
||||||
enableReadability == null
|
enableReadability == null
|
||||||
@@ -711,6 +725,7 @@ GeneralSettings _$GeneralSettingsFromJson(
|
|||||||
) => GeneralSettings.withDefaults(
|
) => GeneralSettings.withDefaults(
|
||||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||||
uiScaleFactor: (json['uiScaleFactor'] as num?)?.toDouble(),
|
uiScaleFactor: (json['uiScaleFactor'] as num?)?.toDouble(),
|
||||||
|
disableAnimations: json['disableAnimations'] as bool?,
|
||||||
enableReadability: json['enableReadability'] as bool?,
|
enableReadability: json['enableReadability'] as bool?,
|
||||||
enforceReadability: json['enforceReadability'] as bool?,
|
enforceReadability: json['enforceReadability'] as bool?,
|
||||||
deleteBrowsingDataOnQuit: (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
deleteBrowsingDataOnQuit: (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
||||||
@@ -799,6 +814,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
|||||||
) => <String, dynamic>{
|
) => <String, dynamic>{
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'uiScaleFactor': instance.uiScaleFactor,
|
'uiScaleFactor': instance.uiScaleFactor,
|
||||||
|
'disableAnimations': instance.disableAnimations,
|
||||||
'enableReadability': instance.enableReadability,
|
'enableReadability': instance.enableReadability,
|
||||||
'enforceReadability': instance.enforceReadability,
|
'enforceReadability': instance.enforceReadability,
|
||||||
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
|||||||
DriftSqlType.double,
|
DriftSqlType.double,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
),
|
),
|
||||||
|
'disableAnimations': settings['disableAnimations']?.readAs(
|
||||||
|
DriftSqlType.bool,
|
||||||
|
db.typeMapping,
|
||||||
|
),
|
||||||
'enableReadability': settings['enableReadability']?.readAs(
|
'enableReadability': settings['enableReadability']?.readAs(
|
||||||
DriftSqlType.bool,
|
DriftSqlType.bool,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$generalSettingsRepositoryHash() =>
|
String _$generalSettingsRepositoryHash() =>
|
||||||
r'2098a12825b94a9b39d2e24f90a6afaab47fe3c7';
|
r'12e49ad38ff25c6bad1680b3eb3a01274ebfb35f';
|
||||||
|
|
||||||
abstract class _$GeneralSettingsRepository
|
abstract class _$GeneralSettingsRepository
|
||||||
extends $StreamNotifier<GeneralSettings> {
|
extends $StreamNotifier<GeneralSettings> {
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ class _MainWidget extends HookConsumerWidget {
|
|||||||
(value) => value.uiScaleFactor,
|
(value) => value.uiScaleFactor,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
final disableAnimations = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.disableAnimations,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
useOnInitialization(() async {
|
useOnInitialization(() async {
|
||||||
await CountryCodes.init();
|
await CountryCodes.init();
|
||||||
@@ -261,6 +266,7 @@ class _MainWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
uiScaleFactor: uiScaleFactor,
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
disableAnimations: disableAnimations,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,12 +31,14 @@ class MainApp extends HookConsumerWidget {
|
|||||||
final ThemeData? darkTheme;
|
final ThemeData? darkTheme;
|
||||||
final ThemeMode? themeMode;
|
final ThemeMode? themeMode;
|
||||||
final double uiScaleFactor;
|
final double uiScaleFactor;
|
||||||
|
final bool disableAnimations;
|
||||||
|
|
||||||
const MainApp({
|
const MainApp({
|
||||||
required this.theme,
|
required this.theme,
|
||||||
required this.darkTheme,
|
required this.darkTheme,
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
required this.uiScaleFactor,
|
required this.uiScaleFactor,
|
||||||
|
required this.disableAnimations,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,8 +56,9 @@ class MainApp extends HookConsumerWidget {
|
|||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return _AppUiScale(
|
return _AppMediaQueryOverrides(
|
||||||
uiScaleFactor: uiScaleFactor,
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
disableAnimations: disableAnimations,
|
||||||
child: child ?? const SizedBox.shrink(),
|
child: child ?? const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -84,8 +87,9 @@ class MainApp extends HookConsumerWidget {
|
|||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
routerConfig: router.value,
|
routerConfig: router.value,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return _AppUiScale(
|
return _AppMediaQueryOverrides(
|
||||||
uiScaleFactor: uiScaleFactor,
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
disableAnimations: disableAnimations,
|
||||||
child: _SyncEventListener(
|
child: _SyncEventListener(
|
||||||
child: child ?? const SizedBox.shrink(),
|
child: child ?? const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
@@ -100,8 +104,9 @@ class MainApp extends HookConsumerWidget {
|
|||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return _AppUiScale(
|
return _AppMediaQueryOverrides(
|
||||||
uiScaleFactor: uiScaleFactor,
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
disableAnimations: disableAnimations,
|
||||||
child: child ?? const SizedBox.shrink(),
|
child: child ?? const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -125,29 +130,54 @@ class MainApp extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppUiScale extends StatelessWidget {
|
MediaQueryData applyAppMediaQueryOverrides({
|
||||||
|
required MediaQueryData mediaQuery,
|
||||||
|
required double uiScaleFactor,
|
||||||
|
required bool disableAnimations,
|
||||||
|
}) {
|
||||||
|
final textScaler = uiScaleFactor == 1.0
|
||||||
|
? mediaQuery.textScaler
|
||||||
|
: _AppTextScaler(
|
||||||
|
baseTextScaler: mediaQuery.textScaler,
|
||||||
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (disableAnimations) {
|
||||||
|
return mediaQuery.copyWith(textScaler: textScaler, disableAnimations: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiScaleFactor == 1.0) {
|
||||||
|
return mediaQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaQuery.copyWith(textScaler: textScaler);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppMediaQueryOverrides extends StatelessWidget {
|
||||||
final double uiScaleFactor;
|
final double uiScaleFactor;
|
||||||
|
final bool disableAnimations;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const _AppUiScale({required this.uiScaleFactor, required this.child});
|
const _AppMediaQueryOverrides({
|
||||||
|
required this.uiScaleFactor,
|
||||||
|
required this.disableAnimations,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (uiScaleFactor == 1.0) {
|
if (uiScaleFactor == 1.0 && !disableAnimations) {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final overriddenMediaQuery = applyAppMediaQueryOverrides(
|
||||||
return MediaQuery(
|
mediaQuery: mediaQuery,
|
||||||
data: mediaQuery.copyWith(
|
uiScaleFactor: uiScaleFactor,
|
||||||
textScaler: _AppTextScaler(
|
disableAnimations: disableAnimations,
|
||||||
baseTextScaler: mediaQuery.textScaler,
|
|
||||||
uiScaleFactor: uiScaleFactor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return MediaQuery(data: overriddenMediaQuery, child: child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:weblibre/presentation/main_app.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('applyAppMediaQueryOverrides', () {
|
||||||
|
test('returns the original media query when no overrides are enabled', () {
|
||||||
|
const mediaQuery = MediaQueryData();
|
||||||
|
|
||||||
|
final result = applyAppMediaQueryOverrides(
|
||||||
|
mediaQuery: mediaQuery,
|
||||||
|
uiScaleFactor: 1.0,
|
||||||
|
disableAnimations: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, same(mediaQuery));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'preserves the system animation preference when app override is off',
|
||||||
|
() {
|
||||||
|
const mediaQuery = MediaQueryData(disableAnimations: true);
|
||||||
|
|
||||||
|
final result = applyAppMediaQueryOverrides(
|
||||||
|
mediaQuery: mediaQuery,
|
||||||
|
uiScaleFactor: 1.0,
|
||||||
|
disableAnimations: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.disableAnimations, isTrue);
|
||||||
|
expect(result, same(mediaQuery));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('forces animations off when the app setting is enabled', () {
|
||||||
|
const mediaQuery = MediaQueryData(disableAnimations: false);
|
||||||
|
|
||||||
|
final result = applyAppMediaQueryOverrides(
|
||||||
|
mediaQuery: mediaQuery,
|
||||||
|
uiScaleFactor: 1.0,
|
||||||
|
disableAnimations: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.disableAnimations, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('applies ui scale on top of the base text scaler', () {
|
||||||
|
const mediaQuery = MediaQueryData();
|
||||||
|
|
||||||
|
final result = applyAppMediaQueryOverrides(
|
||||||
|
mediaQuery: mediaQuery,
|
||||||
|
uiScaleFactor: 1.25,
|
||||||
|
disableAnimations: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.textScaler.scale(20), 25);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user