Skip to content

Commit 1b93d73

Browse files
authored
feat(llc): add local events for push preference updates (#2387)
1 parent f85c293 commit 1b93d73

File tree

8 files changed

+268
-2
lines changed

8 files changed

+268
-2
lines changed

packages/stream_chat/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
🐞 Fixed
44

5+
- Fixed `currentUser.pushPreferences` not updating immediately after calling `setPushPreferences`.
56
- Fixed `Channel.sendMessage` to prevent sending empty messages when all attachments are cancelled
67
during upload.
78
- Fixed `toDraftMessage` to only include successfully uploaded attachments in draft messages.

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,6 +2197,8 @@ class ChannelClientState {
21972197

21982198
_startCleaningStalePinnedMessages();
21992199

2200+
_listenChannelPushPreferenceUpdated();
2201+
22002202
_channel._client.chatPersistenceClient
22012203
?.getChannelThreads(_channel.cid!)
22022204
.then((threads) {
@@ -3515,6 +3517,24 @@ class ChannelClientState {
35153517
);
35163518
}
35173519

3520+
// Listens to channel push preference update events and updates the state
3521+
void _listenChannelPushPreferenceUpdated() {
3522+
_subscriptions.add(
3523+
_channel.on(EventType.channelPushPreferenceUpdated).listen(
3524+
(event) {
3525+
final pushPreferences = event.channelPushPreference;
3526+
if (pushPreferences == null) return;
3527+
3528+
updateChannelState(
3529+
channelState.copyWith(
3530+
pushPreferences: pushPreferences,
3531+
),
3532+
);
3533+
},
3534+
),
3535+
);
3536+
}
3537+
35183538
/// Call this method to dispose this object.
35193539
void dispose() {
35203540
_debouncedUpdatePersistenceChannelState.cancel();

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,8 +1034,39 @@ class StreamChatClient {
10341034
/// ```
10351035
Future<UpsertPushPreferencesResponse> setPushPreferences(
10361036
List<PushPreferenceInput> preferences,
1037-
) {
1038-
return _chatApi.device.setPushPreferences(preferences);
1037+
) async {
1038+
final res = await _chatApi.device.setPushPreferences(preferences);
1039+
1040+
final currentUser = state.currentUser;
1041+
final currentUserId = currentUser?.id;
1042+
if (currentUserId == null) return res;
1043+
1044+
// Emit events for updated preferences
1045+
final updatedPushPreference = res.userPreferences[currentUserId];
1046+
if (updatedPushPreference != null) {
1047+
final pushPreferenceUpdatedEvent = Event(
1048+
type: EventType.pushPreferenceUpdated,
1049+
pushPreference: updatedPushPreference,
1050+
);
1051+
1052+
handleEvent(pushPreferenceUpdatedEvent);
1053+
}
1054+
1055+
// Emit events for updated channel-specific preferences
1056+
final channelPushPreferences = res.userChannelPreferences[currentUserId];
1057+
if (channelPushPreferences != null) {
1058+
for (final MapEntry(:key, :value) in channelPushPreferences.entries) {
1059+
final pushPreferenceUpdatedEvent = Event(
1060+
type: EventType.channelPushPreferenceUpdated,
1061+
cid: key,
1062+
channelPushPreference: value,
1063+
);
1064+
1065+
handleEvent(pushPreferenceUpdatedEvent);
1066+
}
1067+
}
1068+
1069+
return res;
10391070
}
10401071

10411072
/// Get a development token
@@ -2129,6 +2160,11 @@ class ClientState {
21292160
if (event.unreadThreads case final count?) {
21302161
currentUser = currentUser?.copyWith(unreadThreads: count);
21312162
}
2163+
2164+
// Update the push preferences.
2165+
if (event.pushPreference case final preferences?) {
2166+
currentUser = currentUser?.copyWith(pushPreferences: preferences);
2167+
}
21322168
}),
21332169
);
21342170

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class Event {
4141
this.lastReadMessageId,
4242
this.draft,
4343
this.reminder,
44+
this.pushPreference,
45+
this.channelPushPreference,
4446
this.extraData = const {},
4547
this.isLocal = true,
4648
}) : createdAt = createdAt?.toUtc() ?? DateTime.now().toUtc();
@@ -154,6 +156,12 @@ class Event {
154156
/// The message reminder sent with the event.
155157
final MessageReminder? reminder;
156158

159+
/// Push notification preferences for the current user.
160+
final PushPreference? pushPreference;
161+
162+
/// Push notification preferences for the current user for this channel.
163+
final ChannelPushPreference? channelPushPreference;
164+
157165
/// Map of custom channel extraData
158166
final Map<String, Object?> extraData;
159167

@@ -193,6 +201,8 @@ class Event {
193201
'last_read_message_id',
194202
'draft',
195203
'reminder',
204+
'push_preference',
205+
'channel_push_preference',
196206
];
197207

198208
/// Serialize to json
@@ -234,6 +244,8 @@ class Event {
234244
String? lastReadMessageId,
235245
Draft? draft,
236246
MessageReminder? reminder,
247+
PushPreference? pushPreference,
248+
ChannelPushPreference? channelPushPreference,
237249
Map<String, Object?>? extraData,
238250
}) =>
239251
Event(
@@ -269,6 +281,9 @@ class Event {
269281
lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId,
270282
draft: draft ?? this.draft,
271283
reminder: reminder ?? this.reminder,
284+
pushPreference: pushPreference ?? this.pushPreference,
285+
channelPushPreference:
286+
channelPushPreference ?? this.channelPushPreference,
272287
isLocal: isLocal,
273288
extraData: extraData ?? this.extraData,
274289
);

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

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

packages/stream_chat/lib/src/event_type.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,11 @@ class EventType {
170170

171171
/// Event sent when a message reminder is due.
172172
static const String notificationReminderDue = 'notification.reminder_due';
173+
174+
/// Local event sent when push notification preference is updated.
175+
static const String pushPreferenceUpdated = 'push_preference.updated';
176+
177+
/// Local event sent when channel push notification preference is updated.
178+
static const String channelPushPreferenceUpdated =
179+
'channel.push_preference.updated';
173180
}

packages/stream_chat/test/src/client/channel_test.dart

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5102,6 +5102,99 @@ void main() {
51025102
expect(updatedMessage?.reminder, isNull);
51035103
});
51045104
});
5105+
5106+
group('Channel push preference events', () {
5107+
const channelId = 'test-channel-id';
5108+
const channelType = 'test-channel-type';
5109+
late Channel channel;
5110+
5111+
setUp(() {
5112+
final channelState = _generateChannelState(channelId, channelType);
5113+
channel = Channel.fromState(client, channelState);
5114+
});
5115+
5116+
tearDown(() {
5117+
channel.dispose();
5118+
});
5119+
5120+
test('should handle channel.push_preference.updated event', () async {
5121+
// Verify initial state
5122+
expect(channel.state?.channelState.pushPreferences, isNull);
5123+
5124+
// Create channel push preference
5125+
final channelPushPreference = ChannelPushPreference(
5126+
chatLevel: ChatLevel.mentions,
5127+
disabledUntil: DateTime.now().add(const Duration(hours: 1)),
5128+
);
5129+
5130+
// Create channel.push_preference.updated event
5131+
final channelPushPreferenceUpdatedEvent = Event(
5132+
cid: channel.cid,
5133+
type: EventType.channelPushPreferenceUpdated,
5134+
channelPushPreference: channelPushPreference,
5135+
);
5136+
5137+
// Dispatch event
5138+
client.addEvent(channelPushPreferenceUpdatedEvent);
5139+
5140+
// Wait for the event to be processed
5141+
await Future.delayed(Duration.zero);
5142+
5143+
// Verify channel push preferences were updated
5144+
final updatedPreferences = channel.state?.channelState.pushPreferences;
5145+
expect(updatedPreferences, isNotNull);
5146+
expect(updatedPreferences?.chatLevel, ChatLevel.mentions);
5147+
expect(
5148+
updatedPreferences?.disabledUntil,
5149+
channelPushPreference.disabledUntil,
5150+
);
5151+
});
5152+
5153+
test('should update existing channel push preferences', () async {
5154+
// Set initial push preferences
5155+
const initialPushPreference = ChannelPushPreference(
5156+
chatLevel: ChatLevel.all,
5157+
);
5158+
5159+
channel.state?.updateChannelState(
5160+
channel.state!.channelState.copyWith(
5161+
pushPreferences: initialPushPreference,
5162+
),
5163+
);
5164+
5165+
// Verify initial state
5166+
final pushPreferences = channel.state?.channelState.pushPreferences;
5167+
expect(pushPreferences?.chatLevel, ChatLevel.all);
5168+
expect(pushPreferences?.disabledUntil, isNull);
5169+
5170+
// Create updated channel push preference
5171+
final updatedPushPreference = ChannelPushPreference(
5172+
chatLevel: ChatLevel.none,
5173+
disabledUntil: DateTime.now().add(const Duration(hours: 2)),
5174+
);
5175+
5176+
// Create channel.push_preference.updated event
5177+
final channelPushPreferenceUpdatedEvent = Event(
5178+
cid: channel.cid,
5179+
type: EventType.channelPushPreferenceUpdated,
5180+
channelPushPreference: updatedPushPreference,
5181+
);
5182+
5183+
// Dispatch event
5184+
client.addEvent(channelPushPreferenceUpdatedEvent);
5185+
5186+
// Wait for the event to be processed
5187+
await Future.delayed(Duration.zero);
5188+
5189+
// Verify channel push preferences were updated
5190+
final updatedPreferences = channel.state?.channelState.pushPreferences;
5191+
expect(updatedPreferences?.chatLevel, ChatLevel.none);
5192+
expect(
5193+
updatedPreferences?.disabledUntil,
5194+
updatedPushPreference.disabledUntil,
5195+
);
5196+
});
5197+
});
51055198
});
51065199

51075200
group('ChannelCapabilityCheck', () {

packages/stream_chat/test/src/client/client_test.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,88 @@ void main() {
12421242
verifyNoMoreInteractions(api.device);
12431243
});
12441244

1245+
test('`.setPushPreferences`', () async {
1246+
const pushPreferenceInput = PushPreferenceInput(
1247+
chatLevel: ChatLevel.mentions,
1248+
);
1249+
1250+
const channelCid = 'messaging:123';
1251+
const channelPreferenceInput = PushPreferenceInput.channel(
1252+
channelCid: channelCid,
1253+
chatLevel: ChatLevel.mentions,
1254+
);
1255+
1256+
const preferences = [pushPreferenceInput, channelPreferenceInput];
1257+
1258+
final currentUser = client.state.currentUser;
1259+
when(() => api.device.setPushPreferences(preferences)).thenAnswer(
1260+
(_) async => UpsertPushPreferencesResponse()
1261+
..userPreferences = {
1262+
'${currentUser?.id}': PushPreference(
1263+
chatLevel: pushPreferenceInput.chatLevel,
1264+
),
1265+
}
1266+
..userChannelPreferences = {
1267+
'${currentUser?.id}': {
1268+
channelCid: ChannelPushPreference(
1269+
chatLevel: channelPreferenceInput.chatLevel,
1270+
),
1271+
},
1272+
},
1273+
);
1274+
1275+
expect(
1276+
client.eventStream,
1277+
emitsInOrder([
1278+
isA<Event>().having(
1279+
(e) => e.type,
1280+
'push_preference.updated event',
1281+
EventType.pushPreferenceUpdated,
1282+
),
1283+
isA<Event>().having(
1284+
(e) => e.type,
1285+
'channel.push_preference.updated event',
1286+
EventType.channelPushPreferenceUpdated,
1287+
),
1288+
]),
1289+
);
1290+
1291+
final res = await client.setPushPreferences(preferences);
1292+
expect(res, isNotNull);
1293+
1294+
verify(() => api.device.setPushPreferences(preferences)).called(1);
1295+
verifyNoMoreInteractions(api.device);
1296+
});
1297+
1298+
test('should handle push_preference.updated event', () async {
1299+
final pushPreference = PushPreference(
1300+
chatLevel: ChatLevel.mentions,
1301+
callLevel: CallLevel.all,
1302+
disabledUntil: DateTime.now().add(const Duration(hours: 1)),
1303+
);
1304+
1305+
final event = Event(
1306+
type: EventType.pushPreferenceUpdated,
1307+
pushPreference: pushPreference,
1308+
);
1309+
1310+
// Initially null
1311+
expect(client.state.currentUser?.pushPreferences, isNull);
1312+
1313+
// Trigger the event
1314+
client.handleEvent(event);
1315+
1316+
// Wait for the event to get processed
1317+
await Future.delayed(Duration.zero);
1318+
1319+
// Should update currentUser.pushPreferences
1320+
final pushPreferences = client.state.currentUser?.pushPreferences;
1321+
expect(pushPreferences, isNotNull);
1322+
expect(pushPreferences?.chatLevel, ChatLevel.mentions);
1323+
expect(pushPreferences?.callLevel, CallLevel.all);
1324+
expect(pushPreferences?.disabledUntil, pushPreference.disabledUntil);
1325+
});
1326+
12451327
test('`.devToken`', () async {
12461328
const userId = 'test-user-id';
12471329

0 commit comments

Comments
 (0)