From 31b83b2d27038530e1bd972961937692498f1264 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 4 Dec 2025 22:10:07 +0800 Subject: [PATCH] :recycle: Refactored the chat loading to use more local data --- lib/database/drift_db.dart | 36 +- lib/pods/chat/chat_subscribe.dart | 18 +- lib/pods/chat/chat_subscribe.g.dart | 2 +- lib/pods/chat/messages_notifier.dart | 52 ++- lib/pods/chat/messages_notifier.g.dart | 2 +- lib/screens/chat/chat.dart | 501 +++++++++++++++++----- lib/screens/chat/chat.g.dart | 402 +++++++++-------- lib/screens/chat/chat_form.dart | 2 +- lib/screens/chat/public_room_preview.dart | 2 +- lib/screens/chat/room.dart | 39 +- lib/screens/chat/room_detail.dart | 22 +- lib/widgets/chat/chat_input.dart | 15 +- lib/widgets/chat/public_room_preview.dart | 2 +- lib/widgets/share/share_sheet.dart | 2 +- 14 files changed, 731 insertions(+), 366 deletions(-) diff --git a/lib/database/drift_db.dart b/lib/database/drift_db.dart index 02100504..b7e86fde 100644 --- a/lib/database/drift_db.dart +++ b/lib/database/drift_db.dart @@ -358,23 +358,29 @@ class AppDatabase extends _$AppDatabase { ); } - Future saveChatRooms(List rooms) async { + Future saveChatRooms( + List rooms, { + bool override = false, + }) async { await transaction(() async { - // 1. Identify rooms to remove - final remoteRoomIds = rooms.map((r) => r.id).toSet(); - final currentRooms = await select(chatRooms).get(); - final currentRoomIds = currentRooms.map((r) => r.id).toSet(); - final idsToRemove = currentRoomIds.difference(remoteRoomIds); + if (override) { + // 1. Identify rooms to remove + final remoteRoomIds = rooms.map((r) => r.id).toSet(); + final currentRooms = await select(chatRooms).get(); + final currentRoomIds = currentRooms.map((r) => r.id).toSet(); + final idsToRemove = currentRoomIds.difference(remoteRoomIds); - if (idsToRemove.isNotEmpty) { - final idsList = idsToRemove.toList(); - // Remove messages - await (delete(chatMessages)..where((t) => t.roomId.isIn(idsList))).go(); - // Remove members - await (delete(chatMembers) - ..where((t) => t.chatRoomId.isIn(idsList))).go(); - // Remove rooms - await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go(); + if (idsToRemove.isNotEmpty) { + final idsList = idsToRemove.toList(); + // Remove messages + await (delete(chatMessages) + ..where((t) => t.roomId.isIn(idsList))).go(); + // Remove members + await (delete(chatMembers) + ..where((t) => t.chatRoomId.isIn(idsList))).go(); + // Remove rooms + await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go(); + } } // 2. Upsert remote rooms diff --git a/lib/pods/chat/chat_subscribe.dart b/lib/pods/chat/chat_subscribe.dart index 4b27d589..7961c003 100644 --- a/lib/pods/chat/chat_subscribe.dart +++ b/lib/pods/chat/chat_subscribe.dart @@ -16,10 +16,9 @@ final currentSubscribedChatIdProvider = StateProvider((ref) => null); @riverpod class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { - late final String _roomId; - late final SnChatRoom _chatRoom; - late final SnChatMember _chatIdentity; - late final MessagesNotifier _messagesNotifier; + late SnChatRoom _chatRoom; + late SnChatMember _chatIdentity; + late MessagesNotifier _messagesNotifier; final List _typingStatuses = []; Timer? _typingCleanupTimer; @@ -29,10 +28,11 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { @override List build(String roomId) { - _roomId = roomId; final ws = ref.watch(websocketProvider); - final chatRoomAsync = ref.watch(chatroomProvider(roomId)); - final chatIdentityAsync = ref.watch(chatroomIdentityProvider(roomId)); + final chatRoomAsync = ref.watch(ChatRoomNotifierProvider(roomId)); + final chatIdentityAsync = ref.watch( + ChatRoomIdentityNotifierProvider(roomId), + ); _messagesNotifier = ref.watch(messagesNotifierProvider(roomId).notifier); if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) { @@ -199,7 +199,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { jsonEncode( WebSocketPacket( type: 'messages.read', - data: {'chat_room_id': _roomId}, + data: {'chat_room_id': roomId}, endpoint: 'sphere', ), ), @@ -216,7 +216,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { jsonEncode( WebSocketPacket( type: 'messages.typing', - data: {'chat_room_id': _roomId}, + data: {'chat_room_id': roomId}, endpoint: 'sphere', ), ), diff --git a/lib/pods/chat/chat_subscribe.g.dart b/lib/pods/chat/chat_subscribe.g.dart index 27d24a4d..9df2595e 100644 --- a/lib/pods/chat/chat_subscribe.g.dart +++ b/lib/pods/chat/chat_subscribe.g.dart @@ -7,7 +7,7 @@ part of 'chat_subscribe.dart'; // ************************************************************************** String _$chatSubscribeNotifierHash() => - r'c605e0c9c45df64e5ba7b65f8de9b47bde8e2b3b'; + r'beec1ddf2e13f6d5af8a08c2c81eff740ae9b986'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/pods/chat/messages_notifier.dart b/lib/pods/chat/messages_notifier.dart index 32e40fc1..33e6c850 100644 --- a/lib/pods/chat/messages_notifier.dart +++ b/lib/pods/chat/messages_notifier.dart @@ -27,10 +27,10 @@ part 'messages_notifier.g.dart'; @riverpod class MessagesNotifier extends _$MessagesNotifier { - late final Dio _apiClient; - late final AppDatabase _database; - late final SnChatRoom _room; - late final SnChatMember _identity; + late Dio _apiClient; + late AppDatabase _database; + late SnChatRoom _room; + late SnChatMember _identity; final Map _pendingMessages = {}; final Map> _fileUploadProgress = {}; @@ -39,7 +39,6 @@ class MessagesNotifier extends _$MessagesNotifier { bool? _withLinks; bool? _withAttachments; - late final String _roomId; static const int _pageSize = 20; bool _hasMore = true; bool _isSyncing = false; @@ -48,15 +47,16 @@ class MessagesNotifier extends _$MessagesNotifier { bool _allRemoteMessagesFetched = false; DateTime? _lastPauseTime; - late final Future Function(String) _fetchAccount; + late Future Function(String) _fetchAccount; @override FutureOr> build(String roomId) async { - _roomId = roomId; _apiClient = ref.watch(apiClientProvider); _database = ref.watch(databaseProvider); - final room = await ref.watch(chatroomProvider(roomId).future); - final identity = await ref.watch(chatroomIdentityProvider(roomId).future); + final room = await ref.watch(ChatRoomNotifierProvider(roomId).future); + final identity = await ref.watch( + ChatRoomIdentityNotifierProvider(roomId).future, + ); // Initialize fetch account method for corrupted data recovery _fetchAccount = (String accountId) async { @@ -144,14 +144,14 @@ class MessagesNotifier extends _$MessagesNotifier { if (searchQuery != null && searchQuery.isNotEmpty) { dbMessages = await _database.searchMessages( - _roomId, + roomId, searchQuery, withAttachments: withAttachments, fetchAccount: _fetchAccount, ); } else { final chatMessagesFromDb = await _database.getMessagesForRoom( - _roomId, + roomId, offset: offset, limit: take, ); @@ -194,9 +194,7 @@ class MessagesNotifier extends _$MessagesNotifier { if (offset == 0) { final pendingForRoom = - _pendingMessages.values - .where((msg) => msg.roomId == _roomId) - .toList(); + _pendingMessages.values.where((msg) => msg.roomId == roomId).toList(); final allMessages = [...pendingForRoom, ...uniqueMessages]; _sortMessages(allMessages); // Use the helper function @@ -221,7 +219,7 @@ class MessagesNotifier extends _$MessagesNotifier { }) async { talker.log('Getting all messages for jump from offset $offset, take $take'); final chatMessagesFromDb = await _database.getMessagesForRoom( - _roomId, + roomId, offset: offset, limit: take, ); @@ -245,9 +243,7 @@ class MessagesNotifier extends _$MessagesNotifier { if (offset == 0) { final pendingForRoom = - _pendingMessages.values - .where((msg) => msg.roomId == _roomId) - .toList(); + _pendingMessages.values.where((msg) => msg.roomId == roomId).toList(); final allMessages = [...pendingForRoom, ...uniqueMessages]; _sortMessages(allMessages); @@ -272,7 +268,7 @@ class MessagesNotifier extends _$MessagesNotifier { talker.log('Fetching messages from API, offset $offset, take $take'); if (_totalCount == null) { final response = await _apiClient.get( - '/sphere/chat/$_roomId/messages', + '/sphere/chat/$roomId/messages', queryParameters: {'offset': 0, 'take': 1}, ); _totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0'); @@ -284,7 +280,7 @@ class MessagesNotifier extends _$MessagesNotifier { } final response = await _apiClient.get( - '/sphere/chat/$_roomId/messages', + '/sphere/chat/$roomId/messages', queryParameters: {'offset': offset, 'take': take}, ); @@ -546,7 +542,7 @@ class MessagesNotifier extends _$MessagesNotifier { final mockMessage = SnChatMessage( id: 'pending_$nonce', - chatRoomId: _roomId, + chatRoomId: roomId, senderId: _identity.id, content: content, createdAt: DateTime.now(), @@ -590,8 +586,8 @@ class MessagesNotifier extends _$MessagesNotifier { final response = await _apiClient.request( editingTo == null - ? '/sphere/chat/$_roomId/messages' - : '/sphere/chat/$_roomId/messages/${editingTo.id}', + ? '/sphere/chat/$roomId/messages' + : '/sphere/chat/$roomId/messages/${editingTo.id}', data: { 'content': content, 'attachments_id': cloudAttachments.map((e) => e.id).toList(), @@ -731,7 +727,7 @@ class MessagesNotifier extends _$MessagesNotifier { } Future receiveMessage(SnChatMessage remoteMessage) async { - if (remoteMessage.chatRoomId != _roomId) return; + if (remoteMessage.chatRoomId != roomId) return; // Block message receiving during jumps to prevent list resets if (_isJumping) { @@ -783,7 +779,7 @@ class MessagesNotifier extends _$MessagesNotifier { } Future receiveMessageUpdate(SnChatMessage remoteMessage) async { - if (remoteMessage.chatRoomId != _roomId) return; + if (remoteMessage.chatRoomId != roomId) return; // Block message updates during jumps to prevent list resets if (_isJumping) { @@ -883,7 +879,7 @@ class MessagesNotifier extends _$MessagesNotifier { } try { - await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId'); + await _apiClient.delete('/sphere/chat/$roomId/messages/$messageId'); await receiveMessageDeletion(messageId); } catch (err, stackTrace) { talker.log( @@ -991,7 +987,7 @@ class MessagesNotifier extends _$MessagesNotifier { } final response = await _apiClient.get( - '/sphere/chat/$_roomId/messages/$messageId', + '/sphere/chat/$roomId/messages/$messageId', ); final remoteMessage = SnChatMessage.fromJson(response.data); final message = LocalChatMessage.fromRemoteMessage( @@ -1048,7 +1044,7 @@ class MessagesNotifier extends _$MessagesNotifier { final query = _database.customSelect( 'SELECT COUNT(*) as count FROM chat_messages WHERE room_id = ? AND created_at > ?', variables: [ - Variable.withString(_roomId), + Variable.withString(roomId), Variable.withDateTime(message.createdAt), ], readsFrom: {_database.chatMessages}, diff --git a/lib/pods/chat/messages_notifier.g.dart b/lib/pods/chat/messages_notifier.g.dart index 9ae8989b..ce546970 100644 --- a/lib/pods/chat/messages_notifier.g.dart +++ b/lib/pods/chat/messages_notifier.g.dart @@ -6,7 +6,7 @@ part of 'messages_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$messagesNotifierHash() => r'27ce32c54e317a04e1d554ed4a70a24e4503fdd1'; +String _$messagesNotifierHash() => r'd76d799494b06fac2adc42d94b7ecd7b8d68c352'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 29276f37..5a373566 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/chat.dart'; import 'package:island/models/file.dart'; import 'package:island/models/account.dart'; +import 'package:island/database/drift_db.dart'; import 'package:island/pods/database.dart'; import 'package:island/pods/chat/chat_summary.dart'; import 'package:island/pods/network.dart'; @@ -184,87 +185,148 @@ class ChatRoomListTile extends HookConsumerWidget { } @riverpod -Future> chatroomsJoined(Ref ref) async { - final db = ref.watch(databaseProvider); +class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier { + @override + Future> build() async { + final db = ref.watch(databaseProvider); - try { + try { + final localRoomsData = await db.select(db.chatRooms).get(); + if (localRoomsData.isNotEmpty) { + final localRooms = await Future.wait( + localRoomsData.map((row) async { + final membersRows = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(row.id))).get(); + final members = + membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + return SnChatRoom( + id: row.id, + name: row.name, + description: row.description, + type: row.type, + isPublic: row.isPublic!, + isCommunity: row.isCommunity!, + picture: + row.picture != null + ? SnCloudFile.fromJson(row.picture!) + : null, + background: + row.background != null + ? SnCloudFile.fromJson(row.background!) + : null, + realmId: row.realmId, + accountId: row.accountId, + realm: null, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + deletedAt: row.deletedAt, + members: members, + ); + }), + ); + + // Background sync + Future(() async { + try { + final client = ref.read(apiClientProvider); + final resp = await client.get('/sphere/chat'); + final remoteRooms = + resp.data + .map((e) => SnChatRoom.fromJson(e)) + .cast() + .toList(); + await db.saveChatRooms(remoteRooms, override: true); + // Update state with fresh data + state = AsyncData(await _buildRoomsFromDb(db)); + } catch (_) {} + }).ignore(); + + return localRooms; + } + } catch (_) {} + + // Fallback to API + final client = ref.watch(apiClientProvider); + final resp = await client.get('/sphere/chat'); + final rooms = + resp.data + .map((e) => SnChatRoom.fromJson(e)) + .cast() + .toList(); + await db.saveChatRooms(rooms, override: true); + return rooms; + } + + Future> _buildRoomsFromDb(AppDatabase db) async { final localRoomsData = await db.select(db.chatRooms).get(); - if (localRoomsData.isNotEmpty) { - final localRooms = await Future.wait( - localRoomsData.map((row) async { - final membersRows = - await (db.select(db.chatMembers) - ..where((m) => m.chatRoomId.equals(row.id))).get(); - final members = - membersRows.map((mRow) { - final account = SnAccount.fromJson(mRow.account); - return SnChatMember( - id: mRow.id, - chatRoomId: mRow.chatRoomId, - accountId: mRow.accountId, - account: account, - nick: mRow.nick, - notify: mRow.notify, - joinedAt: mRow.joinedAt, - breakUntil: mRow.breakUntil, - timeoutUntil: mRow.timeoutUntil, - status: null, - createdAt: mRow.createdAt, - updatedAt: mRow.updatedAt, - deletedAt: mRow.deletedAt, - chatRoom: null, - ); - }).toList(); - return SnChatRoom( - id: row.id, - name: row.name, - description: row.description, - type: row.type, - isPublic: row.isPublic!, - isCommunity: row.isCommunity!, - picture: - row.picture != null ? SnCloudFile.fromJson(row.picture!) : null, - background: - row.background != null - ? SnCloudFile.fromJson(row.background!) - : null, - realmId: row.realmId, - accountId: row.accountId, - realm: null, - createdAt: row.createdAt, - updatedAt: row.updatedAt, - deletedAt: row.deletedAt, - members: members, - ); - }), - ); - - // Background sync - Future(() async { - try { - final client = ref.read(apiClientProvider); - final resp = await client.get('/sphere/chat'); - final remoteRooms = - resp.data - .map((e) => SnChatRoom.fromJson(e)) - .cast() - .toList(); - await db.saveChatRooms(remoteRooms); - ref.invalidateSelf(); - } catch (_) {} - }).ignore(); - - return localRooms; - } - } catch (_) {} - - // Fallback to API - final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat'); - final rooms = - resp.data.map((e) => SnChatRoom.fromJson(e)).cast().toList(); - await db.saveChatRooms(rooms); - return rooms; + return Future.wait( + localRoomsData.map((row) async { + final membersRows = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(row.id))).get(); + final members = + membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + return SnChatRoom( + id: row.id, + name: row.name, + description: row.description, + type: row.type, + isPublic: row.isPublic!, + isCommunity: row.isCommunity!, + picture: + row.picture != null ? SnCloudFile.fromJson(row.picture!) : null, + background: + row.background != null + ? SnCloudFile.fromJson(row.background!) + : null, + realmId: row.realmId, + accountId: row.accountId, + realm: null, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + deletedAt: row.deletedAt, + members: members, + ); + }), + ); + } } class ChatListBodyWidget extends HookConsumerWidget { @@ -281,7 +343,7 @@ class ChatListBodyWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final chats = ref.watch(chatroomsJoinedProvider); + final chats = ref.watch(chatRoomJoinedNotifierProvider); Widget bodyWidget = Column( children: [ @@ -304,7 +366,7 @@ class ChatListBodyWidget extends HookConsumerWidget { (items) => RefreshIndicator( onRefresh: () => Future.sync(() { - ref.invalidate(chatroomsJoinedProvider); + ref.invalidate(chatRoomJoinedNotifierProvider); }), child: SuperListView.builder( padding: EdgeInsets.only(bottom: 96), @@ -354,7 +416,7 @@ class ChatListBodyWidget extends HookConsumerWidget { (error, stack) => ResponseErrorWidget( error: error, onRetry: () { - ref.invalidate(chatroomsJoinedProvider); + ref.invalidate(chatRoomJoinedNotifierProvider); }, ), ), @@ -431,7 +493,7 @@ class ChatListScreen extends HookConsumerWidget { // Listen for chat rooms refresh events final subscription = eventBus.on().listen((event) { - ref.invalidate(chatroomsJoinedProvider); + ref.invalidate(chatRoomJoinedNotifierProvider); }); return () { @@ -600,32 +662,263 @@ class ChatListScreen extends HookConsumerWidget { } @riverpod -Future chatroom(Ref ref, String? identifier) async { - if (identifier == null) return null; - try { - final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat/$identifier'); - return SnChatRoom.fromJson(resp.data); - } catch (err) { - if (err is DioException && err.response?.statusCode == 404) { - return null; // Chat room not found +class ChatRoomNotifier extends _$ChatRoomNotifier { + @override + Future build(String? identifier) async { + if (identifier == null) return null; + final db = ref.watch(databaseProvider); + + try { + // Try to get from local database first + final localRoomData = + await (db.select(db.chatRooms) + ..where((r) => r.id.equals(identifier))).getSingleOrNull(); + + if (localRoomData != null) { + // Fetch members for this room + final membersRows = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(localRoomData.id))).get(); + final members = + membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + + final localRoom = SnChatRoom( + id: localRoomData.id, + name: localRoomData.name, + description: localRoomData.description, + type: localRoomData.type, + isPublic: localRoomData.isPublic!, + isCommunity: localRoomData.isCommunity!, + picture: + localRoomData.picture != null + ? SnCloudFile.fromJson(localRoomData.picture!) + : null, + background: + localRoomData.background != null + ? SnCloudFile.fromJson(localRoomData.background!) + : null, + realmId: localRoomData.realmId, + accountId: localRoomData.accountId, + realm: null, + createdAt: localRoomData.createdAt, + updatedAt: localRoomData.updatedAt, + deletedAt: localRoomData.deletedAt, + members: members, + ); + + // Background sync + Future(() async { + try { + final client = ref.read(apiClientProvider); + final resp = await client.get('/sphere/chat/$identifier'); + final remoteRoom = SnChatRoom.fromJson(resp.data); + await db.saveChatRooms([remoteRoom]); + // Update state with fresh data + state = AsyncData(await _buildRoomFromDb(db, identifier)); + } catch (_) {} + }).ignore(); + + return localRoom; + } + } catch (_) {} + + // Fallback to API + try { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/sphere/chat/$identifier'); + final room = SnChatRoom.fromJson(resp.data); + await db.saveChatRooms([room]); + return room; + } catch (err) { + if (err is DioException && err.response?.statusCode == 404) { + return null; // Chat room not found + } + rethrow; // Rethrow other errors } - rethrow; // Rethrow other errors + } + + Future _buildRoomFromDb( + AppDatabase db, + String identifier, + ) async { + final localRoomData = + await (db.select(db.chatRooms) + ..where((r) => r.id.equals(identifier))).getSingleOrNull(); + + if (localRoomData == null) return null; + + final membersRows = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(localRoomData.id))).get(); + final members = + membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + + return SnChatRoom( + id: localRoomData.id, + name: localRoomData.name, + description: localRoomData.description, + type: localRoomData.type, + isPublic: localRoomData.isPublic!, + isCommunity: localRoomData.isCommunity!, + picture: + localRoomData.picture != null + ? SnCloudFile.fromJson(localRoomData.picture!) + : null, + background: + localRoomData.background != null + ? SnCloudFile.fromJson(localRoomData.background!) + : null, + realmId: localRoomData.realmId, + accountId: localRoomData.accountId, + realm: null, + createdAt: localRoomData.createdAt, + updatedAt: localRoomData.updatedAt, + deletedAt: localRoomData.deletedAt, + members: members, + ); } } @riverpod -Future chatroomIdentity(Ref ref, String? identifier) async { - if (identifier == null) return null; - try { - final client = ref.watch(apiClientProvider); - final resp = await client.get('/sphere/chat/$identifier/members/me'); - return SnChatMember.fromJson(resp.data); - } catch (err) { - if (err is DioException && err.response?.statusCode == 404) { - return null; // Chat member not found +class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier { + @override + Future build(String? identifier) async { + if (identifier == null) return null; + final db = ref.watch(databaseProvider); + final userInfo = ref.watch(userInfoProvider); + + try { + // Try to get from local database first + if (userInfo.value != null) { + final localMemberData = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(identifier)) + ..where((m) => m.accountId.equals(userInfo.value!.id))) + .getSingleOrNull(); + + if (localMemberData != null) { + final account = SnAccount.fromJson(localMemberData.account); + final localMember = SnChatMember( + id: localMemberData.id, + chatRoomId: localMemberData.chatRoomId, + accountId: localMemberData.accountId, + account: account, + nick: localMemberData.nick, + notify: localMemberData.notify, + joinedAt: localMemberData.joinedAt, + breakUntil: localMemberData.breakUntil, + timeoutUntil: localMemberData.timeoutUntil, + status: null, + createdAt: localMemberData.createdAt, + updatedAt: localMemberData.updatedAt, + deletedAt: localMemberData.deletedAt, + chatRoom: null, + ); + + // Background sync + Future(() async { + try { + final client = ref.read(apiClientProvider); + final resp = await client.get( + '/sphere/chat/$identifier/members/me', + ); + final remoteMember = SnChatMember.fromJson(resp.data); + await db.saveMember(remoteMember); + // Update state with fresh data + if (userInfo.value != null) { + state = AsyncData( + await _buildMemberFromDb(db, identifier, userInfo.value!.id), + ); + } + } catch (_) {} + }).ignore(); + + return localMember; + } + } + } catch (_) {} + + // Fallback to API + try { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/sphere/chat/$identifier/members/me'); + final member = SnChatMember.fromJson(resp.data); + await db.saveMember(member); + return member; + } catch (err) { + if (err is DioException && err.response?.statusCode == 404) { + return null; // Chat member not found + } + rethrow; // Rethrow other errors } - rethrow; // Rethrow other errors + } + + Future _buildMemberFromDb( + AppDatabase db, + String identifier, + String accountId, + ) async { + final localMemberData = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(identifier)) + ..where((m) => m.accountId.equals(accountId))) + .getSingleOrNull(); + + if (localMemberData == null) return null; + + final account = SnAccount.fromJson(localMemberData.account); + return SnChatMember( + id: localMemberData.id, + chatRoomId: localMemberData.chatRoomId, + accountId: localMemberData.accountId, + account: account, + nick: localMemberData.nick, + notify: localMemberData.notify, + joinedAt: localMemberData.joinedAt, + breakUntil: localMemberData.breakUntil, + timeoutUntil: localMemberData.timeoutUntil, + status: null, + createdAt: localMemberData.createdAt, + updatedAt: localMemberData.updatedAt, + deletedAt: localMemberData.deletedAt, + chatRoom: null, + ); } } @@ -651,7 +944,7 @@ class _ChatInvitesSheet extends HookConsumerWidget { final client = ref.read(apiClientProvider); await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept'); ref.invalidate(chatroomInvitesProvider); - ref.invalidate(chatroomsJoinedProvider); + ref.invalidate(chatRoomJoinedNotifierProvider); } catch (err) { showErrorAlert(err); } diff --git a/lib/screens/chat/chat.g.dart b/lib/screens/chat/chat.g.dart index 4a59a8cc..4bf2ace9 100644 --- a/lib/screens/chat/chat.g.dart +++ b/lib/screens/chat/chat.g.dart @@ -6,26 +6,46 @@ part of 'chat.dart'; // RiverpodGenerator // ************************************************************************** -String _$chatroomsJoinedHash() => r'50abce4f03a7a8509f16d5ad0b1dbf8e3aeb73b6'; +String _$chatroomInvitesHash() => r'5cd6391b09c5517ede19bacce43b45c8d71dd087'; -/// See also [chatroomsJoined]. -@ProviderFor(chatroomsJoined) -final chatroomsJoinedProvider = - AutoDisposeFutureProvider>.internal( - chatroomsJoined, - name: r'chatroomsJoinedProvider', +/// See also [chatroomInvites]. +@ProviderFor(chatroomInvites) +final chatroomInvitesProvider = + AutoDisposeFutureProvider>.internal( + chatroomInvites, + name: r'chatroomInvitesProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$chatroomsJoinedHash, + : _$chatroomInvitesHash, dependencies: null, allTransitiveDependencies: null, ); @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef>; -String _$chatroomHash() => r'2b17d94728026420d18d6c383d2400cf4a070913'; +typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef>; +String _$chatRoomJoinedNotifierHash() => + r'c8092225ba0d9c08b2b5bca6f800f1877303b4ff'; + +/// See also [ChatRoomJoinedNotifier]. +@ProviderFor(ChatRoomJoinedNotifier) +final chatRoomJoinedNotifierProvider = AutoDisposeAsyncNotifierProvider< + ChatRoomJoinedNotifier, + List +>.internal( + ChatRoomJoinedNotifier.new, + name: r'chatRoomJoinedNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$chatRoomJoinedNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ChatRoomJoinedNotifier = AutoDisposeAsyncNotifier>; +String _$chatRoomNotifierHash() => r'978bd602cf5e93e60e3c7b9f5799d46a87495c79'; /// Copied from Dart SDK class _SystemHash { @@ -48,141 +68,30 @@ class _SystemHash { } } -/// See also [chatroom]. -@ProviderFor(chatroom) -const chatroomProvider = ChatroomFamily(); +abstract class _$ChatRoomNotifier + extends BuildlessAutoDisposeAsyncNotifier { + late final String? identifier; -/// See also [chatroom]. -class ChatroomFamily extends Family> { - /// See also [chatroom]. - const ChatroomFamily(); - - /// See also [chatroom]. - ChatroomProvider call(String? identifier) { - return ChatroomProvider(identifier); - } - - @override - ChatroomProvider getProviderOverride(covariant ChatroomProvider provider) { - return call(provider.identifier); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'chatroomProvider'; + FutureOr build(String? identifier); } -/// See also [chatroom]. -class ChatroomProvider extends AutoDisposeFutureProvider { - /// See also [chatroom]. - ChatroomProvider(String? identifier) - : this._internal( - (ref) => chatroom(ref as ChatroomRef, identifier), - from: chatroomProvider, - name: r'chatroomProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatroomHash, - dependencies: ChatroomFamily._dependencies, - allTransitiveDependencies: ChatroomFamily._allTransitiveDependencies, - identifier: identifier, - ); +/// See also [ChatRoomNotifier]. +@ProviderFor(ChatRoomNotifier) +const chatRoomNotifierProvider = ChatRoomNotifierFamily(); - ChatroomProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.identifier, - }) : super.internal(); +/// See also [ChatRoomNotifier]. +class ChatRoomNotifierFamily extends Family> { + /// See also [ChatRoomNotifier]. + const ChatRoomNotifierFamily(); - final String? identifier; - - @override - Override overrideWith( - FutureOr Function(ChatroomRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: ChatroomProvider._internal( - (ref) => create(ref as ChatroomRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - identifier: identifier, - ), - ); + /// See also [ChatRoomNotifier]. + ChatRoomNotifierProvider call(String? identifier) { + return ChatRoomNotifierProvider(identifier); } @override - AutoDisposeFutureProviderElement createElement() { - return _ChatroomProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is ChatroomProvider && other.identifier == identifier; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, identifier.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin ChatroomRef on AutoDisposeFutureProviderRef { - /// The parameter `identifier` of this provider. - String? get identifier; -} - -class _ChatroomProviderElement - extends AutoDisposeFutureProviderElement - with ChatroomRef { - _ChatroomProviderElement(super.provider); - - @override - String? get identifier => (origin as ChatroomProvider).identifier; -} - -String _$chatroomIdentityHash() => r'35e19a5a3e31752c79b97ba0358a7ec8fb8f6e99'; - -/// See also [chatroomIdentity]. -@ProviderFor(chatroomIdentity) -const chatroomIdentityProvider = ChatroomIdentityFamily(); - -/// See also [chatroomIdentity]. -class ChatroomIdentityFamily extends Family> { - /// See also [chatroomIdentity]. - const ChatroomIdentityFamily(); - - /// See also [chatroomIdentity]. - ChatroomIdentityProvider call(String? identifier) { - return ChatroomIdentityProvider(identifier); - } - - @override - ChatroomIdentityProvider getProviderOverride( - covariant ChatroomIdentityProvider provider, + ChatRoomNotifierProvider getProviderOverride( + covariant ChatRoomNotifierProvider provider, ) { return call(provider.identifier); } @@ -199,29 +108,30 @@ class ChatroomIdentityFamily extends Family> { _allTransitiveDependencies; @override - String? get name => r'chatroomIdentityProvider'; + String? get name => r'chatRoomNotifierProvider'; } -/// See also [chatroomIdentity]. -class ChatroomIdentityProvider - extends AutoDisposeFutureProvider { - /// See also [chatroomIdentity]. - ChatroomIdentityProvider(String? identifier) +/// See also [ChatRoomNotifier]. +class ChatRoomNotifierProvider + extends + AutoDisposeAsyncNotifierProviderImpl { + /// See also [ChatRoomNotifier]. + ChatRoomNotifierProvider(String? identifier) : this._internal( - (ref) => chatroomIdentity(ref as ChatroomIdentityRef, identifier), - from: chatroomIdentityProvider, - name: r'chatroomIdentityProvider', + () => ChatRoomNotifier()..identifier = identifier, + from: chatRoomNotifierProvider, + name: r'chatRoomNotifierProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$chatroomIdentityHash, - dependencies: ChatroomIdentityFamily._dependencies, + : _$chatRoomNotifierHash, + dependencies: ChatRoomNotifierFamily._dependencies, allTransitiveDependencies: - ChatroomIdentityFamily._allTransitiveDependencies, + ChatRoomNotifierFamily._allTransitiveDependencies, identifier: identifier, ); - ChatroomIdentityProvider._internal( + ChatRoomNotifierProvider._internal( super._createNotifier, { required super.name, required super.dependencies, @@ -234,13 +144,16 @@ class ChatroomIdentityProvider final String? identifier; @override - Override overrideWith( - FutureOr Function(ChatroomIdentityRef provider) create, - ) { + FutureOr runNotifierBuild(covariant ChatRoomNotifier notifier) { + return notifier.build(identifier); + } + + @override + Override overrideWith(ChatRoomNotifier Function() create) { return ProviderOverride( origin: this, - override: ChatroomIdentityProvider._internal( - (ref) => create(ref as ChatroomIdentityRef), + override: ChatRoomNotifierProvider._internal( + () => create()..identifier = identifier, from: from, name: null, dependencies: null, @@ -252,13 +165,14 @@ class ChatroomIdentityProvider } @override - AutoDisposeFutureProviderElement createElement() { - return _ChatroomIdentityProviderElement(this); + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _ChatRoomNotifierProviderElement(this); } @override bool operator ==(Object other) { - return other is ChatroomIdentityProvider && other.identifier == identifier; + return other is ChatRoomNotifierProvider && other.identifier == identifier; } @override @@ -272,38 +186,170 @@ class ChatroomIdentityProvider @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -mixin ChatroomIdentityRef on AutoDisposeFutureProviderRef { +mixin ChatRoomNotifierRef on AutoDisposeAsyncNotifierProviderRef { /// The parameter `identifier` of this provider. String? get identifier; } -class _ChatroomIdentityProviderElement - extends AutoDisposeFutureProviderElement - with ChatroomIdentityRef { - _ChatroomIdentityProviderElement(super.provider); +class _ChatRoomNotifierProviderElement + extends + AutoDisposeAsyncNotifierProviderElement + with ChatRoomNotifierRef { + _ChatRoomNotifierProviderElement(super.provider); @override - String? get identifier => (origin as ChatroomIdentityProvider).identifier; + String? get identifier => (origin as ChatRoomNotifierProvider).identifier; } -String _$chatroomInvitesHash() => r'5cd6391b09c5517ede19bacce43b45c8d71dd087'; +String _$chatRoomIdentityNotifierHash() => + r'27c17d55366d39be81d7209837e5c01f80a68a24'; -/// See also [chatroomInvites]. -@ProviderFor(chatroomInvites) -final chatroomInvitesProvider = - AutoDisposeFutureProvider>.internal( - chatroomInvites, - name: r'chatroomInvitesProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatroomInvitesHash, - dependencies: null, - allTransitiveDependencies: null, +abstract class _$ChatRoomIdentityNotifier + extends BuildlessAutoDisposeAsyncNotifier { + late final String? identifier; + + FutureOr build(String? identifier); +} + +/// See also [ChatRoomIdentityNotifier]. +@ProviderFor(ChatRoomIdentityNotifier) +const chatRoomIdentityNotifierProvider = ChatRoomIdentityNotifierFamily(); + +/// See also [ChatRoomIdentityNotifier]. +class ChatRoomIdentityNotifierFamily extends Family> { + /// See also [ChatRoomIdentityNotifier]. + const ChatRoomIdentityNotifierFamily(); + + /// See also [ChatRoomIdentityNotifier]. + ChatRoomIdentityNotifierProvider call(String? identifier) { + return ChatRoomIdentityNotifierProvider(identifier); + } + + @override + ChatRoomIdentityNotifierProvider getProviderOverride( + covariant ChatRoomIdentityNotifierProvider provider, + ) { + return call(provider.identifier); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'chatRoomIdentityNotifierProvider'; +} + +/// See also [ChatRoomIdentityNotifier]. +class ChatRoomIdentityNotifierProvider + extends + AutoDisposeAsyncNotifierProviderImpl< + ChatRoomIdentityNotifier, + SnChatMember? + > { + /// See also [ChatRoomIdentityNotifier]. + ChatRoomIdentityNotifierProvider(String? identifier) + : this._internal( + () => ChatRoomIdentityNotifier()..identifier = identifier, + from: chatRoomIdentityNotifierProvider, + name: r'chatRoomIdentityNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$chatRoomIdentityNotifierHash, + dependencies: ChatRoomIdentityNotifierFamily._dependencies, + allTransitiveDependencies: + ChatRoomIdentityNotifierFamily._allTransitiveDependencies, + identifier: identifier, + ); + + ChatRoomIdentityNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.identifier, + }) : super.internal(); + + final String? identifier; + + @override + FutureOr runNotifierBuild( + covariant ChatRoomIdentityNotifier notifier, + ) { + return notifier.build(identifier); + } + + @override + Override overrideWith(ChatRoomIdentityNotifier Function() create) { + return ProviderOverride( + origin: this, + override: ChatRoomIdentityNotifierProvider._internal( + () => create()..identifier = identifier, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + identifier: identifier, + ), ); + } + + @override + AutoDisposeAsyncNotifierProviderElement< + ChatRoomIdentityNotifier, + SnChatMember? + > + createElement() { + return _ChatRoomIdentityNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ChatRoomIdentityNotifierProvider && + other.identifier == identifier; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, identifier.hashCode); + + return _SystemHash.finish(hash); + } +} @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef>; +mixin ChatRoomIdentityNotifierRef + on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `identifier` of this provider. + String? get identifier; +} + +class _ChatRoomIdentityNotifierProviderElement + extends + AutoDisposeAsyncNotifierProviderElement< + ChatRoomIdentityNotifier, + SnChatMember? + > + with ChatRoomIdentityNotifierRef { + _ChatRoomIdentityNotifierProviderElement(super.provider); + + @override + String? get identifier => + (origin as ChatRoomIdentityNotifierProvider).identifier; +} + // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/chat/chat_form.dart b/lib/screens/chat/chat_form.dart index c4a30475..76fb2261 100644 --- a/lib/screens/chat/chat_form.dart +++ b/lib/screens/chat/chat_form.dart @@ -47,7 +47,7 @@ class EditChatScreen extends HookConsumerWidget { final isPublic = useState(true); final isCommunity = useState(false); - final chat = ref.watch(chatroomProvider(id)); + final chat = ref.watch(ChatRoomNotifierProvider(id)); final joinedRealms = ref.watch(realmsJoinedProvider); final currentRealm = useState(null); diff --git a/lib/screens/chat/public_room_preview.dart b/lib/screens/chat/public_room_preview.dart index 349459db..d114133d 100644 --- a/lib/screens/chat/public_room_preview.dart +++ b/lib/screens/chat/public_room_preview.dart @@ -203,7 +203,7 @@ class PublicRoomPreview extends HookConsumerWidget { showLoadingModal(context); final apiClient = ref.read(apiClientProvider); await apiClient.post('/sphere/chat/${room.id}/members/me'); - ref.invalidate(chatroomIdentityProvider(id)); + ref.invalidate(ChatRoomIdentityNotifierProvider(id)); } catch (err) { showErrorAlert(err); } finally { diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index f4f02fba..b404e6cd 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -20,6 +20,7 @@ import "package:island/pods/chat/messages_notifier.dart"; import "package:island/pods/network.dart"; import "package:island/pods/chat/chat_online_count.dart"; import "package:island/pods/config.dart"; +import "package:island/pods/userinfo.dart"; import "package:island/screens/chat/search_messages.dart"; import "package:island/services/file_uploader.dart"; import "package:island/screens/chat/chat.dart"; @@ -48,8 +49,8 @@ class ChatRoomScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final chatRoom = ref.watch(chatroomProvider(id)); - final chatIdentity = ref.watch(chatroomIdentityProvider(id)); + final chatRoom = ref.watch(ChatRoomNotifierProvider(id)); + final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id)); final isSyncing = ref.watch(isSyncingProvider); final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id)); final settings = ref.watch(appSettingsNotifierProvider); @@ -100,7 +101,9 @@ class ChatRoomScreen extends HookConsumerWidget { await apiClient.post( '/sphere/chat/${room.id}/members/me', ); - ref.invalidate(chatroomIdentityProvider(id)); + ref.invalidate( + ChatRoomIdentityNotifierProvider(id), + ); } catch (err) { showErrorAlert(err); } finally { @@ -129,7 +132,7 @@ class ChatRoomScreen extends HookConsumerWidget { appBar: AppBar(leading: const PageBackButton()), body: ResponseErrorWidget( error: error, - onRetry: () => ref.refresh(chatroomProvider(id)), + onRetry: () => ref.refresh(ChatRoomNotifierProvider(id)), ), ), ); @@ -408,6 +411,14 @@ class ChatRoomScreen extends HookConsumerWidget { final compactHeader = isWideScreen(context); + final userInfo = ref.watch(userInfoProvider); + + List getValidMembers(List members) { + return members + .where((member) => member.accountId != userInfo.value?.id) + .toList(); + } + Widget comfortHeaderWidget(SnChatRoom? room) => Column( spacing: 4, mainAxisAlignment: MainAxisAlignment.center, @@ -428,9 +439,9 @@ class ChatRoomScreen extends HookConsumerWidget { (room!.type == 1 && room.picture?.id == null) ? SplitAvatarWidget( filesId: - room.members! - .map((e) => e.account.profile.picture?.id) - .toList(), + getValidMembers( + room.members!, + ).map((e) => e.account.profile.picture?.id).toList(), ) : room.picture?.id != null ? ProfilePictureWidget( @@ -447,7 +458,9 @@ class ChatRoomScreen extends HookConsumerWidget { ), Text( (room.type == 1 && room.name == null) - ? room.members!.map((e) => e.account.nick).join(', ') + ? getValidMembers( + room.members!, + ).map((e) => e.account.nick).join(', ') : room.name!, ).fontSize(15), ], @@ -473,9 +486,9 @@ class ChatRoomScreen extends HookConsumerWidget { (room!.type == 1 && room.picture?.id == null) ? SplitAvatarWidget( filesId: - room.members! - .map((e) => e.account.profile.picture?.id) - .toList(), + getValidMembers( + room.members!, + ).map((e) => e.account.profile.picture?.id).toList(), ) : room.picture?.id != null ? ProfilePictureWidget( @@ -492,7 +505,9 @@ class ChatRoomScreen extends HookConsumerWidget { ), Text( (room.type == 1 && room.name == null) - ? room.members!.map((e) => e.account.nick).join(', ') + ? getValidMembers( + room.members!, + ).map((e) => e.account.nick).join(', ') : room.name!, ).fontSize(19), ], diff --git a/lib/screens/chat/room_detail.dart b/lib/screens/chat/room_detail.dart index 90043dfa..c719ac9c 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/screens/chat/room_detail.dart @@ -39,8 +39,8 @@ class ChatDetailScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final roomState = ref.watch(chatroomProvider(id)); - final roomIdentity = ref.watch(chatroomIdentityProvider(id)); + final roomState = ref.watch(ChatRoomNotifierProvider(id)); + final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id)); final totalMessages = ref.watch(totalMessagesCountProvider(id)); const kNotifyLevelText = [ @@ -56,7 +56,7 @@ class ChatDetailScreen extends HookConsumerWidget { '/sphere/chat/$id/members/me/notify', data: {'notify_level': level}, ); - ref.invalidate(chatroomIdentityProvider(id)); + ref.invalidate(ChatRoomIdentityNotifierProvider(id)); if (context.mounted) { showSnackBar( 'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]), @@ -74,7 +74,7 @@ class ChatDetailScreen extends HookConsumerWidget { '/sphere/chat/$id/members/me/notify', data: {'break_until': until.toUtc().toIso8601String()}, ); - ref.invalidate(chatroomProvider(id)); + ref.invalidate(ChatRoomNotifierProvider(id)); } catch (err) { showErrorAlert(err); } @@ -439,8 +439,8 @@ class _ChatRoomActionMenu extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final chatIdentity = ref.watch(chatroomIdentityProvider(id)); - final chatRoom = ref.watch(chatroomProvider(id)); + final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id)); + final chatRoom = ref.watch(ChatRoomNotifierProvider(id)); final isManagable = chatIdentity.value?.accountId == chatRoom.value?.accountId || @@ -461,7 +461,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { ).then((value) { if (value != null) { // Invalidate to refresh room data after edit - ref.invalidate(chatroomProvider(id)); + ref.invalidate(ChatRoomNotifierProvider(id)); } }); }, @@ -497,7 +497,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { if (confirm) { final client = ref.watch(apiClientProvider); await client.delete('/sphere/chat/$id'); - ref.invalidate(chatroomsJoinedProvider); + ref.invalidate(chatRoomJoinedNotifierProvider); if (context.mounted) { context.pop(); } @@ -530,7 +530,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { if (confirm) { final client = ref.watch(apiClientProvider); await client.delete('/sphere/chat/$id/members/me'); - ref.invalidate(chatroomsJoinedProvider); + ref.invalidate(chatRoomJoinedNotifierProvider); if (context.mounted) { context.pop(); } @@ -648,8 +648,8 @@ class _ChatMemberListSheet extends HookConsumerWidget { final memberState = ref.watch(chatMemberStateProvider(roomId)); final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier); - final roomIdentity = ref.watch(chatroomIdentityProvider(roomId)); - final chatRoom = ref.watch(chatroomProvider(roomId)); + final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(roomId)); + final chatRoom = ref.watch(ChatRoomNotifierProvider(roomId)); final isManagable = chatRoom.value?.accountId == roomIdentity.value?.accountId || diff --git a/lib/widgets/chat/chat_input.dart b/lib/widgets/chat/chat_input.dart index 00212a93..0267cba2 100644 --- a/lib/widgets/chat/chat_input.dart +++ b/lib/widgets/chat/chat_input.dart @@ -17,6 +17,7 @@ import "package:island/models/wallet.dart"; import "package:island/models/realm.dart"; import "package:island/models/sticker.dart"; import "package:island/pods/config.dart"; +import "package:island/pods/userinfo.dart"; import "package:island/services/autocomplete_service.dart"; import "package:island/services/responsive.dart"; import "package:island/widgets/content/attachment_preview.dart"; @@ -344,6 +345,14 @@ class ChatInput extends HookConsumerWidget { final double rightMargin = isWideScreen(context) ? leftMargin + 8 : 16; const double bottomMargin = 16; + final userInfo = ref.watch(userInfoProvider); + + List getValidMembers(List members) { + return members + .where((member) => member.accountId != userInfo.value?.id) + .toList(); + } + return Container( margin: EdgeInsets.only( left: leftMargin, @@ -878,9 +887,9 @@ class ChatInput extends HookConsumerWidget { (chatRoom.type == 1 && chatRoom.name == null) ? 'chatDirectMessageHint'.tr( args: [ - chatRoom.members! - .map((e) => e.account.nick) - .join(', '), + getValidMembers( + chatRoom.members!, + ).map((e) => e.account.nick).join(', '), ], ) : 'chatMessageHint'.tr( diff --git a/lib/widgets/chat/public_room_preview.dart b/lib/widgets/chat/public_room_preview.dart index 3426b7b0..ae588594 100644 --- a/lib/widgets/chat/public_room_preview.dart +++ b/lib/widgets/chat/public_room_preview.dart @@ -205,7 +205,7 @@ class PublicRoomPreview extends HookConsumerWidget { showLoadingModal(context); final apiClient = ref.read(apiClientProvider); await apiClient.post('/sphere/chat/${room.id}/members/me'); - ref.invalidate(chatroomIdentityProvider(id)); + ref.invalidate(ChatRoomIdentityNotifierProvider(id)); } catch (err) { showErrorAlert(err); } finally { diff --git a/lib/widgets/share/share_sheet.dart b/lib/widgets/share/share_sheet.dart index 9aa5405c..792b5910 100644 --- a/lib/widgets/share/share_sheet.dart +++ b/lib/widgets/share/share_sheet.dart @@ -664,7 +664,7 @@ class _ChatRoomsList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final chatRooms = ref.watch(chatroomsJoinedProvider); + final chatRooms = ref.watch(chatRoomJoinedNotifierProvider); return chatRooms.when( data: (rooms) {