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
@@ -0,0 +1,25 @@
import 'package:dio/dio.dart';
import '../../errors/marianumconnect_error.dart';
import '../../marianumconnect_api.dart';
import '../../marianumconnect_endpoint.dart';
import 'get_ticker_nav_response.dart';
/// Fetches the filtered ticker page tree from
/// `GET /api/mobile/v1/ticker/pages`.
class GetTickerNav {
final Dio _dio;
GetTickerNav({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
Future<TickerNavResponse> run() async {
try {
final response = await _dio.get<Map<String, dynamic>>(
MarianumConnectEndpoint.resolve('ticker/pages'),
);
return TickerNavResponse.fromJson(response.data!);
} on DioException catch (e) {
throw mapMarianumConnectError(e);
}
}
}
@@ -0,0 +1,78 @@
import 'package:json_annotation/json_annotation.dart';
part 'get_ticker_nav_response.g.dart';
/// The filtered ticker page tree from `GET /api/mobile/v1/ticker/pages`.
///
/// The backend already drops unpublished pages, pages the user may not see, and
/// CONTENT pages without ProseMirror JSON — the app renders whatever arrives.
@JsonSerializable(explicitToJson: true)
class TickerNavResponse {
@JsonKey(defaultValue: 1)
final int schemaVersion;
@JsonKey(defaultValue: '')
final String navHash;
@JsonKey(defaultValue: [])
final List<TickerNavSection> sections;
TickerNavResponse({
required this.schemaVersion,
required this.navHash,
required this.sections,
});
factory TickerNavResponse.fromJson(Map<String, dynamic> json) =>
_$TickerNavResponseFromJson(json);
Map<String, dynamic> toJson() => _$TickerNavResponseToJson(this);
}
@JsonSerializable(explicitToJson: true)
class TickerNavSection {
@JsonKey(defaultValue: '')
final String title;
@JsonKey(defaultValue: [])
final List<TickerNavPage> pages;
TickerNavSection({required this.title, required this.pages});
factory TickerNavSection.fromJson(Map<String, dynamic> json) =>
_$TickerNavSectionFromJson(json);
Map<String, dynamic> toJson() => _$TickerNavSectionToJson(this);
}
/// A single navigable page. [kind] is one of `CONTENT`, `REDIRECT`,
/// `PROXIED_FILE` (see [TickerPageKind]); the extra fields are populated per
/// kind (REDIRECT → [externalUrl], PROXIED_FILE → [fileUrl]).
@JsonSerializable()
class TickerNavPage {
@JsonKey(defaultValue: '')
final String title;
@JsonKey(defaultValue: '')
final String slug;
@JsonKey(defaultValue: 'CONTENT')
final String kind;
final String? hash;
final String? externalUrl;
final bool? externalUrlNewTab;
final String? fileUrl;
TickerNavPage({
required this.title,
required this.slug,
required this.kind,
this.hash,
this.externalUrl,
this.externalUrlNewTab,
this.fileUrl,
});
factory TickerNavPage.fromJson(Map<String, dynamic> json) =>
_$TickerNavPageFromJson(json);
Map<String, dynamic> toJson() => _$TickerNavPageToJson(this);
}
@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'get_ticker_nav_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TickerNavResponse _$TickerNavResponseFromJson(Map<String, dynamic> json) =>
TickerNavResponse(
schemaVersion: (json['schemaVersion'] as num?)?.toInt() ?? 1,
navHash: json['navHash'] as String? ?? '',
sections:
(json['sections'] as List<dynamic>?)
?.map((e) => TickerNavSection.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
Map<String, dynamic> _$TickerNavResponseToJson(TickerNavResponse instance) =>
<String, dynamic>{
'schemaVersion': instance.schemaVersion,
'navHash': instance.navHash,
'sections': instance.sections.map((e) => e.toJson()).toList(),
};
TickerNavSection _$TickerNavSectionFromJson(Map<String, dynamic> json) =>
TickerNavSection(
title: json['title'] as String? ?? '',
pages:
(json['pages'] as List<dynamic>?)
?.map((e) => TickerNavPage.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
Map<String, dynamic> _$TickerNavSectionToJson(TickerNavSection instance) =>
<String, dynamic>{
'title': instance.title,
'pages': instance.pages.map((e) => e.toJson()).toList(),
};
TickerNavPage _$TickerNavPageFromJson(Map<String, dynamic> json) =>
TickerNavPage(
title: json['title'] as String? ?? '',
slug: json['slug'] as String? ?? '',
kind: json['kind'] as String? ?? 'CONTENT',
hash: json['hash'] as String?,
externalUrl: json['externalUrl'] as String?,
externalUrlNewTab: json['externalUrlNewTab'] as bool?,
fileUrl: json['fileUrl'] as String?,
);
Map<String, dynamic> _$TickerNavPageToJson(TickerNavPage instance) =>
<String, dynamic>{
'title': instance.title,
'slug': instance.slug,
'kind': instance.kind,
'hash': instance.hash,
'externalUrl': instance.externalUrl,
'externalUrlNewTab': instance.externalUrlNewTab,
'fileUrl': instance.fileUrl,
};