Skip to content

Commit 2e5b853

Browse files
committed
feat: Add delivery receipts privacy setting
This commit introduces a new privacy setting to control delivery receipts. - A `DeliveryReceipts` class is added to `privacy_settings.dart`, allowing users to enable or disable the sending of delivery receipt events. - `PrivacySettings` is updated to include the new `deliveryReceipts` option. - `OwnUser` now includes an `isDeliveryReceiptsEnabled` getter to easily check the user's current setting. - Generated JSON serialization files have been updated accordingly.
1 parent 46ab6d6 commit 2e5b853

File tree

12 files changed

+128
-63
lines changed

12 files changed

+128
-63
lines changed

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

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,7 @@ class Channel {
16311631
Future<EmptyResponse> markRead({String? messageId}) async {
16321632
_checkInitialized();
16331633

1634-
if (!canReceiveReadEvents) {
1634+
if (!canUseReadReceipts) {
16351635
throw const StreamChatError(
16361636
'Cannot mark as read: Channel does not support read events. '
16371637
'Enable read_events in your channel type configuration.',
@@ -1648,7 +1648,7 @@ class Channel {
16481648
Future<EmptyResponse> markUnread(String messageId) async {
16491649
_checkInitialized();
16501650

1651-
if (!canReceiveReadEvents) {
1651+
if (!canUseReadReceipts) {
16521652
throw const StreamChatError(
16531653
'Cannot mark as unread: Channel does not support read events. '
16541654
'Enable read_events in your channel type configuration.',
@@ -1662,7 +1662,7 @@ class Channel {
16621662
Future<EmptyResponse> markThreadRead(String threadId) async {
16631663
_checkInitialized();
16641664

1665-
if (!canReceiveReadEvents) {
1665+
if (!canUseReadReceipts) {
16661666
throw const StreamChatError(
16671667
'Cannot mark thread as read: Channel does not support read events. '
16681668
'Enable read_events in your channel type configuration.',
@@ -1676,7 +1676,7 @@ class Channel {
16761676
Future<EmptyResponse> markThreadUnread(String threadId) async {
16771677
_checkInitialized();
16781678

1679-
if (!canReceiveReadEvents) {
1679+
if (!canUseReadReceipts) {
16801680
throw const StreamChatError(
16811681
'Cannot mark thread as unread: Channel does not support read events. '
16821682
'Enable read_events in your channel type configuration.',
@@ -2092,9 +2092,9 @@ class Channel {
20922092
// privacy settings.
20932093
bool get _canSendTypingEvents {
20942094
final currentUser = client.state.currentUser;
2095-
final typingIndicatorsEnabled = currentUser?.isTypingIndicatorsEnabled;
2095+
if (currentUser == null) return false;
20962096

2097-
return canUseTypingEvents && (typingIndicatorsEnabled ?? true);
2097+
return canUseTypingEvents && currentUser.isTypingIndicatorsEnabled;
20982098
}
20992099

21002100
/// Sends the [Event.typingStart] event and schedules a timer to invoke the
@@ -3501,14 +3501,11 @@ class ChannelClientState {
35013501
final user = event.user;
35023502
if (user == null) return;
35033503

3504-
final currentUser = _channel.client.state.currentUser;
3505-
if (currentUser == null) return;
3504+
final currentUser = _client.state.currentUser;
3505+
if (event.isFromUser(userId: currentUser?.id)) return;
35063506

3507-
if (user.id != currentUser.id) {
3508-
final events = {...typingEvents};
3509-
events[user] = event;
3510-
_typingEventsController.safeAdd(events);
3511-
}
3507+
final events = {...typingEvents, user: event};
3508+
_typingEventsController.safeAdd(events);
35123509
},
35133510
),
35143511
)
@@ -3518,13 +3515,11 @@ class ChannelClientState {
35183515
final user = event.user;
35193516
if (user == null) return;
35203517

3521-
final currentUser = _channel.client.state.currentUser;
3522-
if (currentUser == null) return;
3518+
final currentUser = _client.state.currentUser;
3519+
if (event.isFromUser(userId: currentUser?.id)) return;
35233520

3524-
if (user.id != currentUser.id) {
3525-
final events = {...typingEvents}..remove(user);
3526-
_typingEventsController.safeAdd(events);
3527-
}
3521+
final events = {...typingEvents}..remove(user);
3522+
_typingEventsController.safeAdd(events);
35283523
},
35293524
),
35303525
);
@@ -3819,7 +3814,11 @@ extension ChannelCapabilityCheck on Channel {
38193814
}
38203815

38213816
/// True, if the current user has read events capability.
3822-
bool get canReceiveReadEvents {
3817+
@Deprecated('Use canUseReadReceipts instead')
3818+
bool get canReceiveReadEvents => canUseReadReceipts;
3819+
3820+
/// True, if the current user has read events capability.
3821+
bool get canUseReadReceipts {
38233822
return ownCapabilities.contains(ChannelCapability.readEvents);
38243823
}
38253824

@@ -3858,4 +3857,9 @@ extension ChannelCapabilityCheck on Channel {
38583857
bool get canQueryPollVotes {
38593858
return ownCapabilities.contains(ChannelCapability.queryPollVotes);
38603859
}
3860+
3861+
/// True, if the current user has delivery events capability.
3862+
bool get canUseDeliveryReceipts {
3863+
return ownCapabilities.contains(ChannelCapability.deliveryEvents);
3864+
}
38613865
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'package:logging/logging.dart';
22
import 'package:rate_limiter/rate_limiter.dart';
33
import 'package:stream_chat/src/client/channel.dart';
44
import 'package:stream_chat/src/core/models/message.dart';
5-
import 'package:stream_chat/src/core/models/message_delivery_info.dart';
5+
import 'package:stream_chat/src/core/models/message_delivery.dart';
66
import 'package:stream_chat/src/core/util/message_rules.dart';
77
import 'package:synchronized/synchronized.dart';
88

@@ -11,7 +11,7 @@ import 'package:synchronized/synchronized.dart';
1111
/// Each [MessageDeliveryInfo] represents an acknowledgment that the current
1212
/// user has received a message.
1313
typedef MarkChannelsDelivered = Future<void> Function(
14-
Iterable<MessageDeliveryInfo> messages,
14+
Iterable<MessageDelivery> deliveries,
1515
);
1616

1717
/// Manages the delivery reporting for channel messages.
@@ -154,23 +154,23 @@ class ChannelDeliveryReporter {
154154
Future<void> _markCandidatesAsDelivered() async {
155155
// We only process at-most 100 channels at a time to avoid large payloads.
156156
final batch = {..._deliveryCandidates}.entries.take(_maxCandidatesPerBatch);
157-
final messageInfoPayload = batch.map(
158-
(it) => MessageDeliveryInfo(channelCid: it.key, messageId: it.value.id),
157+
final messageDeliveries = batch.map(
158+
(it) => MessageDelivery(channelCid: it.key, messageId: it.value.id),
159159
);
160160

161-
if (messageInfoPayload.isEmpty) return;
161+
if (messageDeliveries.isEmpty) return;
162162

163-
_logger?.info('Marking ${messageInfoPayload.length} channels as delivered');
163+
_logger?.info('Marking ${messageDeliveries.length} channels as delivered');
164164

165165
try {
166-
await onMarkChannelsDelivered(messageInfoPayload);
166+
await onMarkChannelsDelivered(messageDeliveries);
167167

168168
// Clear the successfully delivered candidates. If a channel's message ID
169169
// has changed since we started delivery, keep it for the next batch.
170170
await _deliveryCandidatesLock.synchronized(() {
171-
for (final messageInfo in messageInfoPayload) {
172-
final deliveredChannelCid = messageInfo.channelCid;
173-
final deliveredMessageId = messageInfo.messageId;
171+
for (final delivery in messageDeliveries) {
172+
final deliveredChannelCid = delivery.channelCid;
173+
final deliveredMessageId = delivery.messageId;
174174

175175
final currentMessage = _deliveryCandidates[deliveredChannelCid];
176176
// Skip removal if a newer message has been added while we were

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import 'package:stream_chat/src/core/models/event.dart';
2828
import 'package:stream_chat/src/core/models/filter.dart';
2929
import 'package:stream_chat/src/core/models/member.dart';
3030
import 'package:stream_chat/src/core/models/message.dart';
31-
import 'package:stream_chat/src/core/models/message_delivery_info.dart';
31+
import 'package:stream_chat/src/core/models/message_delivery.dart';
3232
import 'package:stream_chat/src/core/models/message_reminder.dart';
3333
import 'package:stream_chat/src/core/models/own_user.dart';
3434
import 'package:stream_chat/src/core/models/poll.dart';
@@ -1692,9 +1692,9 @@ class StreamChatClient {
16921692
///
16931693
/// Accepts up to 100 channels per call.
16941694
Future<EmptyResponse> markChannelsDelivered(
1695-
Iterable<MessageDeliveryInfo> messages,
1695+
Iterable<MessageDelivery> deliveries,
16961696
) {
1697-
return _chatApi.channel.markChannelsDelivered(messages);
1697+
return _chatApi.channel.markChannelsDelivered([...deliveries]);
16981698
}
16991699

17001700
/// Send an event to a particular channel

packages/stream_chat/lib/src/core/api/channel_api.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'package:stream_chat/src/core/models/channel_state.dart';
88
import 'package:stream_chat/src/core/models/event.dart';
99
import 'package:stream_chat/src/core/models/filter.dart';
1010
import 'package:stream_chat/src/core/models/message.dart';
11-
import 'package:stream_chat/src/core/models/message_delivery_info.dart';
11+
import 'package:stream_chat/src/core/models/message_delivery.dart';
1212

1313
/// Defines the api dedicated to channel operations
1414
class ChannelApi {
@@ -398,11 +398,13 @@ class ChannelApi {
398398
///
399399
/// Accepts up to 100 channels per call.
400400
Future<EmptyResponse> markChannelsDelivered(
401-
Iterable<MessageDeliveryInfo> messages,
401+
List<MessageDelivery> deliveries,
402402
) async {
403403
final response = await _client.post(
404404
'/channels/delivered',
405-
data: jsonEncode({'latest_delivered_messages': messages}),
405+
data: jsonEncode({
406+
'latest_delivered_messages': deliveries,
407+
}),
406408
);
407409
return EmptyResponse.fromJson(response.data);
408410
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import 'package:json_annotation/json_annotation.dart';
22

3-
part 'message_delivery_info.g.dart';
3+
part 'message_delivery.g.dart';
44

55
/// A delivery receipt for a message in a channel.
66
///
77
/// Used to acknowledge that the current user has received a message,
88
/// notifying the sender that their message was delivered.
99
@JsonSerializable(createFactory: false)
10-
class MessageDeliveryInfo {
10+
class MessageDelivery {
1111
/// Creates a delivery receipt for a message.
12-
const MessageDeliveryInfo({
12+
const MessageDelivery({
1313
required this.channelCid,
1414
required this.messageId,
1515
});
@@ -22,6 +22,6 @@ class MessageDeliveryInfo {
2222
@JsonKey(name: 'id')
2323
final String messageId;
2424

25-
/// Converts this [MessageDeliveryInfo] to JSON.
26-
Map<String, dynamic> toJson() => _$MessageDeliveryInfoToJson(this);
25+
/// Converts this [MessageDelivery] to JSON.
26+
Map<String, dynamic> toJson() => _$MessageDeliveryToJson(this);
2727
}

packages/stream_chat/lib/src/core/models/message_delivery_info.g.dart renamed to packages/stream_chat/lib/src/core/models/message_delivery.g.dart

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/stream_chat/lib/src/core/models/own_user.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,12 @@ extension PrivacySettingsExtension on OwnUser {
206206

207207
return readIndicators.enabled;
208208
}
209+
210+
/// Whether delivery receipts are enabled for the user.
211+
bool get isDeliveryReceiptsEnabled {
212+
final deliveryIndicators = privacySettings?.deliveryReceipts;
213+
if (deliveryIndicators == null) return true;
214+
215+
return deliveryIndicators.enabled;
216+
}
209217
}

packages/stream_chat/lib/src/core/models/privacy_settings.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class PrivacySettings extends Equatable {
1010
const PrivacySettings({
1111
this.typingIndicators,
1212
this.readReceipts,
13+
this.deliveryReceipts,
1314
});
1415

1516
/// Create a new instance from json.
@@ -23,11 +24,14 @@ class PrivacySettings extends Equatable {
2324
/// The settings for the read receipt events.
2425
final ReadReceipts? readReceipts;
2526

27+
/// The settings for the delivery receipt events.
28+
final DeliveryReceipts? deliveryReceipts;
29+
2630
/// Serialize to json.
2731
Map<String, dynamic> toJson() => _$PrivacySettingsToJson(this);
2832

2933
@override
30-
List<Object?> get props => [typingIndicators, readReceipts];
34+
List<Object?> get props => [typingIndicators, readReceipts, deliveryReceipts];
3135
}
3236

3337
/// The settings for typing indicator events.
@@ -80,3 +84,29 @@ class ReadReceipts extends Equatable {
8084
@override
8185
List<Object?> get props => [enabled];
8286
}
87+
88+
/// The settings for the delivery receipt events.
89+
@JsonSerializable(includeIfNull: false)
90+
class DeliveryReceipts extends Equatable {
91+
/// Create a new instance of [DeliveryReceipts].
92+
const DeliveryReceipts({
93+
this.enabled = true,
94+
});
95+
96+
/// Create a new instance from json.
97+
factory DeliveryReceipts.fromJson(Map<String, dynamic> json) {
98+
return _$DeliveryReceiptsFromJson(json);
99+
}
100+
101+
/// Whether the delivery receipt events are enabled for the user.
102+
///
103+
/// If False, the user delivery events will not be sent to other users, along
104+
/// with the user's delivery state.
105+
final bool enabled;
106+
107+
/// Serialize to json.
108+
Map<String, dynamic> toJson() => _$DeliveryReceiptsToJson(this);
109+
110+
@override
111+
List<Object?> get props => [enabled];
112+
}

packages/stream_chat/lib/src/core/models/privacy_settings.g.dart

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)