extract SettingsCheckboxTile for settings toggles

This commit is contained in:
2026-07-12 23:41:12 +02:00
parent dfce3e7b5c
commit 4aa31a2e44
5 changed files with 133 additions and 140 deletions
@@ -6,13 +6,13 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import '../../../../routing/app_routes.dart'; import '../../../../routing/app_routes.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart'; import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../storage/settings.dart' as model; import '../../../../storage/settings.dart' as model;
import '../../../../utils/haptics.dart';
import '../../../../widget/centered_leading.dart'; import '../../../../widget/centered_leading.dart';
import '../../../../widget/confirm_dialog.dart'; import '../../../../widget/confirm_dialog.dart';
import '../../../../widget/debug/cache_view.dart'; import '../../../../widget/debug/cache_view.dart';
import '../../../../widget/debug/json_viewer.dart'; import '../../../../widget/debug/json_viewer.dart';
import '../../../../widget/details_bottom_sheet.dart'; import '../../../../widget/details_bottom_sheet.dart';
import '../widgets/endpoint_picker.dart'; import '../widgets/endpoint_picker.dart';
import '../widgets/settings_checkbox_tile.dart';
class DevToolsSection extends StatefulWidget { class DevToolsSection extends StatefulWidget {
final SettingsCubit settings; final SettingsCubit settings;
@@ -41,49 +41,32 @@ class _DevToolsSectionState extends State<DevToolsSection> {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.auto_graph_outlined), icon: Icons.auto_graph_outlined,
title: const Text('Performance graph'), title: 'Performance graph',
trailing: Checkbox( value: dev.showPerformanceOverlay,
value: dev.showPerformanceOverlay, onChanged: (e) => widget.settings
onChanged: (e) { .val(write: true)
Haptics.selection(); .devToolsSettings
widget.settings .showPerformanceOverlay = e,
.val(write: true)
.devToolsSettings
.showPerformanceOverlay = e!;
},
),
), ),
ListTile( SettingsCheckboxTile(
leading: const Icon( icon: Icons.screen_search_desktop_outlined,
Icons.screen_search_desktop_outlined, title: 'Indicate offscreen layers',
), value: dev.checkerboardOffscreenLayers,
title: const Text('Indicate offscreen layers'), onChanged: (e) => widget.settings
trailing: Checkbox( .val(write: true)
value: dev.checkerboardOffscreenLayers, .devToolsSettings
onChanged: (e) { .checkerboardOffscreenLayers = e,
Haptics.selection();
widget.settings
.val(write: true)
.devToolsSettings
.checkerboardOffscreenLayers = e!;
},
),
), ),
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.imagesearch_roller_outlined), icon: Icons.imagesearch_roller_outlined,
title: const Text('Indicate raster cache images'), title: 'Indicate raster cache images',
trailing: Checkbox( value: dev.checkerboardRasterCacheImages,
value: dev.checkerboardRasterCacheImages, onChanged: (e) => widget.settings
onChanged: (e) { .val(write: true)
Haptics.selection(); .devToolsSettings
widget.settings .checkerboardRasterCacheImages = e,
.val(write: true)
.devToolsSettings
.checkerboardRasterCacheImages = e!;
},
),
), ),
], ],
); );
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart'; import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../utils/haptics.dart'; import '../widgets/settings_checkbox_tile.dart';
class FilesSection extends StatelessWidget { class FilesSection extends StatelessWidget {
const FilesSection({super.key}); const FilesSection({super.key});
@@ -12,30 +12,20 @@ class FilesSection extends StatelessWidget {
final settings = context.watch<SettingsCubit>(); final settings = context.watch<SettingsCubit>();
return Column( return Column(
children: [ children: [
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.drive_folder_upload_outlined), icon: Icons.drive_folder_upload_outlined,
title: const Text('Ordner in Dateien nach oben sortieren'), title: 'Ordner in Dateien nach oben sortieren',
trailing: Checkbox( value: settings.val().fileSettings.sortFoldersToTop,
value: settings.val().fileSettings.sortFoldersToTop, onChanged: (e) =>
onChanged: (e) { settings.val(write: true).fileSettings.sortFoldersToTop = e,
Haptics.selection();
settings.val(write: true).fileSettings.sortFoldersToTop = e!;
},
),
), ),
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.open_in_new_outlined), icon: Icons.open_in_new_outlined,
title: const Text('Dateien immer mit Systemdialog öffnen'), title: 'Dateien immer mit Systemdialog öffnen',
trailing: Checkbox( value: settings.val().fileViewSettings.alwaysOpenExternally,
value: settings.val().fileViewSettings.alwaysOpenExternally, onChanged: (e) =>
onChanged: (e) { settings.val(write: true).fileViewSettings.alwaysOpenExternally =
Haptics.selection(); e,
settings
.val(write: true)
.fileViewSettings
.alwaysOpenExternally = e!;
},
),
), ),
], ],
); );
@@ -6,9 +6,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../push/push_registration.dart'; import '../../../../push/push_registration.dart';
import '../../../../routing/app_routes.dart'; import '../../../../routing/app_routes.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart'; import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../utils/haptics.dart';
import '../../../../widget/centered_leading.dart'; import '../../../../widget/centered_leading.dart';
import '../widgets/push_status_sheet.dart'; import '../widgets/push_status_sheet.dart';
import '../widgets/settings_checkbox_tile.dart';
class TalkSection extends StatelessWidget { class TalkSection extends StatelessWidget {
const TalkSection({super.key}); const TalkSection({super.key});
@@ -20,27 +20,19 @@ class TalkSection extends StatelessWidget {
final notificationSettings = settings.val().notificationSettings; final notificationSettings = settings.val().notificationSettings;
return Column( return Column(
children: [ children: [
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.star_border), icon: Icons.star_border,
title: const Text('Favoriten im Talk nach oben sortieren'), title: 'Favoriten im Talk nach oben sortieren',
trailing: Checkbox( value: talkSettings.sortFavoritesToTop,
value: talkSettings.sortFavoritesToTop, onChanged: (e) =>
onChanged: (e) { settings.val(write: true).talkSettings.sortFavoritesToTop = e,
Haptics.selection();
settings.val(write: true).talkSettings.sortFavoritesToTop = e!;
},
),
), ),
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.mark_email_unread_outlined), icon: Icons.mark_email_unread_outlined,
title: const Text('Ungelesene Chats nach oben sortieren'), title: 'Ungelesene Chats nach oben sortieren',
trailing: Checkbox( value: talkSettings.sortUnreadToTop,
value: talkSettings.sortUnreadToTop, onChanged: (e) =>
onChanged: (e) { settings.val(write: true).talkSettings.sortUnreadToTop = e,
Haptics.selection();
settings.val(write: true).talkSettings.sortUnreadToTop = e!;
},
),
), ),
ListTile( ListTile(
leading: const Icon(Icons.wallpaper_outlined), leading: const Icon(Icons.wallpaper_outlined),
@@ -49,46 +41,38 @@ class TalkSection extends StatelessWidget {
trailing: const Icon(Icons.arrow_right), trailing: const Icon(Icons.arrow_right),
onTap: () => AppRoutes.openChatBackgroundSettings(context), onTap: () => AppRoutes.openChatBackgroundSettings(context),
), ),
ListTile( SettingsCheckboxTile(
leading: const CenteredLeading( icon: Icons.notifications_active_outlined,
Icon(Icons.notifications_active_outlined), title: 'Push-Benachrichtigungen',
), subtitle: 'Benachrichtigungen bei neuen Talk-Nachrichten erhalten',
title: const Text('Push-Benachrichtigungen'), value: notificationSettings.enabled,
subtitle: const Text( onChanged: (enabled) {
'Benachrichtigungen bei neuen Talk-Nachrichten erhalten', settings.val(write: true).notificationSettings.enabled = enabled;
), // Turning off does NOT unregister: the device stays subscribed so
trailing: Checkbox( // silent sync pushes keep arriving; the message handler and iOS
value: notificationSettings.enabled, // NSE suppress only the visible notification (via the mirrored
onChanged: (e) { // flag). Enabling (re-)registers and ensures the OS permission.
Haptics.selection(); if (enabled) {
final enabled = e ?? false; final messenger = ScaffoldMessenger.of(context);
settings.val(write: true).notificationSettings.enabled = enabled; unawaited(() async {
// Turning off does NOT unregister: the device stays subscribed so // Only register when the OS permission isn't explicitly
// silent sync pushes keep arriving; the message handler and iOS // denied — otherwise NC + proxy would push into the void.
// NSE suppress only the visible notification (via the mirrored if (await PushRegistration.requestOsPermission()) {
// flag). Enabling (re-)registers and ensures the OS permission. await PushRegistration().register();
if (enabled) { } else {
final messenger = ScaffoldMessenger.of(context); messenger.showSnackBar(
unawaited(() async { const SnackBar(
// Only register when the OS permission isn't explicitly content: Text(
// denied — otherwise NC + proxy would push into the void. 'Die Benachrichtigungsberechtigung wurde in den '
if (await PushRegistration.requestOsPermission()) { 'Systemeinstellungen deaktiviert. Bitte aktiviere '
await PushRegistration().register(); 'sie dort, um Push-Benachrichtigungen zu erhalten.',
} else {
messenger.showSnackBar(
const SnackBar(
content: Text(
'Die Benachrichtigungsberechtigung wurde in den '
'Systemeinstellungen deaktiviert. Bitte aktiviere '
'sie dort, um Push-Benachrichtigungen zu erhalten.',
),
), ),
); ),
} );
}()); }
} }());
}, }
), },
), ),
ListTile( ListTile(
leading: const CenteredLeading(Icon(Icons.monitor_heart_outlined)), leading: const CenteredLeading(Icon(Icons.monitor_heart_outlined)),
@@ -3,8 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../routing/app_routes.dart'; import '../../../../routing/app_routes.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart'; import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../utils/haptics.dart';
import '../../../../view/pages/timetable/data/timetable_name_mode.dart'; import '../../../../view/pages/timetable/data/timetable_name_mode.dart';
import '../widgets/settings_checkbox_tile.dart';
class TimetableSection extends StatelessWidget { class TimetableSection extends StatelessWidget {
const TimetableSection({super.key}); const TimetableSection({super.key});
@@ -43,19 +43,13 @@ class TimetableSection extends StatelessWidget {
value!, value!,
), ),
), ),
ListTile( SettingsCheckboxTile(
leading: const Icon(Icons.calendar_view_day_outlined), icon: Icons.calendar_view_day_outlined,
title: const Text('Doppelstunden zusammenhängend anzeigen'), title: 'Doppelstunden zusammenhängend anzeigen',
trailing: Checkbox( value: timetableSettings.connectDoubleLessons,
value: timetableSettings.connectDoubleLessons, onChanged: (e) =>
onChanged: (e) { settings.val(write: true).timetableSettings.connectDoubleLessons =
Haptics.selection(); e,
settings
.val(write: true)
.timetableSettings
.connectDoubleLessons = e!;
},
),
), ),
ListTile( ListTile(
leading: const Icon(Icons.palette_outlined), leading: const Icon(Icons.palette_outlined),
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import '../../../../utils/haptics.dart';
import '../../../../widget/centered_leading.dart';
/// Settings row with a trailing checkbox. Fires [Haptics.selection] before
/// invoking [onChanged] (with the resolved non-null value), so the sections
/// don't repeat that. The leading icon is vertically centered when a [subtitle]
/// is present, matching the surrounding settings styling.
class SettingsCheckboxTile extends StatelessWidget {
final IconData icon;
final String title;
final String? subtitle;
final bool value;
final ValueChanged<bool> onChanged;
const SettingsCheckboxTile({
required this.icon,
required this.title,
required this.value,
required this.onChanged,
this.subtitle,
super.key,
});
@override
Widget build(BuildContext context) {
final leadingIcon = Icon(icon);
return ListTile(
leading: subtitle == null ? leadingIcon : CenteredLeading(leadingIcon),
title: Text(title),
subtitle: subtitle == null ? null : Text(subtitle!),
trailing: Checkbox(
value: value,
onChanged: (e) {
Haptics.selection();
onChanged(e ?? false);
},
),
);
}
}