import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/models/channel_model.dart'; import 'package:stream_chat/src/models/channel_state.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/member.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/read.dart'; import 'package:stream_chat/src/models/user.dart'; /// A simple client used for persisting chat data locally. abstract class ChatPersistenceClient { /// Creates a new connection to the client Future connect(String? userId); /// Closes the client connection /// If [flush] is true, the data will also be deleted Future disconnect({bool flush = false}); /// Get stored replies by messageId Future> getReplies( String parentId, { PaginationParams? options, }); /// Get stored connection event Future getConnectionInfo(); /// Get stored lastSyncAt Future getLastSyncAt(); /// Update stored connection event Future updateConnectionInfo(Event event); /// Update stored lastSyncAt Future updateLastSyncAt(DateTime? lastSyncAt); /// Get the channel cids saved in the offline storage Future> getChannelCids(); /// Get stored [ChannelModel]s by providing channel [cid] Future getChannelByCid(String? cid); /// Get stored channel [Member]s by providing channel [cid] Future> getMembersByCid(String? cid); /// Get stored channel [Read]s by providing channel [cid] Future> getReadsByCid(String? cid); /// Get stored [Message]s by providing channel [cid] /// /// Optionally, you can [messagePagination] /// for filtering out messages Future> getMessagesByCid( String? cid, { PaginationParams? messagePagination, }); /// Get stored pinned [Message]s by providing channel [cid] Future> getPinnedMessagesByCid( String? cid, { PaginationParams? messagePagination, }); /// Get [ChannelState] data by providing channel [cid] Future getChannelStateByCid( String? cid, { PaginationParams? messagePagination, PaginationParams? pinnedMessagePagination, }) async { final data = await Future.wait([ getMembersByCid(cid), getReadsByCid(cid), getChannelByCid(cid), getMessagesByCid(cid, messagePagination: messagePagination), getPinnedMessagesByCid(cid, messagePagination: pinnedMessagePagination), ]); return ChannelState( members: (data[0] as List?)!, read: (data[1] as List?)!, channel: data[2] as ChannelModel?, messages: (data[3] as List?)!, pinnedMessages: (data[4] as List?)!, ); } /// Get all the stored [ChannelState]s /// /// Optionally, pass [filter], [sort], [paginationParams] /// for filtering out states. Future> getChannelStates({ Map? filter, List>? sort = const [], PaginationParams? paginationParams, }); /// Update list of channel queries. /// /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( Map? filter, List cids, { bool clearQueryCache = false, }); /// Remove a message by [messageId] Future deleteMessageById(String messageId) => deleteMessageByIds([messageId]); /// Remove a pinned message by [messageId] Future deletePinnedMessageById(String messageId) => deletePinnedMessageByIds([messageId]); /// Remove a message by [messageIds] Future deleteMessageByIds(List messageIds); /// Remove a pinned message by [messageIds] Future deletePinnedMessageByIds(List messageIds); /// Remove a message by channel [cid] Future deleteMessageByCid(String? cid) => deleteMessageByCids([cid]); /// Remove a pinned message by channel [cid] Future deletePinnedMessageByCid(String cid) async => deletePinnedMessageByCids([cid]); /// Remove a message by message [cids] Future deleteMessageByCids(List cids); /// Remove a pinned message by message [cids] Future deletePinnedMessageByCids(List cids); /// Remove a channel by [cid] Future deleteChannels(List cids); /// Updates the message data of a particular channel [cid] with /// the new [messages] data Future updateMessages(String? cid, List messages); /// Updates the pinned message data of a particular channel [cid] with /// the new [messages] data Future updatePinnedMessages(String? cid, List messages); /// Returns all the threads by parent message of a particular channel by /// providing channel [cid] Future>> getChannelThreads(String? cid); /// Updates all the channels using the new [channels] data. Future updateChannels(List channels); /// Updates all the members of a particular channle [cid] /// with the new [members] data Future updateMembers(String? cid, List members); /// Updates the read data of a particular channel [cid] with /// the new [reads] data Future updateReads(String? cid, List reads); /// Updates the users data with the new [users] data Future updateUsers(List users); /// Updates the reactions data with the new [reactions] data Future updateReactions(List reactions); /// Deletes all the reactions by [messageIds] Future deleteReactionsByMessageId(List messageIds); /// Deletes all the members by channel [cids] Future deleteMembersByCids(List cids); /// Update the channel state data using [channelState] Future updateChannelState(ChannelState channelState) => updateChannelStates([channelState]); /// Update list of channel states Future updateChannelStates(List channelStates) async { final deleteReactions = deleteReactionsByMessageId(channelStates .expand((it) => it.messages) .map((m) => m.id) .toList(growable: false)); final deleteMembers = deleteMembersByCids( channelStates.map((it) => it.channel!.cid).toList(growable: false), ); await Future.wait([ deleteReactions, deleteMembers, ]); final channels = channelStates.map((it) => it.channel).where((it) => it != null); final reactions = channelStates .expand((it) => it.messages) .expand((it) => [ if (it.ownReactions != null) ...it.ownReactions!.where((r) => r.userId != null), if (it.latestReactions != null) ...it.latestReactions!.where((r) => r.userId != null) ]); final users = channelStates .map((cs) => [ cs.channel?.createdBy, ...cs.messages .map((m) => [ m.user, if (m.latestReactions != null) ...m.latestReactions!.map((r) => r.user), if (m.ownReactions != null) ...m.ownReactions!.map((r) => r.user), ]) .expand((v) => v), ...cs.read.map((r) => r.user), ...cs.members.map((m) => m!.user), ]) .expand((it) => it) .where((it) => it != null); final updateMessagesFuture = channelStates.map((it) { final cid = it.channel!.cid; final messages = it.messages; return updateMessages(cid, messages.toList(growable: false)); }).toList(growable: false); final updatePinnedMessagesFuture = channelStates.map((it) { final cid = it.channel!.cid; final messages = it.pinnedMessages; return updatePinnedMessages(cid, messages.toList(growable: false)); }).toList(growable: false); final updateReadsFuture = channelStates.map((it) { final cid = it.channel!.cid; final reads = it.read; return updateReads(cid, reads.toList(growable: false)); }).toList(growable: false); final updateMembersFuture = channelStates.map((it) { final cid = it.channel!.cid; final members = it.members.where((it) => it != null); return updateMembers(cid, members.toList(growable: false)); }).toList(growable: false); await Future.wait([ ...updateMessagesFuture, ...updatePinnedMessagesFuture, ...updateReadsFuture, ...updateMembersFuture, updateUsers(users.toList(growable: false)), updateChannels(channels.toList(growable: false)), updateReactions(reactions.toList(growable: false)), ]); } }