refactor: lokale Nachrichtensuche als eigenes Modul (lib/features/search) + 5 Vertragstests
Verhaltensgleich aus search_modal.dart herausgeloest (gleiche Deckel 1500/60/500, gleiche Schleife; Medien-Filter jetzt Parameter). Tests laufen ueber die echte SDK-DB-Schicht mit synthetischen Daten (test/support/synthetic_matrix_data.dart). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b2413ef627
commit
d5ff7982a0
@@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart';
|
||||
import 'package:pyramid/core/app_state.dart';
|
||||
import 'package:pyramid/core/matrix_client.dart';
|
||||
import 'package:pyramid/core/theme.dart';
|
||||
import 'package:pyramid/features/search/local_message_search.dart';
|
||||
import 'package:pyramid/widgets/mxc_image.dart';
|
||||
|
||||
// ─── State ────────────────────────────────────────────────────────────────────
|
||||
@@ -110,7 +111,11 @@ class _SearchModalState extends ConsumerState<SearchModal> {
|
||||
List<Event> messageResults = [];
|
||||
if (_filter != _Filter.people) {
|
||||
try {
|
||||
messageResults = await _localMessageSearch(client, q);
|
||||
messageResults = await localMessageSearch(
|
||||
client,
|
||||
q,
|
||||
mediaOnly: _filter == _Filter.media,
|
||||
);
|
||||
} catch (e) {
|
||||
setState(() => _state = _state.copyWith(
|
||||
loading: false,
|
||||
@@ -129,45 +134,6 @@ class _SearchModalState extends ConsumerState<SearchModal> {
|
||||
}
|
||||
}
|
||||
|
||||
// Scans the local (decrypted) event database across all rooms for messages
|
||||
// whose body matches the query. For the media filter, only media messages
|
||||
// whose filename/caption matches are returned.
|
||||
Future<List<Event>> _localMessageSearch(Client client, String q) async {
|
||||
final db = client.database;
|
||||
final lower = q.toLowerCase();
|
||||
final media = _filter == _Filter.media;
|
||||
const mediaTypes = {'m.image', 'm.video', 'm.file', 'm.audio'};
|
||||
const perRoomScanCap = 1500;
|
||||
const totalCap = 60;
|
||||
const chunk = 500;
|
||||
|
||||
final results = <Event>[];
|
||||
for (final room in client.rooms.where((r) => !r.isSpace)) {
|
||||
var start = 0;
|
||||
var scanned = 0;
|
||||
try {
|
||||
while (scanned < perRoomScanCap) {
|
||||
final events = await db.getEventList(room, start: start, limit: chunk);
|
||||
if (events.isEmpty) break;
|
||||
for (final e in events) {
|
||||
if (e.type != EventTypes.Message || e.redacted) continue;
|
||||
final msgtype = e.content.tryGet<String>('msgtype') ?? '';
|
||||
if (media && !mediaTypes.contains(msgtype)) continue;
|
||||
if (!e.body.toLowerCase().contains(lower)) continue;
|
||||
results.add(e);
|
||||
}
|
||||
start += chunk;
|
||||
scanned += events.length;
|
||||
if (events.length < chunk || results.length >= totalCap) break;
|
||||
}
|
||||
} catch (_) {}
|
||||
if (results.length >= totalCap) break;
|
||||
}
|
||||
|
||||
results.sort((a, b) => b.originServerTs.compareTo(a.originServerTs));
|
||||
return results.take(totalCap).toList();
|
||||
}
|
||||
|
||||
void _openRoom(String roomId, {String? eventId}) {
|
||||
widget.onClose();
|
||||
ref.read(activeRoomIdProvider.notifier).state = roomId;
|
||||
|
||||
Reference in New Issue
Block a user