add scale setting
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
import 'package:fading_scroll/fading_scroll.dart';
|
import 'package:fading_scroll/fading_scroll.dart';
|
||||||
import 'package:flutter/material.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:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
@@ -63,12 +64,89 @@ class _VisualSection extends StatelessWidget {
|
|||||||
return const Column(
|
return const Column(
|
||||||
children: [
|
children: [
|
||||||
SettingSection(name: 'Visual'),
|
SettingSection(name: 'Visual'),
|
||||||
|
_UiZoomSection(),
|
||||||
_ThemeSection(),
|
_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 {
|
class _TabBarSection extends StatelessWidget {
|
||||||
const _TabBarSection();
|
const _TabBarSection();
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ const _fallbackSearchProvider = BangKey(
|
|||||||
);
|
);
|
||||||
const _fallbackAutocompleteProvider = SearchSuggestionProviders.none;
|
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 TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
|
||||||
|
|
||||||
enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
|
enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
|
||||||
@@ -62,6 +67,7 @@ enum DeleteBrowsingDataType {
|
|||||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||||
class GeneralSettings with FastEquatable {
|
class GeneralSettings with FastEquatable {
|
||||||
final ThemeMode themeMode;
|
final ThemeMode themeMode;
|
||||||
|
final double uiScaleFactor;
|
||||||
final bool enableReadability;
|
final bool enableReadability;
|
||||||
final bool enforceReadability;
|
final bool enforceReadability;
|
||||||
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
||||||
@@ -110,6 +116,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
|
|
||||||
GeneralSettings({
|
GeneralSettings({
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
|
required this.uiScaleFactor,
|
||||||
required this.enableReadability,
|
required this.enableReadability,
|
||||||
required this.enforceReadability,
|
required this.enforceReadability,
|
||||||
required this.deleteBrowsingDataOnQuit,
|
required this.deleteBrowsingDataOnQuit,
|
||||||
@@ -157,6 +164,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
|
|
||||||
GeneralSettings.withDefaults({
|
GeneralSettings.withDefaults({
|
||||||
ThemeMode? themeMode,
|
ThemeMode? themeMode,
|
||||||
|
double? uiScaleFactor,
|
||||||
bool? enableReadability,
|
bool? enableReadability,
|
||||||
bool? enforceReadability,
|
bool? enforceReadability,
|
||||||
this.deleteBrowsingDataOnQuit,
|
this.deleteBrowsingDataOnQuit,
|
||||||
@@ -201,6 +209,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
bool? unshortenerEnabled,
|
bool? unshortenerEnabled,
|
||||||
String? unshortenerToken,
|
String? unshortenerToken,
|
||||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||||
|
uiScaleFactor = uiScaleFactor ?? defaultUiScaleFactor,
|
||||||
enableReadability = enableReadability ?? true,
|
enableReadability = enableReadability ?? true,
|
||||||
enforceReadability = enforceReadability ?? false,
|
enforceReadability = enforceReadability ?? false,
|
||||||
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
|
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
|
||||||
@@ -278,6 +287,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get hashParameters => [
|
List<Object?> get hashParameters => [
|
||||||
themeMode,
|
themeMode,
|
||||||
|
uiScaleFactor,
|
||||||
enableReadability,
|
enableReadability,
|
||||||
enforceReadability,
|
enforceReadability,
|
||||||
deleteBrowsingDataOnQuit,
|
deleteBrowsingDataOnQuit,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ part of 'general_settings.dart';
|
|||||||
abstract class _$GeneralSettingsCWProxy {
|
abstract class _$GeneralSettingsCWProxy {
|
||||||
GeneralSettings themeMode(ThemeMode themeMode);
|
GeneralSettings themeMode(ThemeMode themeMode);
|
||||||
|
|
||||||
|
GeneralSettings uiScaleFactor(double uiScaleFactor);
|
||||||
|
|
||||||
GeneralSettings enableReadability(bool enableReadability);
|
GeneralSettings enableReadability(bool enableReadability);
|
||||||
|
|
||||||
GeneralSettings enforceReadability(bool enforceReadability);
|
GeneralSettings enforceReadability(bool enforceReadability);
|
||||||
@@ -122,6 +124,7 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
/// ```
|
/// ```
|
||||||
GeneralSettings call({
|
GeneralSettings call({
|
||||||
ThemeMode themeMode,
|
ThemeMode themeMode,
|
||||||
|
double uiScaleFactor,
|
||||||
bool enableReadability,
|
bool enableReadability,
|
||||||
bool enforceReadability,
|
bool enforceReadability,
|
||||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||||
@@ -178,6 +181,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
@override
|
@override
|
||||||
GeneralSettings themeMode(ThemeMode themeMode) => call(themeMode: themeMode);
|
GeneralSettings themeMode(ThemeMode themeMode) => call(themeMode: themeMode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
GeneralSettings uiScaleFactor(double uiScaleFactor) =>
|
||||||
|
call(uiScaleFactor: uiScaleFactor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GeneralSettings enableReadability(bool enableReadability) =>
|
GeneralSettings enableReadability(bool enableReadability) =>
|
||||||
call(enableReadability: enableReadability);
|
call(enableReadability: enableReadability);
|
||||||
@@ -373,6 +380,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
/// ```
|
/// ```
|
||||||
GeneralSettings call({
|
GeneralSettings call({
|
||||||
Object? themeMode = const $CopyWithPlaceholder(),
|
Object? themeMode = const $CopyWithPlaceholder(),
|
||||||
|
Object? uiScaleFactor = 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(),
|
||||||
@@ -423,6 +431,11 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
? _value.themeMode
|
? _value.themeMode
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: themeMode as ThemeMode,
|
: themeMode as ThemeMode,
|
||||||
|
uiScaleFactor:
|
||||||
|
uiScaleFactor == const $CopyWithPlaceholder() || uiScaleFactor == null
|
||||||
|
? _value.uiScaleFactor
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: uiScaleFactor as double,
|
||||||
enableReadability:
|
enableReadability:
|
||||||
enableReadability == const $CopyWithPlaceholder() ||
|
enableReadability == const $CopyWithPlaceholder() ||
|
||||||
enableReadability == null
|
enableReadability == null
|
||||||
@@ -697,6 +710,7 @@ GeneralSettings _$GeneralSettingsFromJson(
|
|||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
) => GeneralSettings.withDefaults(
|
) => GeneralSettings.withDefaults(
|
||||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||||
|
uiScaleFactor: (json['uiScaleFactor'] as num?)?.toDouble(),
|
||||||
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>?)
|
||||||
@@ -784,6 +798,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
|||||||
GeneralSettings instance,
|
GeneralSettings instance,
|
||||||
) => <String, dynamic>{
|
) => <String, dynamic>{
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
|
'uiScaleFactor': instance.uiScaleFactor,
|
||||||
'enableReadability': instance.enableReadability,
|
'enableReadability': instance.enableReadability,
|
||||||
'enforceReadability': instance.enforceReadability,
|
'enforceReadability': instance.enforceReadability,
|
||||||
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
),
|
),
|
||||||
|
'uiScaleFactor': settings['uiScaleFactor']?.readAs(
|
||||||
|
DriftSqlType.double,
|
||||||
|
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'23b15af79f0f41771c20f7c0f7e94f93659ee1e8';
|
r'2098a12825b94a9b39d2e24f90a6afaab47fe3c7';
|
||||||
|
|
||||||
abstract class _$GeneralSettingsRepository
|
abstract class _$GeneralSettingsRepository
|
||||||
extends $StreamNotifier<GeneralSettings> {
|
extends $StreamNotifier<GeneralSettings> {
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ class _MainWidget extends HookConsumerWidget {
|
|||||||
final themeMode = ref.watch(
|
final themeMode = ref.watch(
|
||||||
generalSettingsWithDefaultsProvider.select((value) => value.themeMode),
|
generalSettingsWithDefaultsProvider.select((value) => value.themeMode),
|
||||||
);
|
);
|
||||||
|
final uiScaleFactor = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.uiScaleFactor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
useOnInitialization(() async {
|
useOnInitialization(() async {
|
||||||
await CountryCodes.init();
|
await CountryCodes.init();
|
||||||
@@ -255,6 +260,7 @@ class _MainWidget extends HookConsumerWidget {
|
|||||||
extensions: const <ThemeExtension<dynamic>>[AppColors.dark],
|
extensions: const <ThemeExtension<dynamic>>[AppColors.dark],
|
||||||
),
|
),
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
uiScaleFactor: uiScaleFactor,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,11 +30,13 @@ class MainApp extends HookConsumerWidget {
|
|||||||
final ThemeData? theme;
|
final ThemeData? theme;
|
||||||
final ThemeData? darkTheme;
|
final ThemeData? darkTheme;
|
||||||
final ThemeMode? themeMode;
|
final ThemeMode? themeMode;
|
||||||
|
final double uiScaleFactor;
|
||||||
|
|
||||||
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,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,6 +53,12 @@ class MainApp extends HookConsumerWidget {
|
|||||||
theme: theme,
|
theme: theme,
|
||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
builder: (context, child) {
|
||||||
|
return _AppUiScale(
|
||||||
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
child: child ?? const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
},
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -76,7 +84,12 @@ class MainApp extends HookConsumerWidget {
|
|||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
routerConfig: router.value,
|
routerConfig: router.value,
|
||||||
builder: (context, child) {
|
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,
|
theme: theme,
|
||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
builder: (context, child) {
|
||||||
|
return _AppUiScale(
|
||||||
|
uiScaleFactor: uiScaleFactor,
|
||||||
|
child: child ?? const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
},
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
appBar: AppBar(title: const Text('Initiallization Error')),
|
appBar: AppBar(title: const Text('Initiallization Error')),
|
||||||
body: Center(
|
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 {
|
class _SyncEventListener extends ConsumerWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user