Skip to content

Commit 1433346

Browse files
committed
Merge remote-tracking branch 'origin/master' into v10.0.0
# Conflicts: # melos.yaml # packages/stream_chat/example/pubspec.yaml # packages/stream_chat/lib/src/client/channel.dart # packages/stream_chat/lib/src/client/client.dart # packages/stream_chat/lib/src/core/api/responses.dart # packages/stream_chat/lib/src/core/api/responses.g.dart # packages/stream_chat/lib/src/core/models/channel_state.dart # packages/stream_chat/lib/src/core/models/channel_state.g.dart # packages/stream_chat/lib/version.dart # packages/stream_chat/pubspec.yaml # packages/stream_chat/test/src/core/models/channel_state_test.dart # packages/stream_chat_flutter/example/pubspec.yaml # packages/stream_chat_flutter/pubspec.yaml # packages/stream_chat_flutter_core/CHANGELOG.md # packages/stream_chat_flutter_core/example/pubspec.yaml # packages/stream_chat_flutter_core/pubspec.yaml # packages/stream_chat_localizations/CHANGELOG.md # packages/stream_chat_localizations/example/pubspec.yaml # packages/stream_chat_localizations/pubspec.yaml # packages/stream_chat_persistence/CHANGELOG.md # packages/stream_chat_persistence/example/pubspec.yaml # packages/stream_chat_persistence/pubspec.yaml # sample_app/pubspec.yaml
2 parents dfa6170 + 7846b38 commit 1433346

33 files changed

+866
-98
lines changed

packages/stream_chat/CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
## Upcoming
1+
## 9.16.0
22

33
🐞 Fixed
44

55
- Fixed `skipPush` and `skipEnrichUrl` not preserving during message send or update retry
66
- Fixed `Channel` methods to throw proper `StateError` exceptions instead of relying on assertions
77
for state validation.
8+
- Fixed `OwnUser` specific fields getting lost when creating a new `OwnUser` instance from
9+
an `User` instance.
10+
- Fixed `Client.currentUser` specific fields getting reset on `user.updated` events.
11+
12+
✅ Added
13+
14+
- Added support for `Client.setPushPreferences` which allows setting PushPreferences for the
15+
current user or for a specific channel.
816

917
## 10.0.0-beta.4
1018

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

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,22 +1754,40 @@ class Channel {
17541754
PaginationParams? options,
17551755
bool preferOffline = false,
17561756
}) async {
1757-
final cachedReplies = await _client.chatPersistenceClient?.getReplies(
1758-
parentId,
1759-
options: options,
1760-
);
1761-
if (cachedReplies != null && cachedReplies.isNotEmpty) {
1762-
state?.updateThreadInfo(parentId, cachedReplies);
1763-
if (preferOffline) {
1764-
return QueryRepliesResponse()..messages = cachedReplies;
1757+
QueryRepliesResponse? response;
1758+
1759+
// If we prefer offline, we first try to get the replies from the
1760+
// offline storage.
1761+
if (preferOffline) {
1762+
if (_client.chatPersistenceClient case final persistenceClient?) {
1763+
final cachedReplies = await persistenceClient.getReplies(
1764+
parentId,
1765+
options: options,
1766+
);
1767+
1768+
// If the cached replies are not empty, we can use them.
1769+
if (cachedReplies.isNotEmpty) {
1770+
response = QueryRepliesResponse()..messages = cachedReplies;
1771+
}
17651772
}
17661773
}
1767-
final repliesResponse = await _client.getReplies(
1768-
parentId,
1769-
options: options,
1770-
);
1771-
state?.updateThreadInfo(parentId, repliesResponse.messages);
1772-
return repliesResponse;
1774+
1775+
// If we still don't have the replies, we try to get them from the API.
1776+
response ??= await _client.getReplies(parentId, options: options);
1777+
1778+
// Before updating the state, we check if we are querying around a
1779+
// reply, If we are, we have to clear the state to avoid potential
1780+
// gaps in the message sequence.
1781+
final isQueryingAround = switch (options) {
1782+
PaginationParams(idAround: _?) => true,
1783+
PaginationParams(createdAtAround: _?) => true,
1784+
_ => false,
1785+
};
1786+
1787+
if (isQueryingAround) state?.clearThread(parentId);
1788+
state?.updateThreadInfo(parentId, response.messages);
1789+
1790+
return response;
17731791
}
17741792

17751793
/// List the reactions for a message in the channel.
@@ -3592,6 +3610,7 @@ class ChannelClientState {
35923610
read: newReads,
35933611
draft: updatedState.draft,
35943612
pinnedMessages: updatedState.pinnedMessages,
3613+
pushPreferences: updatedState.pushPreferences,
35953614
activeLiveLocations: updatedState.activeLiveLocations,
35963615
);
35973616
}
@@ -3630,6 +3649,16 @@ class ChannelClientState {
36303649
);
36313650
}
36323651

3652+
/// Clears all the replies in the thread identified by [parentId].
3653+
void clearThread(String parentId) {
3654+
final updatedThreads = {
3655+
...threads,
3656+
parentId: <Message>[],
3657+
};
3658+
3659+
_threads = updatedThreads;
3660+
}
3661+
36333662
/// Update threads with updated information about messages.
36343663
void updateThreadInfo(String parentId, List<Message> messages) {
36353664
final existingThreadMessages = threads[parentId] ?? [];

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

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import 'package:stream_chat/src/core/models/own_user.dart';
3434
import 'package:stream_chat/src/core/models/poll.dart';
3535
import 'package:stream_chat/src/core/models/poll_option.dart';
3636
import 'package:stream_chat/src/core/models/poll_vote.dart';
37+
import 'package:stream_chat/src/core/models/push_preference.dart';
3738
import 'package:stream_chat/src/core/models/reaction.dart';
3839
import 'package:stream_chat/src/core/models/thread.dart';
3940
import 'package:stream_chat/src/core/models/user.dart';
@@ -972,6 +973,54 @@ class StreamChatClient {
972973
Future<EmptyResponse> removeDevice(String id) =>
973974
_chatApi.device.removeDevice(id);
974975

976+
/// Set push preferences for the current user.
977+
///
978+
/// This method allows you to configure push notification settings
979+
/// at both global and channel-specific levels.
980+
///
981+
/// [preferences] - List of push preferences to apply
982+
///
983+
/// Returns [UpsertPushPreferencesResponse] with the updated preferences.
984+
///
985+
/// Example:
986+
/// ```dart
987+
/// // Set global push preferences
988+
/// await client.setPushPreferences([
989+
/// const PushPreferenceInput(
990+
/// chatLevel: ChatLevelPushPreference.mentions,
991+
/// callLevel: CallLevelPushPreference.all,
992+
/// ),
993+
/// ]);
994+
///
995+
/// // Set channel-specific preferences
996+
/// await client.setPushPreferences([
997+
/// const PushPreferenceInput.channel(
998+
/// channelCid: 'messaging:general',
999+
/// chatLevel: ChatLevelPushPreference.none,
1000+
/// ),
1001+
/// const PushPreferenceInput.channel(
1002+
/// channelCid: 'messaging:support',
1003+
/// chatLevel: ChatLevelPushPreference.mentions,
1004+
/// ),
1005+
/// ]);
1006+
///
1007+
/// // Mix global and channel-specific preferences
1008+
/// await client.setPushPreferences([
1009+
/// const PushPreferenceInput(
1010+
/// chatLevel: ChatLevelPushPreference.all,
1011+
/// ), // Global default
1012+
/// const PushPreferenceInput.channel(
1013+
/// channelCid: 'messaging:spam',
1014+
/// chatLevel: ChatLevelPushPreference.none,
1015+
/// ),
1016+
/// ]);
1017+
/// ```
1018+
Future<UpsertPushPreferencesResponse> setPushPreferences(
1019+
List<PushPreferenceInput> preferences,
1020+
) {
1021+
return _chatApi.device.setPushPreferences(preferences);
1022+
}
1023+
9751024
/// Get a development token
9761025
Token devToken(String userId) => Token.development(userId);
9771026

@@ -2158,10 +2207,25 @@ class ClientState {
21582207
void _listenUserUpdated() {
21592208
_eventsSubscription?.add(
21602209
_client.on(EventType.userUpdated).listen((event) {
2161-
if (event.user!.id == currentUser!.id) {
2162-
currentUser = OwnUser.fromUser(event.user!);
2210+
var user = event.user;
2211+
if (user == null) return;
2212+
2213+
if (user.id == currentUser?.id) {
2214+
final updatedUser = OwnUser.fromUser(user);
2215+
currentUser = user = updatedUser.copyWith(
2216+
// PRESERVE these fields (we don't get them in user.updated events)
2217+
devices: currentUser?.devices,
2218+
mutes: currentUser?.mutes,
2219+
channelMutes: currentUser?.channelMutes,
2220+
totalUnreadCount: currentUser?.totalUnreadCount,
2221+
unreadChannels: currentUser?.unreadChannels,
2222+
unreadThreads: currentUser?.unreadThreads,
2223+
blockedUserIds: currentUser?.blockedUserIds,
2224+
pushPreferences: currentUser?.pushPreferences,
2225+
);
21632226
}
2164-
updateUser(event.user);
2227+
2228+
updateUser(user);
21652229
}),
21662230
);
21672231
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import 'dart:convert';
2+
13
import 'package:stream_chat/src/core/api/responses.dart';
24
import 'package:stream_chat/src/core/http/stream_http_client.dart';
5+
import 'package:stream_chat/src/core/models/push_preference.dart';
36

47
/// Provider used to send push notifications.
58
enum PushProvider {
@@ -57,4 +60,34 @@ class DeviceApi {
5760
);
5861
return EmptyResponse.fromJson(response.data);
5962
}
63+
64+
/// Set push preferences for the current user.
65+
///
66+
/// This method allows you to configure push notification settings
67+
/// at both global and channel-specific levels.
68+
///
69+
/// [preferences] - List of [PushPreferenceInput] to apply. Use the default
70+
/// constructor for user-level preferences or [PushPreferenceInput.channel]
71+
/// for channel-specific preferences.
72+
///
73+
/// Returns [UpsertPushPreferencesResponse] with the updated preferences.
74+
///
75+
/// Throws [ArgumentError] if preferences list is empty.
76+
Future<UpsertPushPreferencesResponse> setPushPreferences(
77+
List<PushPreferenceInput> preferences,
78+
) async {
79+
if (preferences.isEmpty) {
80+
throw ArgumentError.value(
81+
preferences,
82+
'preferences',
83+
'Cannot be empty. At least one preference must be provided.',
84+
);
85+
}
86+
87+
final response = await _client.post(
88+
'/push_preferences',
89+
data: jsonEncode({'preferences': preferences}),
90+
);
91+
return UpsertPushPreferencesResponse.fromJson(response.data);
92+
}
6093
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:stream_chat/src/core/models/message_reminder.dart';
1414
import 'package:stream_chat/src/core/models/poll.dart';
1515
import 'package:stream_chat/src/core/models/poll_option.dart';
1616
import 'package:stream_chat/src/core/models/poll_vote.dart';
17+
import 'package:stream_chat/src/core/models/push_preference.dart';
1718
import 'package:stream_chat/src/core/models/reaction.dart';
1819
import 'package:stream_chat/src/core/models/read.dart';
1920
import 'package:stream_chat/src/core/models/thread.dart';
@@ -799,6 +800,22 @@ class GetUnreadCountResponse extends _BaseResponse {
799800
_$GetUnreadCountResponseFromJson(json);
800801
}
801802

803+
/// Model response for [StreamChatClient.setPushPreferences] api call
804+
@JsonSerializable(createToJson: false)
805+
class UpsertPushPreferencesResponse extends _BaseResponse {
806+
/// Mapping of user IDs to their push preferences
807+
@JsonKey(defaultValue: {})
808+
late Map<String, PushPreference> userPreferences;
809+
810+
/// Mapping of user IDs to their channel-specific push preferences
811+
@JsonKey(defaultValue: {})
812+
late Map<String, Map<String, ChannelPushPreference>> userChannelPreferences;
813+
814+
/// Create a new instance from a json
815+
static UpsertPushPreferencesResponse fromJson(Map<String, dynamic> json) =>
816+
_$UpsertPushPreferencesResponseFromJson(json);
817+
}
818+
802819
/// Model response for [StreamChatClient.updateDraft] api call
803820
@JsonSerializable(createToJson: false)
804821
class GetActiveLiveLocationsResponse extends _BaseResponse {

packages/stream_chat/lib/src/core/api/responses.g.dart

Lines changed: 24 additions & 1 deletion
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/channel_state.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:stream_chat/src/core/models/draft.dart';
55
import 'package:stream_chat/src/core/models/location.dart';
66
import 'package:stream_chat/src/core/models/member.dart';
77
import 'package:stream_chat/src/core/models/message.dart';
8+
import 'package:stream_chat/src/core/models/push_preference.dart';
89
import 'package:stream_chat/src/core/models/read.dart';
910
import 'package:stream_chat/src/core/models/user.dart';
1011

@@ -20,7 +21,7 @@ const _nullConst = _NullConst();
2021
@JsonSerializable()
2122
class ChannelState implements ComparableFieldProvider {
2223
/// Constructor used for json serialization
23-
ChannelState({
24+
const ChannelState({
2425
this.channel,
2526
this.messages,
2627
this.members,
@@ -30,6 +31,7 @@ class ChannelState implements ComparableFieldProvider {
3031
this.read,
3132
this.membership,
3233
this.draft,
34+
this.pushPreferences,
3335
this.activeLiveLocations,
3436
});
3537

@@ -60,6 +62,9 @@ class ChannelState implements ComparableFieldProvider {
6062
/// The draft message for this channel if it exists.
6163
final Draft? draft;
6264

65+
/// The push preferences for this channel if it exists.
66+
final ChannelPushPreference? pushPreferences;
67+
6368
/// The list of active live locations in the channel.
6469
final List<Location>? activeLiveLocations;
6570

@@ -81,6 +86,7 @@ class ChannelState implements ComparableFieldProvider {
8186
List<Read>? read,
8287
Member? membership,
8388
Object? draft = _nullConst,
89+
ChannelPushPreference? pushPreferences,
8490
List<Location>? activeLiveLocations,
8591
}) =>
8692
ChannelState(
@@ -93,6 +99,7 @@ class ChannelState implements ComparableFieldProvider {
9399
read: read ?? this.read,
94100
membership: membership ?? this.membership,
95101
draft: draft == _nullConst ? this.draft : draft as Draft?,
102+
pushPreferences: pushPreferences ?? this.pushPreferences,
96103
activeLiveLocations: activeLiveLocations ?? this.activeLiveLocations,
97104
);
98105

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

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

0 commit comments

Comments
 (0)