96 lines
3.0 KiB
Dart
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));
|
|
}
|
|
}
|