♻️ Refactored the chat loading to use more local data

This commit is contained in:
2025-12-04 22:10:07 +08:00
parent dfcb089c69
commit 31b83b2d27
14 changed files with 731 additions and 366 deletions

View File

@@ -358,8 +358,12 @@ class AppDatabase extends _$AppDatabase {
); );
} }
Future<void> saveChatRooms(List<SnChatRoom> rooms) async { Future<void> saveChatRooms(
List<SnChatRoom> rooms, {
bool override = false,
}) async {
await transaction(() async { await transaction(() async {
if (override) {
// 1. Identify rooms to remove // 1. Identify rooms to remove
final remoteRoomIds = rooms.map((r) => r.id).toSet(); final remoteRoomIds = rooms.map((r) => r.id).toSet();
final currentRooms = await select(chatRooms).get(); final currentRooms = await select(chatRooms).get();
@@ -369,13 +373,15 @@ class AppDatabase extends _$AppDatabase {
if (idsToRemove.isNotEmpty) { if (idsToRemove.isNotEmpty) {
final idsList = idsToRemove.toList(); final idsList = idsToRemove.toList();
// Remove messages // Remove messages
await (delete(chatMessages)..where((t) => t.roomId.isIn(idsList))).go(); await (delete(chatMessages)
..where((t) => t.roomId.isIn(idsList))).go();
// Remove members // Remove members
await (delete(chatMembers) await (delete(chatMembers)
..where((t) => t.chatRoomId.isIn(idsList))).go(); ..where((t) => t.chatRoomId.isIn(idsList))).go();
// Remove rooms // Remove rooms
await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go(); await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go();
} }
}
// 2. Upsert remote rooms // 2. Upsert remote rooms
await batch((batch) { await batch((batch) {

View File

@@ -16,10 +16,9 @@ final currentSubscribedChatIdProvider = StateProvider<String?>((ref) => null);
@riverpod @riverpod
class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
late final String _roomId; late SnChatRoom _chatRoom;
late final SnChatRoom _chatRoom; late SnChatMember _chatIdentity;
late final SnChatMember _chatIdentity; late MessagesNotifier _messagesNotifier;
late final MessagesNotifier _messagesNotifier;
final List<SnChatMember> _typingStatuses = []; final List<SnChatMember> _typingStatuses = [];
Timer? _typingCleanupTimer; Timer? _typingCleanupTimer;
@@ -29,10 +28,11 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
@override @override
List<SnChatMember> build(String roomId) { List<SnChatMember> build(String roomId) {
_roomId = roomId;
final ws = ref.watch(websocketProvider); final ws = ref.watch(websocketProvider);
final chatRoomAsync = ref.watch(chatroomProvider(roomId)); final chatRoomAsync = ref.watch(ChatRoomNotifierProvider(roomId));
final chatIdentityAsync = ref.watch(chatroomIdentityProvider(roomId)); final chatIdentityAsync = ref.watch(
ChatRoomIdentityNotifierProvider(roomId),
);
_messagesNotifier = ref.watch(messagesNotifierProvider(roomId).notifier); _messagesNotifier = ref.watch(messagesNotifierProvider(roomId).notifier);
if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) { if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) {
@@ -199,7 +199,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
jsonEncode( jsonEncode(
WebSocketPacket( WebSocketPacket(
type: 'messages.read', type: 'messages.read',
data: {'chat_room_id': _roomId}, data: {'chat_room_id': roomId},
endpoint: 'sphere', endpoint: 'sphere',
), ),
), ),
@@ -216,7 +216,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
jsonEncode( jsonEncode(
WebSocketPacket( WebSocketPacket(
type: 'messages.typing', type: 'messages.typing',
data: {'chat_room_id': _roomId}, data: {'chat_room_id': roomId},
endpoint: 'sphere', endpoint: 'sphere',
), ),
), ),

View File

@@ -7,7 +7,7 @@ part of 'chat_subscribe.dart';
// ************************************************************************** // **************************************************************************
String _$chatSubscribeNotifierHash() => String _$chatSubscribeNotifierHash() =>
r'c605e0c9c45df64e5ba7b65f8de9b47bde8e2b3b'; r'beec1ddf2e13f6d5af8a08c2c81eff740ae9b986';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View File

@@ -27,10 +27,10 @@ part 'messages_notifier.g.dart';
@riverpod @riverpod
class MessagesNotifier extends _$MessagesNotifier { class MessagesNotifier extends _$MessagesNotifier {
late final Dio _apiClient; late Dio _apiClient;
late final AppDatabase _database; late AppDatabase _database;
late final SnChatRoom _room; late SnChatRoom _room;
late final SnChatMember _identity; late SnChatMember _identity;
final Map<String, LocalChatMessage> _pendingMessages = {}; final Map<String, LocalChatMessage> _pendingMessages = {};
final Map<String, Map<int, double?>> _fileUploadProgress = {}; final Map<String, Map<int, double?>> _fileUploadProgress = {};
@@ -39,7 +39,6 @@ class MessagesNotifier extends _$MessagesNotifier {
bool? _withLinks; bool? _withLinks;
bool? _withAttachments; bool? _withAttachments;
late final String _roomId;
static const int _pageSize = 20; static const int _pageSize = 20;
bool _hasMore = true; bool _hasMore = true;
bool _isSyncing = false; bool _isSyncing = false;
@@ -48,15 +47,16 @@ class MessagesNotifier extends _$MessagesNotifier {
bool _allRemoteMessagesFetched = false; bool _allRemoteMessagesFetched = false;
DateTime? _lastPauseTime; DateTime? _lastPauseTime;
late final Future<SnAccount?> Function(String) _fetchAccount; late Future<SnAccount?> Function(String) _fetchAccount;
@override @override
FutureOr<List<LocalChatMessage>> build(String roomId) async { FutureOr<List<LocalChatMessage>> build(String roomId) async {
_roomId = roomId;
_apiClient = ref.watch(apiClientProvider); _apiClient = ref.watch(apiClientProvider);
_database = ref.watch(databaseProvider); _database = ref.watch(databaseProvider);
final room = await ref.watch(chatroomProvider(roomId).future); final room = await ref.watch(ChatRoomNotifierProvider(roomId).future);
final identity = await ref.watch(chatroomIdentityProvider(roomId).future); final identity = await ref.watch(
ChatRoomIdentityNotifierProvider(roomId).future,
);
// Initialize fetch account method for corrupted data recovery // Initialize fetch account method for corrupted data recovery
_fetchAccount = (String accountId) async { _fetchAccount = (String accountId) async {
@@ -144,14 +144,14 @@ class MessagesNotifier extends _$MessagesNotifier {
if (searchQuery != null && searchQuery.isNotEmpty) { if (searchQuery != null && searchQuery.isNotEmpty) {
dbMessages = await _database.searchMessages( dbMessages = await _database.searchMessages(
_roomId, roomId,
searchQuery, searchQuery,
withAttachments: withAttachments, withAttachments: withAttachments,
fetchAccount: _fetchAccount, fetchAccount: _fetchAccount,
); );
} else { } else {
final chatMessagesFromDb = await _database.getMessagesForRoom( final chatMessagesFromDb = await _database.getMessagesForRoom(
_roomId, roomId,
offset: offset, offset: offset,
limit: take, limit: take,
); );
@@ -194,9 +194,7 @@ class MessagesNotifier extends _$MessagesNotifier {
if (offset == 0) { if (offset == 0) {
final pendingForRoom = final pendingForRoom =
_pendingMessages.values _pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
.where((msg) => msg.roomId == _roomId)
.toList();
final allMessages = [...pendingForRoom, ...uniqueMessages]; final allMessages = [...pendingForRoom, ...uniqueMessages];
_sortMessages(allMessages); // Use the helper function _sortMessages(allMessages); // Use the helper function
@@ -221,7 +219,7 @@ class MessagesNotifier extends _$MessagesNotifier {
}) async { }) async {
talker.log('Getting all messages for jump from offset $offset, take $take'); talker.log('Getting all messages for jump from offset $offset, take $take');
final chatMessagesFromDb = await _database.getMessagesForRoom( final chatMessagesFromDb = await _database.getMessagesForRoom(
_roomId, roomId,
offset: offset, offset: offset,
limit: take, limit: take,
); );
@@ -245,9 +243,7 @@ class MessagesNotifier extends _$MessagesNotifier {
if (offset == 0) { if (offset == 0) {
final pendingForRoom = final pendingForRoom =
_pendingMessages.values _pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
.where((msg) => msg.roomId == _roomId)
.toList();
final allMessages = [...pendingForRoom, ...uniqueMessages]; final allMessages = [...pendingForRoom, ...uniqueMessages];
_sortMessages(allMessages); _sortMessages(allMessages);
@@ -272,7 +268,7 @@ class MessagesNotifier extends _$MessagesNotifier {
talker.log('Fetching messages from API, offset $offset, take $take'); talker.log('Fetching messages from API, offset $offset, take $take');
if (_totalCount == null) { if (_totalCount == null) {
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages', '/sphere/chat/$roomId/messages',
queryParameters: {'offset': 0, 'take': 1}, queryParameters: {'offset': 0, 'take': 1},
); );
_totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0'); _totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0');
@@ -284,7 +280,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages', '/sphere/chat/$roomId/messages',
queryParameters: {'offset': offset, 'take': take}, queryParameters: {'offset': offset, 'take': take},
); );
@@ -546,7 +542,7 @@ class MessagesNotifier extends _$MessagesNotifier {
final mockMessage = SnChatMessage( final mockMessage = SnChatMessage(
id: 'pending_$nonce', id: 'pending_$nonce',
chatRoomId: _roomId, chatRoomId: roomId,
senderId: _identity.id, senderId: _identity.id,
content: content, content: content,
createdAt: DateTime.now(), createdAt: DateTime.now(),
@@ -590,8 +586,8 @@ class MessagesNotifier extends _$MessagesNotifier {
final response = await _apiClient.request( final response = await _apiClient.request(
editingTo == null editingTo == null
? '/sphere/chat/$_roomId/messages' ? '/sphere/chat/$roomId/messages'
: '/sphere/chat/$_roomId/messages/${editingTo.id}', : '/sphere/chat/$roomId/messages/${editingTo.id}',
data: { data: {
'content': content, 'content': content,
'attachments_id': cloudAttachments.map((e) => e.id).toList(), 'attachments_id': cloudAttachments.map((e) => e.id).toList(),
@@ -731,7 +727,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> receiveMessage(SnChatMessage remoteMessage) async { Future<void> receiveMessage(SnChatMessage remoteMessage) async {
if (remoteMessage.chatRoomId != _roomId) return; if (remoteMessage.chatRoomId != roomId) return;
// Block message receiving during jumps to prevent list resets // Block message receiving during jumps to prevent list resets
if (_isJumping) { if (_isJumping) {
@@ -783,7 +779,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async { Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
if (remoteMessage.chatRoomId != _roomId) return; if (remoteMessage.chatRoomId != roomId) return;
// Block message updates during jumps to prevent list resets // Block message updates during jumps to prevent list resets
if (_isJumping) { if (_isJumping) {
@@ -883,7 +879,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
try { try {
await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId'); await _apiClient.delete('/sphere/chat/$roomId/messages/$messageId');
await receiveMessageDeletion(messageId); await receiveMessageDeletion(messageId);
} catch (err, stackTrace) { } catch (err, stackTrace) {
talker.log( talker.log(
@@ -991,7 +987,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages/$messageId', '/sphere/chat/$roomId/messages/$messageId',
); );
final remoteMessage = SnChatMessage.fromJson(response.data); final remoteMessage = SnChatMessage.fromJson(response.data);
final message = LocalChatMessage.fromRemoteMessage( final message = LocalChatMessage.fromRemoteMessage(
@@ -1048,7 +1044,7 @@ class MessagesNotifier extends _$MessagesNotifier {
final query = _database.customSelect( final query = _database.customSelect(
'SELECT COUNT(*) as count FROM chat_messages WHERE room_id = ? AND created_at > ?', 'SELECT COUNT(*) as count FROM chat_messages WHERE room_id = ? AND created_at > ?',
variables: [ variables: [
Variable.withString(_roomId), Variable.withString(roomId),
Variable.withDateTime(message.createdAt), Variable.withDateTime(message.createdAt),
], ],
readsFrom: {_database.chatMessages}, readsFrom: {_database.chatMessages},

View File

@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$messagesNotifierHash() => r'27ce32c54e317a04e1d554ed4a70a24e4503fdd1'; String _$messagesNotifierHash() => r'd76d799494b06fac2adc42d94b7ecd7b8d68c352';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View File

@@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/database/drift_db.dart';
import 'package:island/pods/database.dart'; import 'package:island/pods/database.dart';
import 'package:island/pods/chat/chat_summary.dart'; import 'package:island/pods/chat/chat_summary.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
@@ -184,13 +185,100 @@ class ChatRoomListTile extends HookConsumerWidget {
} }
@riverpod @riverpod
Future<List<SnChatRoom>> chatroomsJoined(Ref ref) async { class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
@override
Future<List<SnChatRoom>> build() async {
final db = ref.watch(databaseProvider); final db = ref.watch(databaseProvider);
try { try {
final localRoomsData = await db.select(db.chatRooms).get(); final localRoomsData = await db.select(db.chatRooms).get();
if (localRoomsData.isNotEmpty) { if (localRoomsData.isNotEmpty) {
final localRooms = await Future.wait( 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<SnChatRoom>()
.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<SnChatRoom>()
.toList();
await db.saveChatRooms(rooms, override: true);
return rooms;
}
Future<List<SnChatRoom>> _buildRoomsFromDb(AppDatabase db) async {
final localRoomsData = await db.select(db.chatRooms).get();
return Future.wait(
localRoomsData.map((row) async { localRoomsData.map((row) async {
final membersRows = final membersRows =
await (db.select(db.chatMembers) await (db.select(db.chatMembers)
@@ -238,33 +326,7 @@ Future<List<SnChatRoom>> chatroomsJoined(Ref ref) async {
); );
}), }),
); );
// 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<SnChatRoom>()
.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<SnChatRoom>().toList();
await db.saveChatRooms(rooms);
return rooms;
} }
class ChatListBodyWidget extends HookConsumerWidget { class ChatListBodyWidget extends HookConsumerWidget {
@@ -281,7 +343,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chats = ref.watch(chatroomsJoinedProvider); final chats = ref.watch(chatRoomJoinedNotifierProvider);
Widget bodyWidget = Column( Widget bodyWidget = Column(
children: [ children: [
@@ -304,7 +366,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
(items) => RefreshIndicator( (items) => RefreshIndicator(
onRefresh: onRefresh:
() => Future.sync(() { () => Future.sync(() {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
}), }),
child: SuperListView.builder( child: SuperListView.builder(
padding: EdgeInsets.only(bottom: 96), padding: EdgeInsets.only(bottom: 96),
@@ -354,7 +416,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
(error, stack) => ResponseErrorWidget( (error, stack) => ResponseErrorWidget(
error: error, error: error,
onRetry: () { onRetry: () {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
}, },
), ),
), ),
@@ -431,7 +493,7 @@ class ChatListScreen extends HookConsumerWidget {
// Listen for chat rooms refresh events // Listen for chat rooms refresh events
final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) { final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
}); });
return () { return () {
@@ -600,12 +662,91 @@ class ChatListScreen extends HookConsumerWidget {
} }
@riverpod @riverpod
Future<SnChatRoom?> chatroom(Ref ref, String? identifier) async { class ChatRoomNotifier extends _$ChatRoomNotifier {
@override
Future<SnChatRoom?> build(String? identifier) async {
if (identifier == null) return null; 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 { try {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier'); final resp = await client.get('/sphere/chat/$identifier');
return SnChatRoom.fromJson(resp.data); final room = SnChatRoom.fromJson(resp.data);
await db.saveChatRooms([room]);
return room;
} catch (err) { } catch (err) {
if (err is DioException && err.response?.statusCode == 404) { if (err is DioException && err.response?.statusCode == 404) {
return null; // Chat room not found return null; // Chat room not found
@@ -614,13 +755,132 @@ Future<SnChatRoom?> chatroom(Ref ref, String? identifier) async {
} }
} }
Future<SnChatRoom?> _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 @riverpod
Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async { class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier {
@override
Future<SnChatMember?> build(String? identifier) async {
if (identifier == null) return null; 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 { try {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier/members/me'); final resp = await client.get('/sphere/chat/$identifier/members/me');
return SnChatMember.fromJson(resp.data); final member = SnChatMember.fromJson(resp.data);
await db.saveMember(member);
return member;
} catch (err) { } catch (err) {
if (err is DioException && err.response?.statusCode == 404) { if (err is DioException && err.response?.statusCode == 404) {
return null; // Chat member not found return null; // Chat member not found
@@ -629,6 +889,39 @@ Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async {
} }
} }
Future<SnChatMember?> _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,
);
}
}
@riverpod @riverpod
Future<List<SnChatMember>> chatroomInvites(Ref ref) async { Future<List<SnChatMember>> chatroomInvites(Ref ref) async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
@@ -651,7 +944,7 @@ class _ChatInvitesSheet extends HookConsumerWidget {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept'); await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept');
ref.invalidate(chatroomInvitesProvider); ref.invalidate(chatroomInvitesProvider);
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} }

View File

@@ -6,26 +6,46 @@ part of 'chat.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$chatroomsJoinedHash() => r'50abce4f03a7a8509f16d5ad0b1dbf8e3aeb73b6'; String _$chatroomInvitesHash() => r'5cd6391b09c5517ede19bacce43b45c8d71dd087';
/// See also [chatroomsJoined]. /// See also [chatroomInvites].
@ProviderFor(chatroomsJoined) @ProviderFor(chatroomInvites)
final chatroomsJoinedProvider = final chatroomInvitesProvider =
AutoDisposeFutureProvider<List<SnChatRoom>>.internal( AutoDisposeFutureProvider<List<SnChatMember>>.internal(
chatroomsJoined, chatroomInvites,
name: r'chatroomsJoinedProvider', name: r'chatroomInvitesProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') const bool.fromEnvironment('dart.vm.product')
? null ? null
: _$chatroomsJoinedHash, : _$chatroomInvitesHash,
dependencies: null, dependencies: null,
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChatRoom>>; typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef<List<SnChatMember>>;
String _$chatroomHash() => r'2b17d94728026420d18d6c383d2400cf4a070913'; String _$chatRoomJoinedNotifierHash() =>
r'c8092225ba0d9c08b2b5bca6f800f1877303b4ff';
/// See also [ChatRoomJoinedNotifier].
@ProviderFor(ChatRoomJoinedNotifier)
final chatRoomJoinedNotifierProvider = AutoDisposeAsyncNotifierProvider<
ChatRoomJoinedNotifier,
List<SnChatRoom>
>.internal(
ChatRoomJoinedNotifier.new,
name: r'chatRoomJoinedNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatRoomJoinedNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ChatRoomJoinedNotifier = AutoDisposeAsyncNotifier<List<SnChatRoom>>;
String _$chatRoomNotifierHash() => r'978bd602cf5e93e60e3c7b9f5799d46a87495c79';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@@ -48,141 +68,30 @@ class _SystemHash {
} }
} }
/// See also [chatroom]. abstract class _$ChatRoomNotifier
@ProviderFor(chatroom) extends BuildlessAutoDisposeAsyncNotifier<SnChatRoom?> {
const chatroomProvider = ChatroomFamily(); late final String? identifier;
/// See also [chatroom]. FutureOr<SnChatRoom?> build(String? identifier);
class ChatroomFamily extends Family<AsyncValue<SnChatRoom?>> { }
/// See also [chatroom].
const ChatroomFamily();
/// See also [chatroom]. /// See also [ChatRoomNotifier].
ChatroomProvider call(String? identifier) { @ProviderFor(ChatRoomNotifier)
return ChatroomProvider(identifier); const chatRoomNotifierProvider = ChatRoomNotifierFamily();
/// See also [ChatRoomNotifier].
class ChatRoomNotifierFamily extends Family<AsyncValue<SnChatRoom?>> {
/// See also [ChatRoomNotifier].
const ChatRoomNotifierFamily();
/// See also [ChatRoomNotifier].
ChatRoomNotifierProvider call(String? identifier) {
return ChatRoomNotifierProvider(identifier);
} }
@override @override
ChatroomProvider getProviderOverride(covariant ChatroomProvider provider) { ChatRoomNotifierProvider getProviderOverride(
return call(provider.identifier); covariant ChatRoomNotifierProvider provider,
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatroomProvider';
}
/// See also [chatroom].
class ChatroomProvider extends AutoDisposeFutureProvider<SnChatRoom?> {
/// 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,
);
ChatroomProvider._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
Override overrideWith(
FutureOr<SnChatRoom?> 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,
),
);
}
@override
AutoDisposeFutureProviderElement<SnChatRoom?> 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<SnChatRoom?> {
/// The parameter `identifier` of this provider.
String? get identifier;
}
class _ChatroomProviderElement
extends AutoDisposeFutureProviderElement<SnChatRoom?>
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<AsyncValue<SnChatMember?>> {
/// See also [chatroomIdentity].
const ChatroomIdentityFamily();
/// See also [chatroomIdentity].
ChatroomIdentityProvider call(String? identifier) {
return ChatroomIdentityProvider(identifier);
}
@override
ChatroomIdentityProvider getProviderOverride(
covariant ChatroomIdentityProvider provider,
) { ) {
return call(provider.identifier); return call(provider.identifier);
} }
@@ -199,29 +108,30 @@ class ChatroomIdentityFamily extends Family<AsyncValue<SnChatMember?>> {
_allTransitiveDependencies; _allTransitiveDependencies;
@override @override
String? get name => r'chatroomIdentityProvider'; String? get name => r'chatRoomNotifierProvider';
} }
/// See also [chatroomIdentity]. /// See also [ChatRoomNotifier].
class ChatroomIdentityProvider class ChatRoomNotifierProvider
extends AutoDisposeFutureProvider<SnChatMember?> { extends
/// See also [chatroomIdentity]. AutoDisposeAsyncNotifierProviderImpl<ChatRoomNotifier, SnChatRoom?> {
ChatroomIdentityProvider(String? identifier) /// See also [ChatRoomNotifier].
ChatRoomNotifierProvider(String? identifier)
: this._internal( : this._internal(
(ref) => chatroomIdentity(ref as ChatroomIdentityRef, identifier), () => ChatRoomNotifier()..identifier = identifier,
from: chatroomIdentityProvider, from: chatRoomNotifierProvider,
name: r'chatroomIdentityProvider', name: r'chatRoomNotifierProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') const bool.fromEnvironment('dart.vm.product')
? null ? null
: _$chatroomIdentityHash, : _$chatRoomNotifierHash,
dependencies: ChatroomIdentityFamily._dependencies, dependencies: ChatRoomNotifierFamily._dependencies,
allTransitiveDependencies: allTransitiveDependencies:
ChatroomIdentityFamily._allTransitiveDependencies, ChatRoomNotifierFamily._allTransitiveDependencies,
identifier: identifier, identifier: identifier,
); );
ChatroomIdentityProvider._internal( ChatRoomNotifierProvider._internal(
super._createNotifier, { super._createNotifier, {
required super.name, required super.name,
required super.dependencies, required super.dependencies,
@@ -234,13 +144,16 @@ class ChatroomIdentityProvider
final String? identifier; final String? identifier;
@override @override
Override overrideWith( FutureOr<SnChatRoom?> runNotifierBuild(covariant ChatRoomNotifier notifier) {
FutureOr<SnChatMember?> Function(ChatroomIdentityRef provider) create, return notifier.build(identifier);
) { }
@override
Override overrideWith(ChatRoomNotifier Function() create) {
return ProviderOverride( return ProviderOverride(
origin: this, origin: this,
override: ChatroomIdentityProvider._internal( override: ChatRoomNotifierProvider._internal(
(ref) => create(ref as ChatroomIdentityRef), () => create()..identifier = identifier,
from: from, from: from,
name: null, name: null,
dependencies: null, dependencies: null,
@@ -252,13 +165,14 @@ class ChatroomIdentityProvider
} }
@override @override
AutoDisposeFutureProviderElement<SnChatMember?> createElement() { AutoDisposeAsyncNotifierProviderElement<ChatRoomNotifier, SnChatRoom?>
return _ChatroomIdentityProviderElement(this); createElement() {
return _ChatRoomNotifierProviderElement(this);
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is ChatroomIdentityProvider && other.identifier == identifier; return other is ChatRoomNotifierProvider && other.identifier == identifier;
} }
@override @override
@@ -272,38 +186,170 @@ class ChatroomIdentityProvider
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
mixin ChatroomIdentityRef on AutoDisposeFutureProviderRef<SnChatMember?> { mixin ChatRoomNotifierRef on AutoDisposeAsyncNotifierProviderRef<SnChatRoom?> {
/// The parameter `identifier` of this provider. /// The parameter `identifier` of this provider.
String? get identifier; String? get identifier;
} }
class _ChatroomIdentityProviderElement class _ChatRoomNotifierProviderElement
extends AutoDisposeFutureProviderElement<SnChatMember?> extends
with ChatroomIdentityRef { AutoDisposeAsyncNotifierProviderElement<ChatRoomNotifier, SnChatRoom?>
_ChatroomIdentityProviderElement(super.provider); with ChatRoomNotifierRef {
_ChatRoomNotifierProviderElement(super.provider);
@override @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]. abstract class _$ChatRoomIdentityNotifier
@ProviderFor(chatroomInvites) extends BuildlessAutoDisposeAsyncNotifier<SnChatMember?> {
final chatroomInvitesProvider = late final String? identifier;
AutoDisposeFutureProvider<List<SnChatMember>>.internal(
chatroomInvites, FutureOr<SnChatMember?> build(String? identifier);
name: r'chatroomInvitesProvider', }
/// See also [ChatRoomIdentityNotifier].
@ProviderFor(ChatRoomIdentityNotifier)
const chatRoomIdentityNotifierProvider = ChatRoomIdentityNotifierFamily();
/// See also [ChatRoomIdentityNotifier].
class ChatRoomIdentityNotifierFamily extends Family<AsyncValue<SnChatMember?>> {
/// 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<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? 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: debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') const bool.fromEnvironment('dart.vm.product')
? null ? null
: _$chatroomInvitesHash, : _$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<SnChatMember?> 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, dependencies: null,
allTransitiveDependencies: 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') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef<List<SnChatMember>>; mixin ChatRoomIdentityNotifierRef
on AutoDisposeAsyncNotifierProviderRef<SnChatMember?> {
/// 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: 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 // 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

View File

@@ -47,7 +47,7 @@ class EditChatScreen extends HookConsumerWidget {
final isPublic = useState(true); final isPublic = useState(true);
final isCommunity = useState(false); final isCommunity = useState(false);
final chat = ref.watch(chatroomProvider(id)); final chat = ref.watch(ChatRoomNotifierProvider(id));
final joinedRealms = ref.watch(realmsJoinedProvider); final joinedRealms = ref.watch(realmsJoinedProvider);
final currentRealm = useState<SnRealm?>(null); final currentRealm = useState<SnRealm?>(null);

View File

@@ -203,7 +203,7 @@ class PublicRoomPreview extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/chat/${room.id}/members/me'); await apiClient.post('/sphere/chat/${room.id}/members/me');
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(ChatRoomIdentityNotifierProvider(id));
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {

View File

@@ -20,6 +20,7 @@ import "package:island/pods/chat/messages_notifier.dart";
import "package:island/pods/network.dart"; import "package:island/pods/network.dart";
import "package:island/pods/chat/chat_online_count.dart"; import "package:island/pods/chat/chat_online_count.dart";
import "package:island/pods/config.dart"; import "package:island/pods/config.dart";
import "package:island/pods/userinfo.dart";
import "package:island/screens/chat/search_messages.dart"; import "package:island/screens/chat/search_messages.dart";
import "package:island/services/file_uploader.dart"; import "package:island/services/file_uploader.dart";
import "package:island/screens/chat/chat.dart"; import "package:island/screens/chat/chat.dart";
@@ -48,8 +49,8 @@ class ChatRoomScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chatRoom = ref.watch(chatroomProvider(id)); final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
final chatIdentity = ref.watch(chatroomIdentityProvider(id)); final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final isSyncing = ref.watch(isSyncingProvider); final isSyncing = ref.watch(isSyncingProvider);
final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id)); final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id));
final settings = ref.watch(appSettingsNotifierProvider); final settings = ref.watch(appSettingsNotifierProvider);
@@ -100,7 +101,9 @@ class ChatRoomScreen extends HookConsumerWidget {
await apiClient.post( await apiClient.post(
'/sphere/chat/${room.id}/members/me', '/sphere/chat/${room.id}/members/me',
); );
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(
ChatRoomIdentityNotifierProvider(id),
);
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {
@@ -129,7 +132,7 @@ class ChatRoomScreen extends HookConsumerWidget {
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: ResponseErrorWidget( body: ResponseErrorWidget(
error: error, 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 compactHeader = isWideScreen(context);
final userInfo = ref.watch(userInfoProvider);
List<SnChatMember> getValidMembers(List<SnChatMember> members) {
return members
.where((member) => member.accountId != userInfo.value?.id)
.toList();
}
Widget comfortHeaderWidget(SnChatRoom? room) => Column( Widget comfortHeaderWidget(SnChatRoom? room) => Column(
spacing: 4, spacing: 4,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -428,9 +439,9 @@ class ChatRoomScreen extends HookConsumerWidget {
(room!.type == 1 && room.picture?.id == null) (room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: filesId:
room.members! getValidMembers(
.map((e) => e.account.profile.picture?.id) room.members!,
.toList(), ).map((e) => e.account.profile.picture?.id).toList(),
) )
: room.picture?.id != null : room.picture?.id != null
? ProfilePictureWidget( ? ProfilePictureWidget(
@@ -447,7 +458,9 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
Text( Text(
(room.type == 1 && room.name == null) (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!, : room.name!,
).fontSize(15), ).fontSize(15),
], ],
@@ -473,9 +486,9 @@ class ChatRoomScreen extends HookConsumerWidget {
(room!.type == 1 && room.picture?.id == null) (room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: filesId:
room.members! getValidMembers(
.map((e) => e.account.profile.picture?.id) room.members!,
.toList(), ).map((e) => e.account.profile.picture?.id).toList(),
) )
: room.picture?.id != null : room.picture?.id != null
? ProfilePictureWidget( ? ProfilePictureWidget(
@@ -492,7 +505,9 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
Text( Text(
(room.type == 1 && room.name == null) (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!, : room.name!,
).fontSize(19), ).fontSize(19),
], ],

View File

@@ -39,8 +39,8 @@ class ChatDetailScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(chatroomProvider(id)); final roomState = ref.watch(ChatRoomNotifierProvider(id));
final roomIdentity = ref.watch(chatroomIdentityProvider(id)); final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final totalMessages = ref.watch(totalMessagesCountProvider(id)); final totalMessages = ref.watch(totalMessagesCountProvider(id));
const kNotifyLevelText = [ const kNotifyLevelText = [
@@ -56,7 +56,7 @@ class ChatDetailScreen extends HookConsumerWidget {
'/sphere/chat/$id/members/me/notify', '/sphere/chat/$id/members/me/notify',
data: {'notify_level': level}, data: {'notify_level': level},
); );
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(ChatRoomIdentityNotifierProvider(id));
if (context.mounted) { if (context.mounted) {
showSnackBar( showSnackBar(
'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]), 'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]),
@@ -74,7 +74,7 @@ class ChatDetailScreen extends HookConsumerWidget {
'/sphere/chat/$id/members/me/notify', '/sphere/chat/$id/members/me/notify',
data: {'break_until': until.toUtc().toIso8601String()}, data: {'break_until': until.toUtc().toIso8601String()},
); );
ref.invalidate(chatroomProvider(id)); ref.invalidate(ChatRoomNotifierProvider(id));
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} }
@@ -439,8 +439,8 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chatIdentity = ref.watch(chatroomIdentityProvider(id)); final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final chatRoom = ref.watch(chatroomProvider(id)); final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
final isManagable = final isManagable =
chatIdentity.value?.accountId == chatRoom.value?.accountId || chatIdentity.value?.accountId == chatRoom.value?.accountId ||
@@ -461,7 +461,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
// Invalidate to refresh room data after edit // 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) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id'); await client.delete('/sphere/chat/$id');
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
if (context.mounted) { if (context.mounted) {
context.pop(); context.pop();
} }
@@ -530,7 +530,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id/members/me'); await client.delete('/sphere/chat/$id/members/me');
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
if (context.mounted) { if (context.mounted) {
context.pop(); context.pop();
} }
@@ -648,8 +648,8 @@ class _ChatMemberListSheet extends HookConsumerWidget {
final memberState = ref.watch(chatMemberStateProvider(roomId)); final memberState = ref.watch(chatMemberStateProvider(roomId));
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier); final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
final roomIdentity = ref.watch(chatroomIdentityProvider(roomId)); final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(roomId));
final chatRoom = ref.watch(chatroomProvider(roomId)); final chatRoom = ref.watch(ChatRoomNotifierProvider(roomId));
final isManagable = final isManagable =
chatRoom.value?.accountId == roomIdentity.value?.accountId || chatRoom.value?.accountId == roomIdentity.value?.accountId ||

View File

@@ -17,6 +17,7 @@ import "package:island/models/wallet.dart";
import "package:island/models/realm.dart"; import "package:island/models/realm.dart";
import "package:island/models/sticker.dart"; import "package:island/models/sticker.dart";
import "package:island/pods/config.dart"; import "package:island/pods/config.dart";
import "package:island/pods/userinfo.dart";
import "package:island/services/autocomplete_service.dart"; import "package:island/services/autocomplete_service.dart";
import "package:island/services/responsive.dart"; import "package:island/services/responsive.dart";
import "package:island/widgets/content/attachment_preview.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; final double rightMargin = isWideScreen(context) ? leftMargin + 8 : 16;
const double bottomMargin = 16; const double bottomMargin = 16;
final userInfo = ref.watch(userInfoProvider);
List<SnChatMember> getValidMembers(List<SnChatMember> members) {
return members
.where((member) => member.accountId != userInfo.value?.id)
.toList();
}
return Container( return Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
left: leftMargin, left: leftMargin,
@@ -878,9 +887,9 @@ class ChatInput extends HookConsumerWidget {
(chatRoom.type == 1 && chatRoom.name == null) (chatRoom.type == 1 && chatRoom.name == null)
? 'chatDirectMessageHint'.tr( ? 'chatDirectMessageHint'.tr(
args: [ args: [
chatRoom.members! getValidMembers(
.map((e) => e.account.nick) chatRoom.members!,
.join(', '), ).map((e) => e.account.nick).join(', '),
], ],
) )
: 'chatMessageHint'.tr( : 'chatMessageHint'.tr(

View File

@@ -205,7 +205,7 @@ class PublicRoomPreview extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/chat/${room.id}/members/me'); await apiClient.post('/sphere/chat/${room.id}/members/me');
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(ChatRoomIdentityNotifierProvider(id));
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {

View File

@@ -664,7 +664,7 @@ class _ChatRoomsList extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chatRooms = ref.watch(chatroomsJoinedProvider); final chatRooms = ref.watch(chatRoomJoinedNotifierProvider);
return chatRooms.when( return chatRooms.when(
data: (rooms) { data: (rooms) {