migrated Marianum Message module to the Marianum-Connect API and implemented push notification deep linking
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../state/app/modules/marianum_message/bloc/marianum_message_state.dart';
|
||||
|
||||
/// Demo fixtures for the info messages — a single friendly welcome entry so the
|
||||
@@ -6,16 +8,16 @@ class DemoMarianumMessage {
|
||||
const DemoMarianumMessage._();
|
||||
|
||||
static MarianumMessageList list() {
|
||||
final today = DateTime.now();
|
||||
final date =
|
||||
'${today.day.toString().padLeft(2, '0')}.${today.month.toString().padLeft(2, '0')}.${today.year}';
|
||||
final date = DateFormat.yMMMM('de').format(DateTime.now());
|
||||
|
||||
return MarianumMessageList(
|
||||
base: 'https://marianum-fulda.de',
|
||||
messages: [
|
||||
MarianumMessage(
|
||||
id: 'demo',
|
||||
name: 'Willkommen in der Marianum-App',
|
||||
date: date,
|
||||
description: 'Eine kurze Begrüßung zum Ausprobieren.',
|
||||
url: 'https://marianum-fulda.de',
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../errors/marianumconnect_error.dart';
|
||||
import '../../marianumconnect_api.dart';
|
||||
import '../../marianumconnect_endpoint.dart';
|
||||
|
||||
/// Downloads the raw PDF bytes of a Marianum Message from
|
||||
/// `GET /api/mobile/v1/newsletter/{id}/file`.
|
||||
///
|
||||
/// Goes through the shared MC dio so the bearer token is attached automatically;
|
||||
/// the bytes are handed to `SfPdfViewer.memory` so no auth header has to be
|
||||
/// plumbed into the viewer itself.
|
||||
class GetNewsletterFile {
|
||||
final String id;
|
||||
final Dio _dio;
|
||||
|
||||
GetNewsletterFile(this.id, {Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||
|
||||
Future<Uint8List> run() async {
|
||||
try {
|
||||
final response = await _dio.get<List<int>>(
|
||||
MarianumConnectEndpoint.resolve(
|
||||
'newsletter/${Uri.encodeComponent(id)}/file',
|
||||
),
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
return Uint8List.fromList(response.data!);
|
||||
} on DioException catch (e) {
|
||||
throw mapMarianumConnectError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,13 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
NotificationTasks.navigateToTalk(context, chatToken: token);
|
||||
}
|
||||
|
||||
void _onNewsletterTapPending() {
|
||||
final id = PushTapRouter.pendingNewsletterId.value;
|
||||
if (id == null || !mounted) return;
|
||||
PushTapRouter.pendingNewsletterId.value = null;
|
||||
AppRoutes.openNewsletterById(context, id: id);
|
||||
}
|
||||
|
||||
Future<void> _handlePendingWidgetNavigation() async {
|
||||
final pending = await WidgetNavigation.consumePendingTimetableTap();
|
||||
if (!pending || !mounted) return;
|
||||
@@ -191,6 +198,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
// Android renders pushes locally, so a tap arrives via the local
|
||||
// notifications callback (PushTapRouter) rather than onMessageOpenedApp.
|
||||
PushTapRouter.pendingChatToken.addListener(_onPushTapPending);
|
||||
PushTapRouter.pendingNewsletterId.addListener(_onNewsletterTapPending);
|
||||
|
||||
_onMessageSub = FirebaseMessaging.onMessage.listen((message) {
|
||||
if (!mounted) return;
|
||||
@@ -221,6 +229,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
_onMessageOpenedAppSub?.cancel();
|
||||
_fcmTokenRefreshSub?.cancel();
|
||||
PushTapRouter.pendingChatToken.removeListener(_onPushTapPending);
|
||||
PushTapRouter.pendingNewsletterId.removeListener(_onNewsletterTapPending);
|
||||
ShareIntentListener.pending.removeListener(_handlePendingShare);
|
||||
ShareIntentListener.instance.detach();
|
||||
Main.bottomNavigator.removeListener(_onTabControllerChanged);
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../push/push_message_handler.dart';
|
||||
import '../routing/app_routes.dart';
|
||||
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../widget/debug/debug_tile.dart';
|
||||
import '../widget/debug/json_viewer.dart';
|
||||
@@ -41,10 +42,19 @@ class NotificationController {
|
||||
RemoteMessage message,
|
||||
BuildContext context,
|
||||
) async {
|
||||
NotificationTasks.navigateToTalk(
|
||||
context,
|
||||
chatToken: _extractChatToken(message),
|
||||
);
|
||||
final newsletterId = _extractNewsletterId(message);
|
||||
if (newsletterId != null) {
|
||||
AppRoutes.openNewsletterById(
|
||||
context,
|
||||
id: newsletterId,
|
||||
title: message.notification?.title,
|
||||
);
|
||||
} else {
|
||||
NotificationTasks.navigateToTalk(
|
||||
context,
|
||||
chatToken: _extractChatToken(message),
|
||||
);
|
||||
}
|
||||
NotificationTasks.updateProviders(context);
|
||||
unawaited(NotificationTasks.refreshBadge());
|
||||
|
||||
@@ -66,4 +76,9 @@ class NotificationController {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String? _extractNewsletterId(RemoteMessage message) {
|
||||
final value = message.data['newsletterId'];
|
||||
return value is String && value.isNotEmpty ? value : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ class PushTapRouter {
|
||||
/// listens to this and opens the chat, then resets it to null.
|
||||
static final ValueNotifier<String?> pendingChatToken = ValueNotifier(null);
|
||||
|
||||
/// Newsletter id of the most recently tapped Marianum-Message notification,
|
||||
/// or null. [App] listens to this and opens the message, then resets it.
|
||||
static final ValueNotifier<String?> pendingNewsletterId = ValueNotifier(null);
|
||||
|
||||
static void handleResponse(NotificationResponse response) {
|
||||
final actionId = response.actionId;
|
||||
if (actionId == kTalkReplyActionId || actionId == kTalkMarkReadActionId) {
|
||||
@@ -23,18 +27,28 @@ class PushTapRouter {
|
||||
PushActions.handleBackgroundResponse(response);
|
||||
return;
|
||||
}
|
||||
final token = _chatTokenFrom(response.payload);
|
||||
final map = _payloadMap(response.payload);
|
||||
if (map == null) return;
|
||||
final newsletterId = _stringValue(map, 'newsletterId');
|
||||
if (newsletterId != null) {
|
||||
pendingNewsletterId.value = newsletterId;
|
||||
return;
|
||||
}
|
||||
final token = _stringValue(map, 'chatToken');
|
||||
if (token != null) pendingChatToken.value = token;
|
||||
}
|
||||
|
||||
static String? _chatTokenFrom(String? payload) {
|
||||
static Map<String, dynamic>? _payloadMap(String? payload) {
|
||||
if (payload == null || payload.isEmpty) return null;
|
||||
try {
|
||||
final map = jsonDecode(payload) as Map<String, dynamic>;
|
||||
final token = map['chatToken'];
|
||||
return token is String && token.isNotEmpty ? token : null;
|
||||
return jsonDecode(payload) as Map<String, dynamic>;
|
||||
} on Object {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String? _stringValue(Map<String, dynamic> map, String key) {
|
||||
final value = map[key];
|
||||
return value is String && value.isNotEmpty ? value : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,13 +131,29 @@ class AppRoutes {
|
||||
|
||||
static void openMarianumMessage(
|
||||
BuildContext context,
|
||||
String basePath,
|
||||
MarianumMessage message,
|
||||
) {
|
||||
pushScreen(
|
||||
context,
|
||||
withNavBar: false,
|
||||
screen: MessageView(basePath: basePath, message: message),
|
||||
screen: MessageView(id: message.id, title: message.name),
|
||||
);
|
||||
}
|
||||
|
||||
/// Opens a Marianum Message by id — used for push deep links where only the
|
||||
/// id (and the notification title) are known.
|
||||
static void openNewsletterById(
|
||||
BuildContext context, {
|
||||
required String id,
|
||||
String? title,
|
||||
}) {
|
||||
pushScreen(
|
||||
context,
|
||||
withNavBar: false,
|
||||
screen: MessageView(
|
||||
id: id,
|
||||
title: title == null || title.isEmpty ? 'Marianum Message' : title,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ abstract class MarianumMessageList with _$MarianumMessageList {
|
||||
@freezed
|
||||
abstract class MarianumMessage with _$MarianumMessage {
|
||||
const factory MarianumMessage({
|
||||
@Default('') String id,
|
||||
required String name,
|
||||
required String date,
|
||||
@Default('') String description,
|
||||
required String url,
|
||||
}) = _MarianumMessage;
|
||||
|
||||
|
||||
@@ -568,7 +568,7 @@ as List<MarianumMessage>,
|
||||
/// @nodoc
|
||||
mixin _$MarianumMessage {
|
||||
|
||||
String get name; String get date; String get url;
|
||||
String get id; String get name; String get date; String get description; String get url;
|
||||
/// Create a copy of MarianumMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -581,16 +581,16 @@ $MarianumMessageCopyWith<MarianumMessage> get copyWith => _$MarianumMessageCopyW
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is MarianumMessage&&(identical(other.name, name) || other.name == name)&&(identical(other.date, date) || other.date == date)&&(identical(other.url, url) || other.url == url));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is MarianumMessage&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.date, date) || other.date == date)&&(identical(other.description, description) || other.description == description)&&(identical(other.url, url) || other.url == url));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,date,url);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,date,description,url);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MarianumMessage(name: $name, date: $date, url: $url)';
|
||||
return 'MarianumMessage(id: $id, name: $name, date: $date, description: $description, url: $url)';
|
||||
}
|
||||
|
||||
|
||||
@@ -601,7 +601,7 @@ abstract mixin class $MarianumMessageCopyWith<$Res> {
|
||||
factory $MarianumMessageCopyWith(MarianumMessage value, $Res Function(MarianumMessage) _then) = _$MarianumMessageCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String name, String date, String url
|
||||
String id, String name, String date, String description, String url
|
||||
});
|
||||
|
||||
|
||||
@@ -618,10 +618,12 @@ class _$MarianumMessageCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of MarianumMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? date = null,Object? url = null,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? date = null,Object? description = null,Object? url = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -708,10 +710,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String date, String url)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String date, String description, String url)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _MarianumMessage() when $default != null:
|
||||
return $default(_that.name,_that.date,_that.url);case _:
|
||||
return $default(_that.id,_that.name,_that.date,_that.description,_that.url);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -729,10 +731,10 @@ return $default(_that.name,_that.date,_that.url);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String date, String url) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String date, String description, String url) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _MarianumMessage():
|
||||
return $default(_that.name,_that.date,_that.url);case _:
|
||||
return $default(_that.id,_that.name,_that.date,_that.description,_that.url);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -749,10 +751,10 @@ return $default(_that.name,_that.date,_that.url);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String date, String url)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String date, String description, String url)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _MarianumMessage() when $default != null:
|
||||
return $default(_that.name,_that.date,_that.url);case _:
|
||||
return $default(_that.id,_that.name,_that.date,_that.description,_that.url);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -764,11 +766,13 @@ return $default(_that.name,_that.date,_that.url);case _:
|
||||
@JsonSerializable()
|
||||
|
||||
class _MarianumMessage implements MarianumMessage {
|
||||
const _MarianumMessage({required this.name, required this.date, required this.url});
|
||||
const _MarianumMessage({this.id = '', required this.name, required this.date, this.description = '', required this.url});
|
||||
factory _MarianumMessage.fromJson(Map<String, dynamic> json) => _$MarianumMessageFromJson(json);
|
||||
|
||||
@override@JsonKey() final String id;
|
||||
@override final String name;
|
||||
@override final String date;
|
||||
@override@JsonKey() final String description;
|
||||
@override final String url;
|
||||
|
||||
/// Create a copy of MarianumMessage
|
||||
@@ -784,16 +788,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MarianumMessage&&(identical(other.name, name) || other.name == name)&&(identical(other.date, date) || other.date == date)&&(identical(other.url, url) || other.url == url));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MarianumMessage&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.date, date) || other.date == date)&&(identical(other.description, description) || other.description == description)&&(identical(other.url, url) || other.url == url));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,date,url);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,date,description,url);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MarianumMessage(name: $name, date: $date, url: $url)';
|
||||
return 'MarianumMessage(id: $id, name: $name, date: $date, description: $description, url: $url)';
|
||||
}
|
||||
|
||||
|
||||
@@ -804,7 +808,7 @@ abstract mixin class _$MarianumMessageCopyWith<$Res> implements $MarianumMessage
|
||||
factory _$MarianumMessageCopyWith(_MarianumMessage value, $Res Function(_MarianumMessage) _then) = __$MarianumMessageCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String name, String date, String url
|
||||
String id, String name, String date, String description, String url
|
||||
});
|
||||
|
||||
|
||||
@@ -821,10 +825,12 @@ class __$MarianumMessageCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of MarianumMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? date = null,Object? url = null,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? date = null,Object? description = null,Object? url = null,}) {
|
||||
return _then(_MarianumMessage(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
|
||||
@@ -32,14 +32,18 @@ Map<String, dynamic> _$MarianumMessageListToJson(
|
||||
|
||||
_MarianumMessage _$MarianumMessageFromJson(Map<String, dynamic> json) =>
|
||||
_MarianumMessage(
|
||||
id: json['id'] as String? ?? '',
|
||||
name: json['name'] as String,
|
||||
date: json['date'] as String,
|
||||
description: json['description'] as String? ?? '',
|
||||
url: json['url'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MarianumMessageToJson(_MarianumMessage instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'date': instance.date,
|
||||
'description': instance.description,
|
||||
'url': instance.url,
|
||||
};
|
||||
|
||||
+48
-8
@@ -1,13 +1,53 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../basis/dataloader/mhsl_data_loader.dart';
|
||||
import '../../../infrastructure/data_loader/data_loader.dart';
|
||||
import '../../../../../api/marianumconnect/errors/marianumconnect_error.dart';
|
||||
import '../../../../../api/marianumconnect/marianumconnect_api.dart';
|
||||
import '../../../../../api/marianumconnect/marianumconnect_endpoint.dart';
|
||||
import '../bloc/marianum_message_state.dart';
|
||||
|
||||
class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> {
|
||||
@override
|
||||
Future<Response<String>> fetch() async => dio.get('/message/messages.json');
|
||||
@override
|
||||
MarianumMessageList assemble(DataLoaderResult data) =>
|
||||
MarianumMessageList.fromJson(data.asMap());
|
||||
/// Loads the "Marianum Message" list from `GET /api/mobile/v1/newsletter`
|
||||
/// (formerly the mhsl.eu `message/messages.json` endpoint). Bearer token is
|
||||
/// attached by the shared Marianum-Connect dio.
|
||||
class MarianumMessageGetMessages {
|
||||
final Dio _dio;
|
||||
|
||||
MarianumMessageGetMessages({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||
|
||||
Future<MarianumMessageList> run() async {
|
||||
try {
|
||||
final response = await _dio.get<List<dynamic>>(
|
||||
MarianumConnectEndpoint.resolve('newsletter'),
|
||||
);
|
||||
final messages = (response.data ?? const [])
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(_mapItem)
|
||||
.toList();
|
||||
return MarianumMessageList(
|
||||
base: MarianumConnectEndpoint.current(),
|
||||
messages: messages,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
throw mapMarianumConnectError(e);
|
||||
}
|
||||
}
|
||||
|
||||
MarianumMessage _mapItem(Map<String, dynamic> map) => MarianumMessage(
|
||||
id: map['id'] as String,
|
||||
name: map['title'] as String? ?? '',
|
||||
date: _formatDate(map['date'] as String?),
|
||||
description: map['description'] as String? ?? '',
|
||||
url: map['fileUrl'] as String? ?? '',
|
||||
);
|
||||
|
||||
static final DateFormat _monthYear = DateFormat.yMMMM('de');
|
||||
|
||||
/// Server sends ISO `yyyy-MM-dd`; the list shows only the German month + year
|
||||
/// (e.g. "April 2024").
|
||||
static String _formatDate(String? iso) {
|
||||
if (iso == null || iso.isEmpty) return '';
|
||||
final parsed = DateTime.tryParse(iso);
|
||||
if (parsed == null) return iso;
|
||||
return _monthYear.format(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +47,21 @@ class MarianumMessageListView extends StatelessWidget {
|
||||
children: [Icon(Icons.newspaper)],
|
||||
),
|
||||
title: Text(message.name, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Text('vom ${message.date}'),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (message.description.isNotEmpty)
|
||||
Text(
|
||||
message.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text('vom ${message.date}'),
|
||||
],
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
AppRoutes.openMarianumMessage(
|
||||
context,
|
||||
state.messageList.base,
|
||||
message,
|
||||
);
|
||||
AppRoutes.openMarianumMessage(context, message);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,47 +1,57 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../state/app/modules/marianum_message/bloc/marianum_message_state.dart';
|
||||
import '../../../api/marianumconnect/queries/get_newsletter_file/get_newsletter_file.dart';
|
||||
import '../../../widget/confirm_dialog.dart';
|
||||
import '../../../widget/info_dialog.dart';
|
||||
import '../../../widget/placeholder_view.dart';
|
||||
|
||||
class MessageView extends StatefulWidget {
|
||||
final String basePath;
|
||||
final MarianumMessage message;
|
||||
const MessageView({super.key, required this.basePath, required this.message});
|
||||
final String id;
|
||||
final String title;
|
||||
const MessageView({super.key, required this.id, required this.title});
|
||||
|
||||
@override
|
||||
State<MessageView> createState() => _MessageViewState();
|
||||
}
|
||||
|
||||
class _MessageViewState extends State<MessageView> {
|
||||
late final Future<Uint8List> _pdf = GetNewsletterFile(widget.id).run();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(title: Text(widget.message.name)),
|
||||
body: SfPdfViewer.network(
|
||||
widget.basePath + widget.message.url,
|
||||
enableHyperlinkNavigation: true,
|
||||
onDocumentLoadFailed: (PdfDocumentLoadFailedDetails e) {
|
||||
Navigator.of(context).pop();
|
||||
InfoDialog.show(
|
||||
context,
|
||||
"Dokument '${widget.message.name}' konnte nicht geladen werden:\n${e.description}",
|
||||
title: 'Fehler beim öffnen',
|
||||
);
|
||||
},
|
||||
onHyperlinkClicked: (PdfHyperlinkClickedDetails e) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: 'Link öffnen',
|
||||
content: 'Möchtest du den folgenden Link öffnen?\n${e.uri}',
|
||||
confirmButton: 'Öffnen',
|
||||
onConfirm: () => launchUrl(
|
||||
Uri.parse(e.uri),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
),
|
||||
appBar: AppBar(title: Text(widget.title)),
|
||||
body: FutureBuilder<Uint8List>(
|
||||
future: _pdf,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return const PlaceholderView(
|
||||
icon: Icons.error_outline,
|
||||
text: 'Das Dokument konnte nicht geladen werden.',
|
||||
);
|
||||
}
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return SfPdfViewer.memory(
|
||||
snapshot.data!,
|
||||
enableHyperlinkNavigation: true,
|
||||
onHyperlinkClicked: (PdfHyperlinkClickedDetails e) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: 'Link öffnen',
|
||||
content: 'Möchtest du den folgenden Link öffnen?\n${e.uri}',
|
||||
confirmButton: 'Öffnen',
|
||||
onConfirm: () => launchUrl(
|
||||
Uri.parse(e.uri),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -54,7 +54,7 @@ class SearchMarianumMessages extends SearchDelegate<MarianumMessage?> {
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
close(context, message);
|
||||
AppRoutes.openMarianumMessage(context, base, message);
|
||||
AppRoutes.openMarianumMessage(context, message);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user