✨ Search messages!
♻️ Optimize messages loading, syncing
This commit is contained in:
@@ -40,6 +40,8 @@ PODS:
|
|||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- file_saver (0.0.1):
|
||||||
|
- Flutter
|
||||||
- Firebase/CoreOnly (12.0.0):
|
- Firebase/CoreOnly (12.0.0):
|
||||||
- FirebaseCore (~> 12.0.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- Firebase/Crashlytics (12.0.0):
|
- Firebase/Crashlytics (12.0.0):
|
||||||
@@ -303,6 +305,7 @@ DEPENDENCIES:
|
|||||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
|
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
||||||
@@ -381,6 +384,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
|
file_saver:
|
||||||
|
:path: ".symlinks/plugins/file_saver/ios"
|
||||||
firebase_analytics:
|
firebase_analytics:
|
||||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
:path: ".symlinks/plugins/firebase_analytics/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
@@ -464,6 +469,7 @@ SPEC CHECKSUMS:
|
|||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
|
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
|
||||||
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||||
firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d
|
firebase_analytics: cd56fc56f75c1df30a6ff5290cd56e230996a76d
|
||||||
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4
|
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4
|
||||||
|
@@ -68,6 +68,30 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
return (delete(chatMessages)..where((m) => m.id.equals(id))).go();
|
return (delete(chatMessages)..where((m) => m.id.equals(id))).go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<LocalChatMessage>> searchMessages(
|
||||||
|
String roomId,
|
||||||
|
String query,
|
||||||
|
) async {
|
||||||
|
var selectStatement = select(chatMessages)
|
||||||
|
..where((m) => m.roomId.equals(roomId));
|
||||||
|
|
||||||
|
if (query.isNotEmpty) {
|
||||||
|
selectStatement =
|
||||||
|
selectStatement
|
||||||
|
..where((m) => m.content.like('%${query.toLowerCase()}%'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
final messages =
|
||||||
|
await (selectStatement
|
||||||
|
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
|
||||||
|
.get();
|
||||||
|
return messages.map((msg) => companionToMessage(msg)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
// Convert between Drift and model objects
|
// Convert between Drift and model objects
|
||||||
ChatMessagesCompanion messageToCompanion(LocalChatMessage message) {
|
ChatMessagesCompanion messageToCompanion(LocalChatMessage message) {
|
||||||
return ChatMessagesCompanion(
|
return ChatMessagesCompanion(
|
||||||
|
@@ -8,7 +8,7 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
@@ -38,6 +38,7 @@ import 'package:island/screens/chat/chat.dart';
|
|||||||
import 'package:island/screens/chat/room.dart';
|
import 'package:island/screens/chat/room.dart';
|
||||||
import 'package:island/screens/chat/room_detail.dart';
|
import 'package:island/screens/chat/room_detail.dart';
|
||||||
import 'package:island/screens/chat/call.dart';
|
import 'package:island/screens/chat/call.dart';
|
||||||
|
import 'package:island/screens/chat/search_messages_screen.dart';
|
||||||
import 'package:island/screens/creators/hub.dart';
|
import 'package:island/screens/creators/hub.dart';
|
||||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
||||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||||
@@ -555,6 +556,14 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return ChatDetailScreen(id: id);
|
return ChatDetailScreen(id: id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: 'searchMessages',
|
||||||
|
path: '/chat/:id/search',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return SearchMessagesScreen(roomId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@@ -283,6 +283,9 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
final Map<String, LocalChatMessage> _pendingMessages = {};
|
final Map<String, LocalChatMessage> _pendingMessages = {};
|
||||||
final Map<String, Map<int, double>> _fileUploadProgress = {};
|
final Map<String, Map<int, double>> _fileUploadProgress = {};
|
||||||
int? _totalCount;
|
int? _totalCount;
|
||||||
|
String? _searchQuery;
|
||||||
|
bool? _withLinks;
|
||||||
|
bool? _withAttachments;
|
||||||
|
|
||||||
late final String _roomId;
|
late final String _roomId;
|
||||||
int _currentPage = 0;
|
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({
|
Future<List<LocalChatMessage>> _getCachedMessages({
|
||||||
@@ -337,13 +346,29 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
'Getting cached messages from offset $offset, take $take',
|
'Getting cached messages from offset $offset, take $take',
|
||||||
name: 'MessagesNotifier',
|
name: 'MessagesNotifier',
|
||||||
);
|
);
|
||||||
final dbMessages = await _database.getMessagesForRoom(
|
final List<LocalChatMessage> dbMessages;
|
||||||
|
if (_searchQuery != null && _searchQuery!.isNotEmpty) {
|
||||||
|
dbMessages = await _database.searchMessages(_roomId, _searchQuery ?? '');
|
||||||
|
} else {
|
||||||
|
final chatMessagesFromDb = await _database.getMessagesForRoom(
|
||||||
_roomId,
|
_roomId,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: take,
|
limit: take,
|
||||||
);
|
);
|
||||||
final dbLocalMessages =
|
dbMessages = chatMessagesFromDb.map(_database.companionToMessage).toList();
|
||||||
dbMessages.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) {
|
if (offset == 0) {
|
||||||
final pendingForRoom =
|
final pendingForRoom =
|
||||||
@@ -352,7 +377,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final allMessages = [...pendingForRoom, ...dbLocalMessages];
|
final allMessages = [...pendingForRoom, ...dbLocalMessages];
|
||||||
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
_sortMessages(allMessages); // Use the helper function
|
||||||
|
|
||||||
final uniqueMessages = <LocalChatMessage>[];
|
final uniqueMessages = <LocalChatMessage>[];
|
||||||
final seenIds = <String>{};
|
final seenIds = <String>{};
|
||||||
@@ -427,7 +452,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
_isSyncing = true;
|
_isSyncing = true;
|
||||||
|
|
||||||
developer.log('Starting message sync', name: 'MessagesNotifier');
|
developer.log('Starting message sync', name: 'MessagesNotifier');
|
||||||
ref.read(isSyncingProvider.notifier).state = true;
|
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true);
|
||||||
try {
|
try {
|
||||||
final dbMessages = await _database.getMessagesForRoom(
|
final dbMessages = await _database.getMessagesForRoom(
|
||||||
_room.id,
|
_room.id,
|
||||||
@@ -488,7 +513,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
} finally {
|
} finally {
|
||||||
developer.log('Finished message sync', name: 'MessagesNotifier');
|
developer.log('Finished message sync', name: 'MessagesNotifier');
|
||||||
ref.read(isSyncingProvider.notifier).state = false;
|
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = false);
|
||||||
_isSyncing = false;
|
_isSyncing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,7 +524,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
bool synced = false,
|
bool synced = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
if (offset == 0 && !synced) {
|
if (offset == 0 && !synced && (_searchQuery == null || _searchQuery!.isEmpty)) {
|
||||||
_fetchAndCacheMessages(offset: 0, take: take).catchError((_) {
|
_fetchAndCacheMessages(offset: 0, take: take).catchError((_) {
|
||||||
return <LocalChatMessage>[];
|
return <LocalChatMessage>[];
|
||||||
});
|
});
|
||||||
@@ -514,7 +539,11 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
return localMessages;
|
return localMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_searchQuery == null || _searchQuery!.isEmpty) {
|
||||||
return await _fetchAndCacheMessages(offset: offset, take: take);
|
return await _fetchAndCacheMessages(offset: offset, take: take);
|
||||||
|
} else {
|
||||||
|
return []; // If searching, and no local messages, don't fetch from network
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final localMessages = await _getCachedMessages(
|
final localMessages = await _getCachedMessages(
|
||||||
offset: offset,
|
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');
|
developer.log('Loading initial messages', name: 'MessagesNotifier');
|
||||||
|
if (_searchQuery == null || _searchQuery!.isEmpty) {
|
||||||
syncMessages();
|
syncMessages();
|
||||||
|
}
|
||||||
final messages = await _getCachedMessages(offset: 0, take: 100);
|
final messages = await _getCachedMessages(offset: 0, take: 100);
|
||||||
_currentPage = 0;
|
_currentPage = 0;
|
||||||
_hasMore = messages.length == _pageSize;
|
_hasMore = messages.length == _pageSize;
|
||||||
return messages;
|
state = AsyncValue.data(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadMore() async {
|
Future<void> loadMore() async {
|
||||||
@@ -553,7 +584,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
_hasMore = false;
|
_hasMore = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = AsyncValue.data([...currentMessages, ...newMessages]);
|
state = AsyncValue.data(_sortMessages([...currentMessages, ...newMessages]));
|
||||||
} catch (err, stackTrace) {
|
} catch (err, stackTrace) {
|
||||||
developer.log(
|
developer.log(
|
||||||
'Error loading more messages',
|
'Error loading more messages',
|
||||||
@@ -778,7 +809,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
}).toList();
|
}).toList();
|
||||||
state = AsyncValue.data(newMessages);
|
state = AsyncValue.data(_sortMessages(newMessages));
|
||||||
showErrorAlert(e);
|
showErrorAlert(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -838,7 +869,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
final newList = [...currentMessages];
|
final newList = [...currentMessages];
|
||||||
newList[index] = updatedMessage;
|
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 {
|
Future<LocalChatMessage?> fetchMessageById(String messageId) async {
|
||||||
developer.log(
|
developer.log(
|
||||||
'Fetching message by id $messageId',
|
'Fetching message by id $messageId',
|
||||||
@@ -927,6 +972,18 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
rethrow;
|
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 {
|
class ChatRoomScreen extends HookConsumerWidget {
|
||||||
@@ -1424,6 +1481,12 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed('searchMessages', pathParameters: {'id': id});
|
||||||
|
},
|
||||||
|
),
|
||||||
AudioCallButton(roomId: id),
|
AudioCallButton(roomId: id),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
@@ -1433,8 +1496,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
bottom:
|
bottom: isSyncing
|
||||||
isSyncing
|
|
||||||
? const PreferredSize(
|
? const PreferredSize(
|
||||||
preferredSize: Size.fromHeight(2),
|
preferredSize: Size.fromHeight(2),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
|
@@ -359,6 +359,17 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
: const Text('chatBreakNone').tr(),
|
: const Text('chatBreakNone').tr(),
|
||||||
onTap: () => showChatBreakDialog(),
|
onTap: () => showChatBreakDialog(),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: const Text('Search Messages').tr(),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('searchMessages', pathParameters: {'id': id});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
|
139
lib/screens/chat/search_messages_screen.dart
Normal file
139
lib/screens/chat/search_messages_screen.dart
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/screens/chat/room.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/chat/message_item.dart';
|
||||||
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||||
|
|
||||||
|
class SearchMessagesScreen extends HookConsumerWidget {
|
||||||
|
final String roomId;
|
||||||
|
|
||||||
|
const SearchMessagesScreen({super.key, required this.roomId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final searchController = useTextEditingController();
|
||||||
|
final withLinks = useState(false);
|
||||||
|
final withAttachments = useState(false);
|
||||||
|
|
||||||
|
final messagesNotifier = ref.read(
|
||||||
|
messagesNotifierProvider(roomId).notifier,
|
||||||
|
);
|
||||||
|
final messages = ref.watch(messagesNotifierProvider(roomId));
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
// Clear search when screen is disposed
|
||||||
|
return () {
|
||||||
|
messagesNotifier.clearSearch();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: const Text('Search Messages')),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Search messages...',
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 12,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
suffix: IconButton(
|
||||||
|
iconSize: 18,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
searchController.clear();
|
||||||
|
messagesNotifier.clearSearch();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (query) {
|
||||||
|
messagesNotifier.searchMessages(
|
||||||
|
query,
|
||||||
|
withLinks: withLinks.value,
|
||||||
|
withAttachments: withAttachments.value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.link),
|
||||||
|
title: const Text('Links'),
|
||||||
|
value: withLinks.value,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
withLinks.value = value!;
|
||||||
|
messagesNotifier.searchMessages(
|
||||||
|
searchController.text,
|
||||||
|
withLinks: withLinks.value,
|
||||||
|
withAttachments: withAttachments.value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.file_copy),
|
||||||
|
title: const Text('Attachments'),
|
||||||
|
value: withAttachments.value,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
withAttachments.value = value!;
|
||||||
|
messagesNotifier.searchMessages(
|
||||||
|
searchController.text,
|
||||||
|
withLinks: withLinks.value,
|
||||||
|
withAttachments: withAttachments.value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: messages.when(
|
||||||
|
data:
|
||||||
|
(messageList) =>
|
||||||
|
messageList.isEmpty
|
||||||
|
? Center(child: Text('No messages found'.tr()))
|
||||||
|
: SuperListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
reverse: true, // Show newest messages at the bottom
|
||||||
|
itemCount: messageList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final message = messageList[index];
|
||||||
|
// Simplified MessageItem for search results, no grouping logic
|
||||||
|
return MessageItem(
|
||||||
|
message: message,
|
||||||
|
isCurrentUser:
|
||||||
|
false, // Or determine based on actual user
|
||||||
|
onAction: null,
|
||||||
|
onJump: (_) {},
|
||||||
|
progress: null,
|
||||||
|
showAvatar: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, _) => Center(child: Text('Error: $error')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user