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:
2026-07-09 00:51:08 +02:00
parent 1114291313
commit cedeb06569
57 changed files with 4758 additions and 43 deletions
+74
View File
@@ -1,9 +1,13 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:url_launcher/url_launcher_string.dart';
import '../api/marianumcloud/talk/room/get_room_response.dart';
import '../api/marianumconnect/marianumconnect_endpoint.dart';
import '../api/marianumconnect/queries/timetable_get_element_week/timetable_element_type.dart';
import '../main.dart';
import '../model/account_data.dart';
@@ -30,6 +34,7 @@ import '../view/pages/share_intent/share_target_page.dart';
import '../view/pages/talk/chat_view.dart';
import '../view/pages/talk/details/message_reactions.dart';
import '../view/pages/talk/talk_navigator.dart';
import '../view/pages/ticker/ticker_page_view.dart';
import '../view/pages/timetable/custom_events/custom_events_view.dart';
import '../widget/debug/cache_view.dart';
import '../widget/file_viewer.dart';
@@ -136,6 +141,75 @@ class AppRoutes {
);
}
/// Opens a ticker page (CONTENT or PROXIED_FILE) as a standalone detail
/// screen. Used for deep links from outside the ticker module — inside the
/// module pages open in-place instead.
static void openTickerPage(
BuildContext context, {
required String slug,
String? title,
}) {
pushScreen(
context,
withNavBar: false,
screen: TickerPageView(slug: slug, title: title),
);
}
/// Resolves a link tapped inside ticker content. Links that point at another
/// ticker page (absolute against the active base URL, or site-relative
/// `/ticker/p/{slug}`) open in-app; everything else goes to the external
/// launcher.
static void openTickerLink(BuildContext context, String href) {
final slug = tickerSlugOf(href);
if (slug != null && slug.isNotEmpty) {
openTickerPage(context, slug: slug);
return;
}
unawaited(openExternalUrl(href));
}
/// Extracts the ticker page slug from a link that points at another ticker
/// page (absolute against the active base URL, or site-relative
/// `/ticker/p/{slug}`), or null for any other link. Exposed so the in-place
/// ticker navigation can reuse the same detection instead of duplicating it.
static String? tickerSlugOf(String href) {
const marker = '/ticker/p/';
final base = MarianumConnectEndpoint.current();
String? rest;
if (href.startsWith('$base$marker')) {
rest = href.substring('$base$marker'.length);
} else if (href.startsWith(marker)) {
rest = href.substring(marker.length);
}
if (rest == null) return null;
rest = rest.split('#').first.split('?').first;
if (rest.isEmpty) return null;
return Uri.decodeComponent(rest);
}
/// Launches an external URL, restricted to safe schemes (http/https/mailto/tel).
static Future<void> openExternalUrl(String url) async {
final uri = Uri.tryParse(url);
if (uri == null) return;
const allowed = {'http', 'https', 'mailto', 'tel'};
if (!allowed.contains(uri.scheme.toLowerCase())) return;
if (await canLaunchUrlString(url)) {
await launchUrlString(url, mode: LaunchMode.externalApplication);
}
}
/// Opens a possibly site-relative web URL (e.g. the ticker `webUrl`) by
/// prefixing the active Marianum-Connect base URL when needed.
static Future<void> openWebUrl(String relativeOrAbsolute) {
if (relativeOrAbsolute.startsWith('http')) {
return openExternalUrl(relativeOrAbsolute);
}
return openExternalUrl(
'${MarianumConnectEndpoint.current()}$relativeOrAbsolute',
);
}
static void openQrShare(BuildContext context) {
pushScreen(context, withNavBar: false, screen: const QrShareView());
}