implemented the Ticker module with a native ProseMirror document renderer and integrated API support for structured content, navigation trees, and proxied files
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||
|
||||
import '../../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||
import '../../../routing/app_routes.dart';
|
||||
import '../../../storage/modules_settings.dart';
|
||||
import '../../../view/pages/files/files.dart';
|
||||
import '../../../view/pages/grade_averages/grade_averages_view.dart';
|
||||
import '../../../view/pages/holidays/holidays_view.dart';
|
||||
@@ -12,6 +13,7 @@ import '../../../view/pages/marianum_dates/marianum_dates_view.dart';
|
||||
import '../../../view/pages/marianum_message/marianum_message_list_view.dart';
|
||||
import '../../../view/pages/more/roomplan/roomplan.dart';
|
||||
import '../../../view/pages/talk/chat_list.dart';
|
||||
import '../../../view/pages/ticker/ticker_view.dart';
|
||||
import '../../../view/pages/timetable/timetable.dart';
|
||||
import '../../../widget/breaker/breaker.dart';
|
||||
import '../../../widget/centered_leading.dart';
|
||||
@@ -48,6 +50,15 @@ class AppModule {
|
||||
breakerArea: BreakerArea.timetable,
|
||||
create: Timetable.new,
|
||||
),
|
||||
Modules.ticker: AppModule(
|
||||
Modules.ticker,
|
||||
name: 'Ticker',
|
||||
// Icons.newspaper is already taken by the "Marianum Message" module;
|
||||
// use feed to keep the two visually distinct.
|
||||
icon: () => Icon(Icons.feed),
|
||||
breakerArea: BreakerArea.ticker,
|
||||
create: TickerView.new,
|
||||
),
|
||||
Modules.talk: AppModule(
|
||||
Modules.talk,
|
||||
name: 'Talk',
|
||||
@@ -136,13 +147,59 @@ class AppModule {
|
||||
}
|
||||
|
||||
return {
|
||||
for (var element in settings.val().modulesSettings.moduleOrder.where(
|
||||
(element) => available.containsKey(element),
|
||||
))
|
||||
for (var element in effectiveModuleOrder(
|
||||
settings.val().modulesSettings,
|
||||
).where((element) => available.containsKey(element)))
|
||||
element: available[element]!,
|
||||
};
|
||||
}
|
||||
|
||||
// A persisted moduleOrder predates modules added in newer app versions.
|
||||
// Deriving the effective order at read time (instead of relying on a heal
|
||||
// during hydration) makes it impossible for a module to vanish from the
|
||||
// bottom bar, the "Mehr" menu and the settings list at once. Missing
|
||||
// modules are inserted right after their closest declared predecessor so
|
||||
// they land at their default position (e.g. the ticker between Stundenplan
|
||||
// and Talk) instead of at the end of the "Mehr" menu.
|
||||
static List<Modules> effectiveModuleOrder(ModulesSettings settings) {
|
||||
final seen = <Modules>{};
|
||||
final order = [
|
||||
for (final m in settings.moduleOrder)
|
||||
if (seen.add(m)) m,
|
||||
];
|
||||
for (final missing in Modules.values) {
|
||||
if (!seen.add(missing)) continue;
|
||||
var insertAt = 0;
|
||||
for (final predecessor in Modules.values.takeWhile(
|
||||
(m) => m != missing,
|
||||
)) {
|
||||
final pos = order.indexOf(predecessor);
|
||||
if (pos >= insertAt) insertAt = pos + 1;
|
||||
}
|
||||
order.insert(insertAt, missing);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
// The settings list displays a capability-filtered subset, so reorder
|
||||
// indices refer to that subset; the move is applied there and merged back
|
||||
// into the full order (non-displayed modules keep their relative slots).
|
||||
static List<Modules> reorderModuleOrder({
|
||||
required List<Modules> displayed,
|
||||
required List<Modules> effective,
|
||||
required int oldIndex,
|
||||
required int newIndex,
|
||||
}) {
|
||||
final moved = List<Modules>.from(displayed);
|
||||
moved.insert(newIndex, moved.removeAt(oldIndex));
|
||||
final displayedSet = displayed.toSet();
|
||||
var moveIndex = 0;
|
||||
return [
|
||||
for (final m in effective)
|
||||
displayedSet.contains(m) ? moved[moveIndex++] : m,
|
||||
];
|
||||
}
|
||||
|
||||
static const int minBottomBarSlots = 3;
|
||||
static const int maxBottomBarSlots = 5;
|
||||
|
||||
@@ -152,14 +209,9 @@ class AppModule {
|
||||
|
||||
int desired;
|
||||
if (settings.autoFillBottomBar) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
if (width >= 840) {
|
||||
desired = 5;
|
||||
} else if (width >= 600) {
|
||||
desired = 4;
|
||||
} else {
|
||||
desired = 3;
|
||||
}
|
||||
// 4 content tabs so the bar shows 5 entries including "Mehr" — enough
|
||||
// for the Ticker module without pushing "Dateien" into the overflow.
|
||||
desired = 4;
|
||||
} else {
|
||||
desired = settings.fixedBottomBarSlots;
|
||||
}
|
||||
@@ -225,6 +277,7 @@ class AppModule {
|
||||
|
||||
enum Modules {
|
||||
timetable,
|
||||
ticker,
|
||||
talk,
|
||||
files,
|
||||
marianumMessage,
|
||||
|
||||
Reference in New Issue
Block a user