Search messages!

♻️ Optimize messages loading, syncing
This commit is contained in:
2025-08-26 01:05:30 +08:00
parent 596d212593
commit b48a1aac44
7 changed files with 280 additions and 29 deletions

View File

@@ -283,6 +283,9 @@ class MessagesNotifier extends _$MessagesNotifier {
final Map<String, LocalChatMessage> _pendingMessages = {};
final Map<String, Map<int, double>> _fileUploadProgress = {};
int? _totalCount;
String? _searchQuery;
bool? _withLinks;
bool? _withAttachments;
late final String _roomId;
int _currentPage = 0;
@@ -326,7 +329,13 @@ class MessagesNotifier extends _$MessagesNotifier {
});
}
return await loadInitial();
loadInitial();
return [];
}
List<LocalChatMessage> _sortMessages(List<LocalChatMessage> messages) {
messages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
return messages;
}
Future<List<LocalChatMessage>> _getCachedMessages({
@@ -337,13 +346,29 @@ class MessagesNotifier extends _$MessagesNotifier {
'Getting cached messages from offset $offset, take $take',
name: 'MessagesNotifier',
);
final dbMessages = await _database.getMessagesForRoom(
_roomId,
offset: offset,
limit: take,
);
final dbLocalMessages =
dbMessages.map(_database.companionToMessage).toList();
final List<LocalChatMessage> dbMessages;
if (_searchQuery != null && _searchQuery!.isNotEmpty) {
dbMessages = await _database.searchMessages(_roomId, _searchQuery ?? '');
} else {
final chatMessagesFromDb = await _database.getMessagesForRoom(
_roomId,
offset: offset,
limit: take,
);
dbMessages = chatMessagesFromDb.map(_database.companionToMessage).toList();
}
List<LocalChatMessage> filteredMessages = dbMessages;
if (_withLinks == true) {
filteredMessages = filteredMessages.where((msg) => _hasLink(msg)).toList();
}
if (_withAttachments == true) {
filteredMessages = filteredMessages.where((msg) => _hasAttachment(msg)).toList();
}
final dbLocalMessages = filteredMessages;
if (offset == 0) {
final pendingForRoom =
@@ -352,7 +377,7 @@ class MessagesNotifier extends _$MessagesNotifier {
.toList();
final allMessages = [...pendingForRoom, ...dbLocalMessages];
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
_sortMessages(allMessages); // Use the helper function
final uniqueMessages = <LocalChatMessage>[];
final seenIds = <String>{};
@@ -427,7 +452,7 @@ class MessagesNotifier extends _$MessagesNotifier {
_isSyncing = true;
developer.log('Starting message sync', name: 'MessagesNotifier');
ref.read(isSyncingProvider.notifier).state = true;
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true);
try {
final dbMessages = await _database.getMessagesForRoom(
_room.id,
@@ -488,7 +513,7 @@ class MessagesNotifier extends _$MessagesNotifier {
showErrorAlert(err);
} finally {
developer.log('Finished message sync', name: 'MessagesNotifier');
ref.read(isSyncingProvider.notifier).state = false;
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = false);
_isSyncing = false;
}
}
@@ -499,7 +524,7 @@ class MessagesNotifier extends _$MessagesNotifier {
bool synced = false,
}) async {
try {
if (offset == 0 && !synced) {
if (offset == 0 && !synced && (_searchQuery == null || _searchQuery!.isEmpty)) {
_fetchAndCacheMessages(offset: 0, take: take).catchError((_) {
return <LocalChatMessage>[];
});
@@ -514,7 +539,11 @@ class MessagesNotifier extends _$MessagesNotifier {
return localMessages;
}
return await _fetchAndCacheMessages(offset: offset, take: take);
if (_searchQuery == null || _searchQuery!.isEmpty) {
return await _fetchAndCacheMessages(offset: offset, take: take);
} else {
return []; // If searching, and no local messages, don't fetch from network
}
} catch (e) {
final localMessages = await _getCachedMessages(
offset: offset,
@@ -528,13 +557,15 @@ class MessagesNotifier extends _$MessagesNotifier {
}
}
Future<List<LocalChatMessage>> loadInitial() async {
Future<void> loadInitial() async {
developer.log('Loading initial messages', name: 'MessagesNotifier');
syncMessages();
if (_searchQuery == null || _searchQuery!.isEmpty) {
syncMessages();
}
final messages = await _getCachedMessages(offset: 0, take: 100);
_currentPage = 0;
_hasMore = messages.length == _pageSize;
return messages;
state = AsyncValue.data(messages);
}
Future<void> loadMore() async {
@@ -553,7 +584,7 @@ class MessagesNotifier extends _$MessagesNotifier {
_hasMore = false;
}
state = AsyncValue.data([...currentMessages, ...newMessages]);
state = AsyncValue.data(_sortMessages([...currentMessages, ...newMessages]));
} catch (err, stackTrace) {
developer.log(
'Error loading more messages',
@@ -778,7 +809,7 @@ class MessagesNotifier extends _$MessagesNotifier {
}
return m;
}).toList();
state = AsyncValue.data(newMessages);
state = AsyncValue.data(_sortMessages(newMessages));
showErrorAlert(e);
}
}
@@ -838,7 +869,7 @@ class MessagesNotifier extends _$MessagesNotifier {
if (index >= 0) {
final newList = [...currentMessages];
newList[index] = updatedMessage;
state = AsyncValue.data(newList);
state = AsyncValue.data(_sortMessages(newList));
}
}
@@ -898,6 +929,20 @@ class MessagesNotifier extends _$MessagesNotifier {
}
}
void searchMessages(String query, {bool? withLinks, bool? withAttachments}) {
_searchQuery = query.trim();
_withLinks = withLinks;
_withAttachments = withAttachments;
loadInitial();
}
void clearSearch() {
_searchQuery = null;
_withLinks = null;
_withAttachments = null;
loadInitial();
}
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
developer.log(
'Fetching message by id $messageId',
@@ -927,6 +972,18 @@ class MessagesNotifier extends _$MessagesNotifier {
rethrow;
}
}
bool _hasLink(LocalChatMessage message) {
final content = message.toRemoteMessage().content;
if (content == null) return false;
final urlRegex = RegExp(r'https?://[^\s/$.?#].[^\s]*');
return urlRegex.hasMatch(content);
}
bool _hasAttachment(LocalChatMessage message) {
final remoteMessage = message.toRemoteMessage();
return remoteMessage.attachments.isNotEmpty;
}
}
class ChatRoomScreen extends HookConsumerWidget {
@@ -1424,6 +1481,12 @@ class ChatRoomScreen extends HookConsumerWidget {
),
),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
context.pushNamed('searchMessages', pathParameters: {'id': id});
},
),
AudioCallButton(roomId: id),
IconButton(
icon: const Icon(Icons.more_vert),
@@ -1433,15 +1496,14 @@ class ChatRoomScreen extends HookConsumerWidget {
),
const Gap(8),
],
bottom:
isSyncing
? const PreferredSize(
preferredSize: Size.fromHeight(2),
child: LinearProgressIndicator(
borderRadius: BorderRadius.zero,
),
)
: null,
bottom: isSyncing
? const PreferredSize(
preferredSize: Size.fromHeight(2),
child: LinearProgressIndicator(
borderRadius: BorderRadius.zero,
),
)
: null,
),
body: Stack(
children: [