diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 91077303..82225b27 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -2,20 +2,26 @@ ✅ Added -- You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions +- You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions. +- Added new `client.partialUpdateUsers` function in order to partially update users. 🐞 Fixed -- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890). Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). +- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages. + Thanks [bstolinski](https://github.com/bstolinski). +- [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`. + ## 3.4.0 🐞 Fixed - [[#857]](https://github.com/GetStream/stream-chat-flutter/issues/857) Channel now listens for member ban/unban and updates the channel state with the latest data. -- [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` is now also included while saving users in persistence. +- [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` is now also included while saving + users in persistence. - [[#871]](https://github.com/GetStream/stream-chat-flutter/issues/871) Fixed thread message deletion. -- [[#846]](https://github.com/GetStream/stream-chat-flutter/issues/846) Fixed `message.ownReactions` getting truncated when receiving a reaction event. +- [[#846]](https://github.com/GetStream/stream-chat-flutter/issues/846) Fixed `message.ownReactions` getting truncated + when receiving a reaction event. - Add check for invalid image URLs - Fix `channelState.pinnedMessagesStream` getting reset to `0` after a channel update. - Fixed `unreadCount` after removing user from a channel. diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 9c593694..c3605ac5 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1073,6 +1073,28 @@ class StreamChatClient { Future updateUsers(List users) => _chatApi.user.updateUsers(users); + /// Partially update the given user with [id]. + /// Use [set] to define values to be set. + /// Use [unset] to define values to be unset. + Future partialUpdateUser( + String id, { + Map? set, + List? unset, + }) { + final user = PartialUpdateUserRequest( + id: id, + set: set, + unset: unset, + ); + return partialUpdateUsers([user]); + } + + /// Batch partial updates the [users]. + Future partialUpdateUsers( + List users, + ) => + _chatApi.user.partialUpdateUsers(users); + /// Bans a user from all channels Future banUser( String targetUserId, [ diff --git a/packages/stream_chat/lib/src/core/api/requests.dart b/packages/stream_chat/lib/src/core/api/requests.dart index 6f00b374..3f4e79de 100644 --- a/packages/stream_chat/lib/src/core/api/requests.dart +++ b/packages/stream_chat/lib/src/core/api/requests.dart @@ -156,3 +156,29 @@ class PaginationParams extends Equatable { lessThanOrEqual, ]; } + +/// Request model for the [client.partialUpdateUser] api call. +@JsonSerializable(createFactory: false) +class PartialUpdateUserRequest extends Equatable { + /// Creates a new PartialUpdateUserRequest instance. + const PartialUpdateUserRequest({ + required this.id, + this.set, + this.unset, + }); + + /// User ID. + final String id; + + /// Fields to set. + final Map? set; + + /// Fields to unset. + final List? unset; + + /// Serialize model to json + Map toJson() => _$PartialUpdateUserRequestToJson(this); + + @override + List get props => [id, set, unset]; +} diff --git a/packages/stream_chat/lib/src/core/api/requests.g.dart b/packages/stream_chat/lib/src/core/api/requests.g.dart index 7d45ee86..62ec12b3 100644 --- a/packages/stream_chat/lib/src/core/api/requests.g.dart +++ b/packages/stream_chat/lib/src/core/api/requests.g.dart @@ -54,3 +54,14 @@ Map _$PaginationParamsToJson(PaginationParams instance) { writeNotNull('id_lte', instance.lessThanOrEqual); return val; } + +Map _$PartialUpdateUserRequestToJson( + PartialUpdateUserRequest instance) => + { + 'stringify': instance.stringify, + 'hash_code': instance.hashCode, + 'id': instance.id, + 'set': instance.set, + 'unset': instance.unset, + 'props': instance.props, + }; diff --git a/packages/stream_chat/lib/src/core/api/user_api.dart b/packages/stream_chat/lib/src/core/api/user_api.dart index 61159731..916976c8 100644 --- a/packages/stream_chat/lib/src/core/api/user_api.dart +++ b/packages/stream_chat/lib/src/core/api/user_api.dart @@ -46,4 +46,17 @@ class UserApi { ); return UpdateUsersResponse.fromJson(response.data); } + + /// Batch partial update of [users]. + Future partialUpdateUsers( + List users, + ) async { + final response = await _client.patch( + '/users', + data: { + 'users': users, + }, + ); + return UpdateUsersResponse.fromJson(response.data); + } } diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index 37f618f5..0ef1a408 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -49,10 +49,13 @@ class AuthInterceptor extends Interceptor { DioError err, ErrorInterceptorHandler handler, ) async { - ErrorResponse? error; final data = err.response?.data; - if (data != null) error = ErrorResponse.fromJson(data); - if (error?.code == ChatErrorCode.tokenExpired.code) { + if (data == null || data is! Map) { + return handler.next(err); + } + + final error = ErrorResponse.fromJson(data); + if (error.code == ChatErrorCode.tokenExpired.code) { if (_tokenManager.isStatic) return handler.next(err); _client.lock(); await _tokenManager.loadToken(refresh: true); diff --git a/packages/stream_chat/lib/src/core/models/channel_state.g.dart b/packages/stream_chat/lib/src/core/models/channel_state.g.dart index 0da51768..5100932c 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.g.dart @@ -21,7 +21,7 @@ ChannelState _$ChannelStateFromJson(Map json) => ChannelState( pinnedMessages: (json['pinned_messages'] as List?) ?.map((e) => Message.fromJson(e as Map)) .toList() ?? - const [], + _emptyPinnedMessages, watcherCount: json['watcher_count'] as int?, watchers: (json['watchers'] as List?) ?.map((e) => User.fromJson(e as Map)) diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index c9aa8968..b59937bb 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -1772,6 +1772,46 @@ void main() { verifyNoMoreInteractions(api.user); }); + test('`.partialUpdateUser`', () async { + const userId = 'test-user-id'; + + final set = {'color': 'yellow'}; + final unset = []; + + final partialUpdateRequest = PartialUpdateUserRequest( + id: userId, + set: set, + unset: unset, + ); + + final updatedUser = User( + id: userId, + extraData: {'color': set['color']}, + ); + + when(() => api.user.partialUpdateUsers([partialUpdateRequest])) + .thenAnswer( + (_) async => UpdateUsersResponse() + ..users = { + updatedUser.id: updatedUser, + }, + ); + + final res = await client.partialUpdateUser( + userId, + set: set, + unset: unset, + ); + + expect(res, isNotNull); + expect(res.users, {updatedUser.id: updatedUser}); + + verify( + () => api.user.partialUpdateUsers([partialUpdateRequest]), + ).called(1); + verifyNoMoreInteractions(api.user); + }); + test('`.banUser`', () async { const userId = 'test-user-id'; diff --git a/packages/stream_chat/test/src/core/api/user_api_test.dart b/packages/stream_chat/test/src/core/api/user_api_test.dart index dbf83f2c..764386f8 100644 --- a/packages/stream_chat/test/src/core/api/user_api_test.dart +++ b/packages/stream_chat/test/src/core/api/user_api_test.dart @@ -82,4 +82,37 @@ void main() { verify(() => client.post(path, data: any(named: 'data'))).called(1); verifyNoMoreInteractions(client); }); + + test('partialUpdateUsers', () async { + const user = PartialUpdateUserRequest( + id: 'test-user-id', + set: {'color': 'yellow'}, + ); + + const path = '/users'; + + final updatedUser = {user.id: User(id: user.id, extraData: user.set!)}; + + when(() => client.patch(path, data: { + 'users': [user], + })).thenAnswer( + (_) async => successResponse( + path, + data: { + 'users': + updatedUser.map((key, value) => MapEntry(key, value.toJson())) + }, + ), + ); + + final res = await userApi.partialUpdateUsers([user]); + + expect(res, isNotNull); + expect(res.users.length, updatedUser.length); + + verify(() => client.patch(path, data: { + 'users': [user] + })).called(1); + verifyNoMoreInteractions(client); + }); } diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 48aaacfc..4bc77514 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -4,6 +4,7 @@ - [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`. - [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0 +- Fix `showScrollToBottom` in `MessageListView` not respecting false value. ## 3.4.0 - Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index edd9c601..c6e269b9 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -712,20 +712,21 @@ class _MessageListViewState extends State { ); }, ), - BetterStreamBuilder( - stream: streamChannel!.channel.state!.isUpToDateStream, - initialData: streamChannel!.channel.state!.isUpToDate, - builder: (context, snapshot) => ValueListenableBuilder( - valueListenable: _showScrollToBottom, - child: _buildScrollToBottom(), - builder: (context, value, child) { - if (!snapshot || value) { - return child!; - } - return const Offstage(); - }, + if (widget.showScrollToBottom) + BetterStreamBuilder( + stream: streamChannel!.channel.state!.isUpToDateStream, + initialData: streamChannel!.channel.state!.isUpToDate, + builder: (context, snapshot) => ValueListenableBuilder( + valueListenable: _showScrollToBottom, + child: _buildScrollToBottom(), + builder: (context, value, child) { + if (!snapshot || value) { + return child!; + } + return const Offstage(); + }, + ), ), - ), if (widget.showFloatingDateDivider) _buildFloatingDateDivider(itemCount), ],