Files
MrbWebLibre/app/lib/features/settings/presentation/screens/appearance_display_settings.dart
T
2026-01-13 18:08:23 +01:00

433 lines
13 KiB
Dart

/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/settings/presentation/widgets/sections.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
class AppearanceDisplaySettingsScreen extends StatelessWidget {
const AppearanceDisplaySettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Appearance & Display')),
body: SafeArea(
child: FadingScroll(
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 12.0),
children: const [
_VisualSection(),
_TabBarLayoutSection(),
_GesturesSection(),
],
);
},
),
),
);
}
}
class _VisualSection extends StatelessWidget {
const _VisualSection();
@override
Widget build(BuildContext context) {
return const Column(
children: [
SettingSection(name: 'Visual'),
_ThemeSection(),
],
);
}
}
class _TabBarLayoutSection extends StatelessWidget {
const _TabBarLayoutSection();
@override
Widget build(BuildContext context) {
return const Column(
children: [
SettingSection(name: 'Tab Bar Layout'),
_TabBarPositionSection(),
_ShowContextualTabBarTile(),
_AutoHideTabBarTile(),
_BottomSheetTabViewTile(),
_ShowQuickTabSwitcherBarTile(),
_QuickTabSwitcherModeSection(),
],
);
}
}
class _GesturesSection extends StatelessWidget {
const _GesturesSection();
@override
Widget build(BuildContext context) {
return const Column(
children: [
SettingSection(name: 'Gestures'),
_PullToRefreshTile(),
_DoubleBackCloseTabTile(),
],
);
}
}
class _ThemeSection extends HookConsumerWidget {
const _ThemeSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.themeMode),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Theme'),
leading: Icon(Icons.palette),
contentPadding: EdgeInsets.zero,
),
Center(
child: SegmentedButton<ThemeMode>(
segments: const [
ButtonSegment(
value: ThemeMode.system,
icon: Icon(Icons.brightness_auto),
label: Text('System'),
),
ButtonSegment(
value: ThemeMode.light,
icon: Icon(Icons.light_mode),
label: Text('Light'),
),
ButtonSegment(
value: ThemeMode.dark,
icon: Icon(Icons.dark_mode),
label: Text('Dark'),
),
],
selected: {themeMode},
onSelectionChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.themeMode(value.first),
);
},
),
),
],
),
);
}
}
class _TabBarPositionSection extends HookConsumerWidget {
const _TabBarPositionSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabBarPosition = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.tabBarPosition),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Tab Bar Position'),
leading: Icon(MdiIcons.dockWindow),
contentPadding: EdgeInsets.zero,
),
RadioGroup(
groupValue: tabBarPosition,
onChanged: (value) async {
if (value != null) {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.tabBarPosition(value),
);
}
},
child: const Column(
children: [
RadioListTile.adaptive(
value: TabBarPosition.top,
title: Text('Top'),
subtitle: Text('Persistent tab bar without auto-hide'),
),
RadioListTile.adaptive(
value: TabBarPosition.bottom,
title: Text('Bottom'),
subtitle: Text('Tab bar with auto-hide support'),
),
],
),
),
],
),
);
}
}
class _ShowContextualTabBarTile extends HookConsumerWidget {
const _ShowContextualTabBarTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabBarShowContextualBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
(s) => s.tabBarShowContextualBar,
),
);
return SwitchListTile.adaptive(
title: const Text('Show Contextual Tab Bar'),
subtitle: const Text(
'Show additional bottom toolbar for navigation and actions',
),
secondary: const Icon(MdiIcons.dockBottom),
value: tabBarShowContextualBar,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.tabBarShowContextualBar(value),
);
},
);
}
}
class _ShowQuickTabSwitcherBarTile extends HookConsumerWidget {
const _ShowQuickTabSwitcherBarTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabBarShowQuickTabSwitcherBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
(s) => s.tabBarShowQuickTabSwitcherBar,
),
);
return SwitchListTile.adaptive(
title: const Text('Show Quick Tab Switcher Bar'),
subtitle: const Text(
'Show additional toolbar to quickly switch to recently used tabs',
),
secondary: const Icon(MdiIcons.dockBottom),
value: tabBarShowQuickTabSwitcherBar,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.tabBarShowQuickTabSwitcherBar(value),
);
},
);
}
}
class _QuickTabSwitcherModeSection extends HookConsumerWidget {
const _QuickTabSwitcherModeSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final quickTabSwitcherMode = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.quickTabSwitcherMode),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Quick Tab Switcher Mode'),
leading: Icon(MdiIcons.folderSettings),
contentPadding: EdgeInsets.zero,
),
RadioGroup(
groupValue: quickTabSwitcherMode,
onChanged: (value) async {
if (value != null) {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.quickTabSwitcherMode(value),
);
}
},
child: const Column(
children: [
RadioListTile.adaptive(
value: QuickTabSwitcherMode.lastUsedTabs,
title: Text('Recently Used Tabs'),
subtitle: Text('Recently used tabs across all containers'),
),
RadioListTile.adaptive(
value: QuickTabSwitcherMode.containerTabs,
title: Text('Container Tabs'),
subtitle: Text('Ordered tabs of the selected container'),
),
],
),
),
],
),
);
}
}
class _AutoHideTabBarTile extends HookConsumerWidget {
const _AutoHideTabBarTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final autoHideTabBar = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.autoHideTabBar),
);
return SwitchListTile.adaptive(
title: const Text('Auto Hide Tab Bar'),
subtitle: const Text('Hide tab bar when scrolling'),
secondary: const Icon(MdiIcons.folderHidden),
value: autoHideTabBar,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.autoHideTabBar(value),
);
},
);
}
}
class _BottomSheetTabViewTile extends HookConsumerWidget {
const _BottomSheetTabViewTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabViewBottomSheet = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.tabViewBottomSheet),
);
return SwitchListTile.adaptive(
title: const Text('Bottom Sheet Tab View'),
subtitle: const Text(
'Display tabs in a bottom sheet instead of fullscreen',
),
secondary: const Icon(MdiIcons.dockBottom),
value: tabViewBottomSheet,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.tabViewBottomSheet(value),
);
},
);
}
}
class _PullToRefreshTile extends HookConsumerWidget {
const _PullToRefreshTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final pullToRefreshEnabled = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.pullToRefreshEnabled),
);
return SwitchListTile.adaptive(
title: const Text('Pull to Refresh'),
subtitle: const Text('Swipe down on pages to reload them'),
secondary: const Icon(MdiIcons.gestureSwipeDown),
value: pullToRefreshEnabled,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.pullToRefreshEnabled(value),
);
},
);
}
}
class _DoubleBackCloseTabTile extends HookConsumerWidget {
const _DoubleBackCloseTabTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final doubleBackCloseTab = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.doubleBackCloseTab),
);
return SwitchListTile.adaptive(
title: const Text('Double Back to Close Tab'),
subtitle: const Text(
'When enabled, press back twice to close the tab. When disabled, back button only navigates page history.',
),
secondary: const Icon(MdiIcons.gestureDoubleTap),
value: doubleBackCloseTab,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.doubleBackCloseTab(value),
);
},
);
}
}