hardened API decoding against server fields the models did not expect

The Talk room list crashed for a new account because the server omits
lastMessage for rooms without a visible message and the model required it.
Beyond that single field, a review of the Talk models against the OpenAPI
spec of the installed version (23.0.8) found the same crash class in several
places: closed enums over open server values (inCall bitmask, rich-object
types, actor and message types), a deleted parent sent as {id, deleted},
and the autocomplete status arriving as an object.

- json_serializable `checked: true`: decode errors now name class and field
- lists (rooms, messages, participants, autocomplete) decode per entry;
  a malformed entry is skipped and reported via ClientErrorReporter
- unknownEnumValue fallbacks for all response enums of Talk and breakers
- lastMessage nullable; inCall as bitmask; deleted parent placeholder;
  autocomplete status object/string; fixed shareWithDisplayNameUnique key
- GetRoomParams no longer sends null query parameters

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrepDTL4qDe35TuS29sLW3
This commit is contained in:
2026-09-29 16:35:41 +02:00
co-authored by Claude Fable 5.1
parent a79b376bb3
commit 279b973e89
92 changed files with 2150 additions and 963 deletions
@@ -121,6 +121,7 @@ class _ChatBubbleState extends State<ChatBubble>
case GetRoomResponseObjectMessageType.comment:
case GetRoomResponseObjectMessageType.voiceMessage:
case GetRoomResponseObjectMessageType.command:
case GetRoomResponseObjectMessageType.unknown:
return null;
}
}
+11 -8
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../api/marianumcloud/talk/actions/talk_actions.dart';
import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart';
import '../../../../api/marianumcloud/talk/chat/rich_object_string_processor.dart';
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../api/marianumcloud/talk/room/notification_level.dart';
@@ -69,8 +70,7 @@ class _ChatTileState extends State<ChatTile> {
/// Memoised per last message: the tile rebuilds on every chat-list emit and
/// the Markdown strip is a full parse. Keyed by content, since every refresh
/// delivers new message objects.
String _lastMessagePreview() {
final last = widget.data.lastMessage;
String _lastMessagePreview(GetChatResponseObject last) {
final key = (last.id, last.message, last.markdown);
if (key == _previewFor) return _preview;
final text = RichObjectStringProcessor.parseToString(
@@ -84,9 +84,16 @@ class _ChatTileState extends State<ChatTile> {
Object? _previewFor;
String _preview = '';
String _subtitle() {
final last = widget.data.lastMessage;
if (last == null) return 'Noch keine Nachrichten';
return '${last.sentAt.formatRelative()}: ${_lastMessagePreview(last)}';
}
Future<void> _setCurrentAsRead() async {
final token = widget.data.token;
final lastId = widget.data.lastMessage.id;
final lastId = widget.data.lastMessage?.id;
if (lastId == null) return;
context.read<ChatListBloc>().markRoomAsRead(token, lastId);
unawaited(NotificationTasks.clearNotificationsForChat(token));
await context.read<ChatBloc>().sendServerReadMarker(token, lastId);
@@ -173,11 +180,7 @@ class _ChatTileState extends State<ChatTile> {
],
],
),
subtitle: Text(
'${widget.data.lastMessage.sentAt.formatRelative()}: '
'${_lastMessagePreview()}',
overflow: TextOverflow.ellipsis,
),
subtitle: Text(_subtitle(), overflow: TextOverflow.ellipsis),
trailing: widget.data.unreadMessages <= 0
? null
: Semantics(