implemented dynamic module settings and configurable bottom bar, added all-day event support to timetable, and overhauled marianum dates UI with month grouping and search

This commit is contained in:
2026-05-06 22:37:41 +02:00
parent 86d12884fc
commit 95ef29fb09
19 changed files with 1114 additions and 253 deletions
@@ -31,10 +31,12 @@ class DefaultSettings {
Modules.marianumDates,
],
hiddenModules: [],
autoFillBottomBar: true,
fixedBottomBarSlots: 3,
),
timetableSettings: TimetableSettings(
connectDoubleLessons: true,
timetableNameMode: TimetableNameMode.name
timetableNameMode: TimetableNameMode.name,
),
talkSettings: TalkSettings(
sortFavoritesToTop: true,
@@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../state/app/modules/app_modules.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../storage/settings.dart' as model;
import 'data/default_settings.dart';
/// Reorderable list with bottom-bar slot configuration on top.
///
/// Used both inline in the "Mehr" edit mode and as the body of
/// [ModulesSettingsPage] from the main settings.
class ModuleSortBody extends StatelessWidget {
const ModuleSortBody({super.key});
@override
Widget build(BuildContext context) => BlocBuilder<SettingsCubit, model.Settings>(builder: (context, _) {
final settings = context.read<SettingsCubit>();
final modulesSettings = settings.val().modulesSettings;
void changeVisibility(Modules module) {
var hidden = settings.val(write: true).modulesSettings.hiddenModules;
if (hidden.contains(module)) {
hidden.remove(module);
} else if (hidden.length < 3) {
hidden.add(module);
}
}
return ReorderableListView(
header: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(
'Halte und ziehe einen Eintrag, um ihn zu verschieben.\nEs können 3 Bereiche ausgeblendet werden.',
textAlign: TextAlign.center,
),
),
SwitchListTile(
title: const Text('Modulleiste automatisch füllen'),
subtitle: const Text('Auf größeren Bildschirmen werden mehr Module direkt angezeigt'),
value: modulesSettings.autoFillBottomBar,
onChanged: (value) => settings.val(write: true).modulesSettings.autoFillBottomBar = value,
),
if (!modulesSettings.autoFillBottomBar)
ListTile(
title: const Text('Anzahl Slots in der Modulleiste'),
subtitle: Text('${modulesSettings.fixedBottomBarSlots} Module (zzgl. „Mehr")'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: modulesSettings.fixedBottomBarSlots > AppModule.minBottomBarSlots
? () => settings.val(write: true).modulesSettings.fixedBottomBarSlots -= 1
: null,
),
Text('${modulesSettings.fixedBottomBarSlots}'),
IconButton(
icon: const Icon(Icons.add_circle_outline),
onPressed: modulesSettings.fixedBottomBarSlots < AppModule.maxBottomBarSlots
? () => settings.val(write: true).modulesSettings.fixedBottomBarSlots += 1
: null,
),
],
),
),
const Divider(),
],
),
children: AppModule.modules(context, showFiltered: true)
.map((key, value) => MapEntry(key, value.toListTile(
context,
key: Key(key.name),
isReorder: true,
onVisibleChange: () => changeVisibility(key),
isVisible: !settings.val().modulesSettings.hiddenModules.contains(key),
)))
.values
.toList(),
onReorder: (oldIndex, newIndex) {
if (newIndex > oldIndex) newIndex -= 1;
var order = settings.val().modulesSettings.moduleOrder.toList();
final movedModule = order.removeAt(oldIndex);
order.insert(newIndex, movedModule);
settings.val(write: true).modulesSettings.moduleOrder = order;
},
);
});
}
class ModulesSettingsPage extends StatelessWidget {
const ModulesSettingsPage({super.key});
@override
Widget build(BuildContext context) => BlocBuilder<SettingsCubit, model.Settings>(builder: (context, _) {
final settings = context.read<SettingsCubit>();
final isModified = settings.val().modulesSettings.toJson().toString() != DefaultSettings.get().modulesSettings.toJson().toString();
return Scaffold(
appBar: AppBar(
title: const Text('Module'),
actions: [
IconButton(
tooltip: 'Auf Standard zurücksetzen',
onPressed: isModified ? () => settings.val(write: true).modulesSettings = DefaultSettings.get().modulesSettings : null,
icon: const Icon(Icons.undo_outlined),
),
],
),
body: const ModuleSortBody(),
);
});
}
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import '../../../../routing/app_routes.dart';
class ModulesSection extends StatelessWidget {
const ModulesSection({super.key});
@override
Widget build(BuildContext context) => ListTile(
leading: const Icon(Icons.apps_outlined),
title: const Text('Module'),
subtitle: const Text('Reihenfolge, Sichtbarkeit und Modulleiste anpassen'),
trailing: const Icon(Icons.arrow_right),
onTap: () => AppRoutes.openModulesSettings(context),
);
}
+3
View File
@@ -4,6 +4,7 @@ import 'sections/about_section.dart';
import 'sections/account_section.dart';
import 'sections/appearance_section.dart';
import 'sections/files_section.dart';
import 'sections/modules_section.dart';
import 'sections/talk_section.dart';
import 'sections/timetable_section.dart';
@@ -19,6 +20,8 @@ class Settings extends StatelessWidget {
Divider(),
AppearanceSection(),
Divider(),
ModulesSection(),
Divider(),
TimetableSection(),
Divider(),
TalkSection(),