/* * 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 . */ 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( 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), ); }, ); } }