Skip to content

Commit f34a1bf

Browse files
authored
Merge branch 'master' into feature/pin-and-archive-channel
2 parents d163df7 + d8f8f66 commit f34a1bf

36 files changed

+2663
-104
lines changed

packages/stream_chat/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
✅ Added
44

5-
- Channel pinning and archiving
5+
- Added support for Channel pinning and archiving.
6+
- Added support for 'DraftMessage' feature, which allows users to save draft messages in channels.
7+
Several methods have been added to the `Client` and `Channel` class to manage draft messages:
8+
- `channel.createDraft`: Saves a draft message for a specific channel.
9+
- `channel.getDraft`: Retrieves a draft message for a specific channel.
10+
- `channel.deleteDraft`: Deletes a draft message for a specific channel.
11+
- `client.queryDrafts`: Queries draft messages created by the current user.
612

713
🔄 Changed
814

packages/stream_chat/lib/src/client/channel.dart

Lines changed: 127 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
// ignore_for_file: avoid_redundant_argument_values
2+
13
import 'dart:async';
24
import 'dart:math';
35

46
import 'package:collection/collection.dart';
57
import 'package:rxdart/rxdart.dart';
68
import 'package:stream_chat/src/client/retry_queue.dart';
7-
import 'package:stream_chat/src/core/models/banned_user.dart';
89
import 'package:stream_chat/src/core/util/utils.dart';
910
import 'package:stream_chat/stream_chat.dart';
1011
import 'package:synchronized/synchronized.dart';
@@ -1043,6 +1044,35 @@ class Channel {
10431044
},
10441045
);
10451046

1047+
/// Creates or updates a new [draft] for this channel.
1048+
Future<CreateDraftResponse> createDraft(
1049+
DraftMessage draft,
1050+
) {
1051+
_checkInitialized();
1052+
return _client.createDraft(draft, id!, type);
1053+
}
1054+
1055+
/// Retrieves the draft for this channel.
1056+
///
1057+
/// Optionally, provide a [parentId] to get the draft for a specific thread.
1058+
Future<GetDraftResponse> getDraft({
1059+
String? parentId,
1060+
}) {
1061+
_checkInitialized();
1062+
return _client.getDraft(id!, type, parentId: parentId);
1063+
}
1064+
1065+
/// Deletes the draft for this channel.
1066+
///
1067+
/// Optionally, provide a [parentId] to delete the draft for a specific
1068+
/// thread.
1069+
Future<EmptyResponse> deleteDraft({
1070+
String? parentId,
1071+
}) {
1072+
_checkInitialized();
1073+
return _client.deleteDraft(id!, type, parentId: parentId);
1074+
}
1075+
10461076
/// Send a file to this channel.
10471077
Future<SendFileResponse> sendFile(
10481078
AttachmentFile file, {
@@ -2084,6 +2114,14 @@ class ChannelClientState {
20842114

20852115
_listenMessageUpdated();
20862116

2117+
/* Start of draft events */
2118+
2119+
_listenDraftUpdated();
2120+
2121+
_listenDraftDeleted();
2122+
2123+
/* End of draft events */
2124+
20872125
_listenReactions();
20882126

20892127
_listenReactionDeleted();
@@ -2589,6 +2627,48 @@ class ChannelClientState {
25892627
}));
25902628
}
25912629

2630+
void _listenDraftUpdated() {
2631+
_subscriptions.add(
2632+
_channel.on(EventType.draftUpdated).listen((event) {
2633+
final draft = event.draft;
2634+
if (draft == null) return;
2635+
2636+
if (draft.parentId case final parentId?) {
2637+
for (final message in messages) {
2638+
if (message.id == parentId) {
2639+
return updateMessage(message.copyWith(draft: draft));
2640+
}
2641+
}
2642+
}
2643+
2644+
updateChannelState(channelState.copyWith(draft: draft));
2645+
}),
2646+
);
2647+
}
2648+
2649+
void _listenDraftDeleted() {
2650+
_subscriptions.add(
2651+
_channel.on(EventType.draftDeleted).listen((event) {
2652+
final draft = event.draft;
2653+
if (draft == null) return;
2654+
2655+
if (draft.parentId case final parentId?) {
2656+
for (final message in messages) {
2657+
if (message.id == parentId) {
2658+
return updateMessage(
2659+
message.copyWith(draft: null),
2660+
);
2661+
}
2662+
}
2663+
}
2664+
2665+
updateChannelState(
2666+
channelState.copyWith(draft: null),
2667+
);
2668+
}),
2669+
);
2670+
}
2671+
25922672
void _listenReactionDeleted() {
25932673
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
25942674
final oldMessage =
@@ -2773,8 +2853,8 @@ class ChannelClientState {
27732853
}
27742854

27752855
// If the message is part of a thread, update thread information.
2776-
if (message.parentId != null) {
2777-
updateThreadInfo(message.parentId!, [message]);
2856+
if (message.parentId case final parentId?) {
2857+
updateThreadInfo(parentId, [message]);
27782858
}
27792859
}
27802860

@@ -2994,6 +3074,14 @@ class ChannelClientState {
29943074
(watchers, users) => [...?watchers?.map((e) => users[e.id] ?? e)],
29953075
).distinct(const ListEquality().equals);
29963076

3077+
/// Channel draft.
3078+
Draft? get draft => _channelState.draft;
3079+
3080+
/// Channel draft as a stream.
3081+
Stream<Draft?> get draftStream {
3082+
return channelStateStream.map((cs) => cs.draft).distinct();
3083+
}
3084+
29973085
/// Channel member for the current user.
29983086
Member? get currentUserMember => members.firstWhereOrNull(
29993087
(m) => m.user?.id == _channel.client.state.currentUser?.id,
@@ -3093,24 +3181,6 @@ class ChannelClientState {
30933181
return count;
30943182
}
30953183

3096-
/// Update threads with updated information about messages.
3097-
void updateThreadInfo(String parentId, List<Message> messages) {
3098-
final newThreads = Map<String, List<Message>>.from(threads);
3099-
3100-
if (newThreads.containsKey(parentId)) {
3101-
newThreads[parentId] = [
3102-
...messages,
3103-
...newThreads[parentId]!.where(
3104-
(newMessage) => !messages.any((m) => m.id == newMessage.id),
3105-
),
3106-
].sorted(_sortByCreatedAt);
3107-
} else {
3108-
newThreads[parentId] = messages;
3109-
}
3110-
3111-
_threads = newThreads;
3112-
}
3113-
31143184
/// Delete all channel messages.
31153185
void truncate() {
31163186
_channelState = _channelState.copyWith(
@@ -3161,6 +3231,7 @@ class ChannelClientState {
31613231
members: newMembers,
31623232
membership: updatedState.membership,
31633233
read: newReads,
3234+
draft: updatedState.draft,
31643235
pinnedMessages: updatedState.pinnedMessages,
31653236
);
31663237
}
@@ -3186,15 +3257,11 @@ class ChannelClientState {
31863257
}
31873258

31883259
/// The channel threads related to this channel.
3189-
Map<String, List<Message>> get threads =>
3190-
_threadsController.value.map(MapEntry.new);
3260+
Map<String, List<Message>> get threads => {..._threadsController.value};
31913261

31923262
/// The channel threads related to this channel as a stream.
3193-
Stream<Map<String, List<Message>>> get threadsStream =>
3194-
_threadsController.stream;
3195-
final BehaviorSubject<Map<String, List<Message>>> _threadsController =
3196-
BehaviorSubject.seeded({});
3197-
3263+
Stream<Map<String, List<Message>>> get threadsStream => _threadsController;
3264+
final _threadsController = BehaviorSubject.seeded(<String, List<Message>>{});
31983265
set _threads(Map<String, List<Message>> threads) {
31993266
_threadsController.safeAdd(threads);
32003267
_channel.client.chatPersistenceClient?.updateChannelThreads(
@@ -3203,6 +3270,38 @@ class ChannelClientState {
32033270
);
32043271
}
32053272

3273+
/// Update threads with updated information about messages.
3274+
void updateThreadInfo(String parentId, List<Message> messages) {
3275+
final newThreads = {...threads}..update(
3276+
parentId,
3277+
(original) => <Message>[
3278+
...original.merge(
3279+
messages,
3280+
key: (message) => message.id,
3281+
update: (original, updated) => updated.syncWith(original),
3282+
),
3283+
].sorted(_sortByCreatedAt),
3284+
ifAbsent: () => messages.sorted(_sortByCreatedAt),
3285+
);
3286+
3287+
_threads = newThreads;
3288+
}
3289+
3290+
Draft? _getThreadDraft(String parentId, List<Message>? messages) {
3291+
return messages?.firstWhereOrNull((it) => it.id == parentId)?.draft;
3292+
}
3293+
3294+
/// Draft for a specific thread identified by [parentId].
3295+
Draft? threadDraft(String parentId) => _getThreadDraft(parentId, messages);
3296+
3297+
/// Stream of draft for a specific thread identified by [parentId].
3298+
///
3299+
/// This stream emits a new value whenever the draft associated with the
3300+
/// specified thread is updated or removed.
3301+
Stream<Draft?> threadDraftStream(String parentId) => channelStateStream
3302+
.map((cs) => _getThreadDraft(parentId, cs.messages))
3303+
.distinct();
3304+
32063305
/// Channel related typing users stream.
32073306
Stream<Map<User, Event>> get typingEventsStream =>
32083307
_typingEventsController.stream;

packages/stream_chat/lib/src/client/client.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import 'package:stream_chat/src/core/http/token_manager.dart';
2020
import 'package:stream_chat/src/core/models/attachment_file.dart';
2121
import 'package:stream_chat/src/core/models/banned_user.dart';
2222
import 'package:stream_chat/src/core/models/channel_state.dart';
23+
import 'package:stream_chat/src/core/models/draft.dart';
24+
import 'package:stream_chat/src/core/models/draft_message.dart';
2325
import 'package:stream_chat/src/core/models/event.dart';
2426
import 'package:stream_chat/src/core/models/filter.dart';
2527
import 'package:stream_chat/src/core/models/member.dart';
@@ -1719,6 +1721,57 @@ class StreamChatClient {
17191721
language,
17201722
);
17211723

1724+
/// Creates a draft for the given [channelId] of type [channelType].
1725+
Future<CreateDraftResponse> createDraft(
1726+
DraftMessage draft,
1727+
String channelId,
1728+
String channelType,
1729+
) =>
1730+
_chatApi.message.createDraft(
1731+
channelId,
1732+
channelType,
1733+
draft,
1734+
);
1735+
1736+
/// Retrieves a draft for the given [channelId] of type [channelType].
1737+
///
1738+
/// Optionally, pass [parentId] to get the draft for a thread.
1739+
Future<GetDraftResponse> getDraft(
1740+
String channelId,
1741+
String channelType, {
1742+
String? parentId,
1743+
}) =>
1744+
_chatApi.message.getDraft(
1745+
channelId,
1746+
channelType,
1747+
parentId: parentId,
1748+
);
1749+
1750+
/// Deletes a draft for the given [channelId] of type [channelType].
1751+
///
1752+
/// Optionally, pass [parentId] to delete the draft for a thread.
1753+
Future<EmptyResponse> deleteDraft(
1754+
String channelId,
1755+
String channelType, {
1756+
String? parentId,
1757+
}) =>
1758+
_chatApi.message.deleteDraft(
1759+
channelId,
1760+
channelType,
1761+
parentId: parentId,
1762+
);
1763+
1764+
/// Queries drafts for the current user.
1765+
Future<QueryDraftsResponse> queryDrafts({
1766+
Filter? filter,
1767+
SortOrder<Draft>? sort,
1768+
PaginationParams? pagination,
1769+
}) =>
1770+
_chatApi.message.queryDrafts(
1771+
sort: sort,
1772+
pagination: pagination,
1773+
);
1774+
17221775
/// Enables slow mode
17231776
Future<PartialUpdateChannelResponse> enableSlowdown(
17241777
String channelId,

0 commit comments

Comments
 (0)