Files
Client/lib/view/pages/talk/data/chat_message_matcher.dart
T

96 lines
3.0 KiB
Dart

import '../../../../api/marianumcloud/search/search_chat_messages_response.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';
/// One message matching a chat search, either from the loaded history or
/// from the server-side search.
class ChatSearchHit {
final int messageId;
final String? actorId;
final String? actorDisplayName;
final String text;
final DateTime? sentAt;
final bool isSystem;
const ChatSearchHit({
required this.messageId,
required this.text,
this.actorId,
this.actorDisplayName,
this.sentAt,
this.isSystem = false,
});
factory ChatSearchHit.fromServer(SearchChatMessagesEntry entry) =>
ChatSearchHit(
messageId: entry.messageId,
text: entry.excerpt,
actorId: entry.actorId,
actorDisplayName: entry.actorDisplayName,
sentAt: entry.sentAt,
);
}
class ChatMessageMatcher {
/// System messages that are folded into other bubbles (reactions, poll
/// votes, deletions) and therefore never rendered nor searched as their own
/// entry.
static bool isHiddenSystemMessage(GetChatResponseObject element) =>
element.systemMessage.contains('reaction') ||
element.systemMessage.contains('poll_voted') ||
element.systemMessage.contains('message_deleted');
/// Newest first.
static List<ChatSearchHit> findMatches(
GetChatResponse response,
String query,
) {
final q = query.trim().toLowerCase();
if (q.isEmpty) return const [];
final matches = <ChatSearchHit>[];
for (final element in response.data) {
if (element.id <= 0 || isHiddenSystemMessage(element)) continue;
final text = RichObjectStringProcessor.parseToString(
element.message,
element.messageParameters,
);
final isSystem =
element.messageType == GetRoomResponseObjectMessageType.system;
final matched =
text.toLowerCase().contains(q) ||
(!isSystem && element.actorDisplayName.toLowerCase().contains(q));
if (!matched) continue;
matches.add(
ChatSearchHit(
messageId: element.id,
text: text,
actorId: isSystem ? null : element.actorId,
actorDisplayName: isSystem ? null : element.actorDisplayName,
sentAt: element.sentAt,
isSystem: isSystem,
),
);
}
matches.sort((a, b) => b.sentAt!.compareTo(a.sentAt!));
return matches;
}
/// Local hits win over server hits for the same message — they carry the
/// full text and exact metadata. Result is newest first.
static List<ChatSearchHit> merge(
List<ChatSearchHit> local,
List<ChatSearchHit> server,
) {
if (server.isEmpty) return local;
final seen = {for (final h in local) h.messageId};
return [...local, ...server.where((h) => seen.add(h.messageId))]
..sort((a, b) => b.messageId.compareTo(a.messageId));
}
}