add scale setting
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
@@ -63,12 +64,89 @@ class _VisualSection extends StatelessWidget {
|
||||
return const Column(
|
||||
children: [
|
||||
SettingSection(name: 'Visual'),
|
||||
_UiZoomSection(),
|
||||
_ThemeSection(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UiZoomSection extends HookConsumerWidget {
|
||||
const _UiZoomSection();
|
||||
|
||||
static final _sliderDivisions =
|
||||
((maxUiScaleFactor - minUiScaleFactor) / uiScaleFactorStep).round();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final uiScaleFactor = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select((s) => s.uiScaleFactor),
|
||||
);
|
||||
final sliderValue = useState(uiScaleFactor);
|
||||
|
||||
useEffect(() {
|
||||
sliderValue.value = uiScaleFactor;
|
||||
return null;
|
||||
}, [uiScaleFactor]);
|
||||
|
||||
final sliderLabel = '${(sliderValue.value * 100).round()}%';
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('User Interface Zoom'),
|
||||
subtitle: Text('Make the user interface smaller or larger'),
|
||||
leading: Icon(Icons.zoom_in),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(sliderLabel, style: Theme.of(context).textTheme.titleLarge),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
min: minUiScaleFactor,
|
||||
max: maxUiScaleFactor,
|
||||
divisions: _sliderDivisions,
|
||||
label: sliderLabel,
|
||||
value: sliderValue.value.clamp(
|
||||
minUiScaleFactor,
|
||||
maxUiScaleFactor,
|
||||
),
|
||||
onChanged: (value) {
|
||||
sliderValue.value = value;
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
final normalized = _normalizeUiScale(value);
|
||||
sliderValue.value = normalized;
|
||||
await ref
|
||||
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.uiScaleFactor(normalized),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double _normalizeUiScale(double value) {
|
||||
final clampedValue = value.clamp(minUiScaleFactor, maxUiScaleFactor);
|
||||
final stepIndex = ((clampedValue - minUiScaleFactor) / uiScaleFactorStep)
|
||||
.round();
|
||||
final normalized = minUiScaleFactor + (stepIndex * uiScaleFactorStep);
|
||||
return normalized.clamp(minUiScaleFactor, maxUiScaleFactor);
|
||||
}
|
||||
|
||||
class _TabBarSection extends StatelessWidget {
|
||||
const _TabBarSection();
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ const _fallbackSearchProvider = BangKey(
|
||||
);
|
||||
const _fallbackAutocompleteProvider = SearchSuggestionProviders.none;
|
||||
|
||||
const defaultUiScaleFactor = 1.0;
|
||||
const minUiScaleFactor = 0.5;
|
||||
const maxUiScaleFactor = 1.5;
|
||||
const uiScaleFactorStep = 0.05;
|
||||
|
||||
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
|
||||
|
||||
enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
|
||||
@@ -62,6 +67,7 @@ enum DeleteBrowsingDataType {
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class GeneralSettings with FastEquatable {
|
||||
final ThemeMode themeMode;
|
||||
final double uiScaleFactor;
|
||||
final bool enableReadability;
|
||||
final bool enforceReadability;
|
||||
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
||||
@@ -110,6 +116,7 @@ class GeneralSettings with FastEquatable {
|
||||
|
||||
GeneralSettings({
|
||||
required this.themeMode,
|
||||
required this.uiScaleFactor,
|
||||
required this.enableReadability,
|
||||
required this.enforceReadability,
|
||||
required this.deleteBrowsingDataOnQuit,
|
||||
@@ -157,6 +164,7 @@ class GeneralSettings with FastEquatable {
|
||||
|
||||
GeneralSettings.withDefaults({
|
||||
ThemeMode? themeMode,
|
||||
double? uiScaleFactor,
|
||||
bool? enableReadability,
|
||||
bool? enforceReadability,
|
||||
this.deleteBrowsingDataOnQuit,
|
||||
@@ -201,6 +209,7 @@ class GeneralSettings with FastEquatable {
|
||||
bool? unshortenerEnabled,
|
||||
String? unshortenerToken,
|
||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||
uiScaleFactor = uiScaleFactor ?? defaultUiScaleFactor,
|
||||
enableReadability = enableReadability ?? true,
|
||||
enforceReadability = enforceReadability ?? false,
|
||||
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
|
||||
@@ -278,6 +287,7 @@ class GeneralSettings with FastEquatable {
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
themeMode,
|
||||
uiScaleFactor,
|
||||
enableReadability,
|
||||
enforceReadability,
|
||||
deleteBrowsingDataOnQuit,
|
||||
|
||||
@@ -9,6 +9,8 @@ part of 'general_settings.dart';
|
||||
abstract class _$GeneralSettingsCWProxy {
|
||||
GeneralSettings themeMode(ThemeMode themeMode);
|
||||
|
||||
GeneralSettings uiScaleFactor(double uiScaleFactor);
|
||||
|
||||
GeneralSettings enableReadability(bool enableReadability);
|
||||
|
||||
GeneralSettings enforceReadability(bool enforceReadability);
|
||||
@@ -122,6 +124,7 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
/// ```
|
||||
GeneralSettings call({
|
||||
ThemeMode themeMode,
|
||||
double uiScaleFactor,
|
||||
bool enableReadability,
|
||||
bool enforceReadability,
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||
@@ -178,6 +181,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
@override
|
||||
GeneralSettings themeMode(ThemeMode themeMode) => call(themeMode: themeMode);
|
||||
|
||||
@override
|
||||
GeneralSettings uiScaleFactor(double uiScaleFactor) =>
|
||||
call(uiScaleFactor: uiScaleFactor);
|
||||
|
||||
@override
|
||||
GeneralSettings enableReadability(bool enableReadability) =>
|
||||
call(enableReadability: enableReadability);
|
||||
@@ -373,6 +380,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
/// ```
|
||||
GeneralSettings call({
|
||||
Object? themeMode = const $CopyWithPlaceholder(),
|
||||
Object? uiScaleFactor = const $CopyWithPlaceholder(),
|
||||
Object? enableReadability = const $CopyWithPlaceholder(),
|
||||
Object? enforceReadability = const $CopyWithPlaceholder(),
|
||||
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
|
||||
@@ -423,6 +431,11 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
? _value.themeMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: themeMode as ThemeMode,
|
||||
uiScaleFactor:
|
||||
uiScaleFactor == const $CopyWithPlaceholder() || uiScaleFactor == null
|
||||
? _value.uiScaleFactor
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: uiScaleFactor as double,
|
||||
enableReadability:
|
||||
enableReadability == const $CopyWithPlaceholder() ||
|
||||
enableReadability == null
|
||||
@@ -697,6 +710,7 @@ GeneralSettings _$GeneralSettingsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GeneralSettings.withDefaults(
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||
uiScaleFactor: (json['uiScaleFactor'] as num?)?.toDouble(),
|
||||
enableReadability: json['enableReadability'] as bool?,
|
||||
enforceReadability: json['enforceReadability'] as bool?,
|
||||
deleteBrowsingDataOnQuit: (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
||||
@@ -784,6 +798,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
||||
GeneralSettings instance,
|
||||
) => <String, dynamic>{
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'uiScaleFactor': instance.uiScaleFactor,
|
||||
'enableReadability': instance.enableReadability,
|
||||
'enforceReadability': instance.enforceReadability,
|
||||
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
||||
|
||||
@@ -48,6 +48,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
||||
DriftSqlType.string,
|
||||
db.typeMapping,
|
||||
),
|
||||
'uiScaleFactor': settings['uiScaleFactor']?.readAs(
|
||||
DriftSqlType.double,
|
||||
db.typeMapping,
|
||||
),
|
||||
'enableReadability': settings['enableReadability']?.readAs(
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
|
||||
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
|
||||
}
|
||||
|
||||
String _$generalSettingsRepositoryHash() =>
|
||||
r'23b15af79f0f41771c20f7c0f7e94f93659ee1e8';
|
||||
r'2098a12825b94a9b39d2e24f90a6afaab47fe3c7';
|
||||
|
||||
abstract class _$GeneralSettingsRepository
|
||||
extends $StreamNotifier<GeneralSettings> {
|
||||
|
||||
@@ -128,6 +128,11 @@ class _MainWidget extends HookConsumerWidget {
|
||||
final themeMode = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select((value) => value.themeMode),
|
||||
);
|
||||
final uiScaleFactor = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.uiScaleFactor,
|
||||
),
|
||||
);
|
||||
|
||||
useOnInitialization(() async {
|
||||
await CountryCodes.init();
|
||||
@@ -255,6 +260,7 @@ class _MainWidget extends HookConsumerWidget {
|
||||
extensions: const <ThemeExtension<dynamic>>[AppColors.dark],
|
||||
),
|
||||
themeMode: themeMode,
|
||||
uiScaleFactor: uiScaleFactor,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -30,11 +30,13 @@ class MainApp extends HookConsumerWidget {
|
||||
final ThemeData? theme;
|
||||
final ThemeData? darkTheme;
|
||||
final ThemeMode? themeMode;
|
||||
final double uiScaleFactor;
|
||||
|
||||
const MainApp({
|
||||
required this.theme,
|
||||
required this.darkTheme,
|
||||
required this.themeMode,
|
||||
required this.uiScaleFactor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -51,6 +53,12 @@ class MainApp extends HookConsumerWidget {
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
builder: (context, child) {
|
||||
return _AppUiScale(
|
||||
uiScaleFactor: uiScaleFactor,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
@@ -76,7 +84,12 @@ class MainApp extends HookConsumerWidget {
|
||||
themeMode: themeMode,
|
||||
routerConfig: router.value,
|
||||
builder: (context, child) {
|
||||
return _SyncEventListener(child: child ?? const SizedBox.shrink());
|
||||
return _AppUiScale(
|
||||
uiScaleFactor: uiScaleFactor,
|
||||
child: _SyncEventListener(
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -86,6 +99,12 @@ class MainApp extends HookConsumerWidget {
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
builder: (context, child) {
|
||||
return _AppUiScale(
|
||||
uiScaleFactor: uiScaleFactor,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('Initiallization Error')),
|
||||
body: Center(
|
||||
@@ -106,6 +125,63 @@ class MainApp extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AppUiScale extends StatelessWidget {
|
||||
final double uiScaleFactor;
|
||||
final Widget child;
|
||||
|
||||
const _AppUiScale({required this.uiScaleFactor, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (uiScaleFactor == 1.0) {
|
||||
return child;
|
||||
}
|
||||
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
|
||||
return MediaQuery(
|
||||
data: mediaQuery.copyWith(
|
||||
textScaler: _AppTextScaler(
|
||||
baseTextScaler: mediaQuery.textScaler,
|
||||
uiScaleFactor: uiScaleFactor,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppTextScaler extends TextScaler {
|
||||
final TextScaler baseTextScaler;
|
||||
final double uiScaleFactor;
|
||||
|
||||
const _AppTextScaler({
|
||||
required this.baseTextScaler,
|
||||
required this.uiScaleFactor,
|
||||
}) : assert(uiScaleFactor > 0);
|
||||
|
||||
@override
|
||||
double scale(double fontSize) =>
|
||||
baseTextScaler.scale(fontSize) * uiScaleFactor;
|
||||
|
||||
@override
|
||||
double get textScaleFactor => scale(1.0);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is _AppTextScaler &&
|
||||
baseTextScaler == other.baseTextScaler &&
|
||||
uiScaleFactor == other.uiScaleFactor;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(baseTextScaler, uiScaleFactor);
|
||||
}
|
||||
|
||||
class _SyncEventListener extends ConsumerWidget {
|
||||
final Widget child;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user