diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index e037a3b8f..df797b1d6 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -7,6 +7,7 @@ - Added `PaginationParams.createdAtBeforeOrEqual` for message pagination. - Added `PaginationParams.createdAtBefore` for message pagination. - Added `PaginationParams.createdAtAround` for message pagination. +- Added support for `channel.disabled`, `channel.hidden` and `channel.truncatedAt` in `Channel`. - Added support for `channel.membership` and `channel.membershipStream` in `Channel`. - `Channel` now listens for `member.updated` events and updates the `Channel.members` accordingly. @@ -14,6 +15,11 @@ - Deprecated `PaginationParams.before` and `PaginationParams.after`. Use `PaginationParams.limit` instead. +🐞 Fixed + +- [[#1147]](https://github.com/GetStream/stream-chat-flutter/issues/1147) `channel.unset` not updating the extra data + stream. + ## 4.1.0 ✅ Added diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index dd8518ab4..b4e1379d3 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -207,6 +207,42 @@ class Channel { return state!.channelStateStream.map((cs) => cs.channel?.frozen == true); } + /// Channel disabled status. + bool get disabled { + _checkInitialized(); + return state!._channelState.channel?.disabled == true; + } + + /// Channel disabled status as a stream. + Stream get disabledStream { + _checkInitialized(); + return state!.channelStateStream.map((cs) => cs.channel?.disabled == true); + } + + /// Channel hidden status. + bool get hidden { + _checkInitialized(); + return state!._channelState.channel?.hidden == true; + } + + /// Channel hidden status as a stream. + Stream get hiddenStream { + _checkInitialized(); + return state!.channelStateStream.map((cs) => cs.channel?.hidden == true); + } + + /// The last date at which the channel got truncated. + DateTime? get truncatedAt { + _checkInitialized(); + return state!._channelState.channel?.truncatedAt; + } + + /// The last date at which the channel got truncated as a stream. + Stream get truncatedAtStream { + _checkInitialized(); + return state!.channelStateStream.map((cs) => cs.channel?.truncatedAt); + } + /// Cooldown count int get cooldown { _checkInitialized(); diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index c3e834354..942b9e63f 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -22,9 +22,12 @@ class ChannelModel { DateTime? updatedAt, this.deletedAt, this.memberCount = 0, - this.extraData = const {}, + Map extraData = const {}, this.team, this.cooldown = 0, + bool? disabled, + bool? hidden, + DateTime? truncatedAt, }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), 'provide either a cid or an id and type', @@ -34,7 +37,18 @@ class ChannelModel { cid = cid ?? '$type:$id', config = config ?? ChannelConfig(), createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + updatedAt = updatedAt ?? DateTime.now(), + + // TODO: Make them top-level fields in v5 + // For backwards compatibility, set 'disabled', 'hidden' + // and 'truncated_at' in [extraData]. + extraData = { + ...extraData, + if (disabled != null) 'disabled': disabled, + if (hidden != null) 'hidden': hidden, + if (truncatedAt != null) + 'truncated_at': truncatedAt.toIso8601String(), + }; /// Create a new instance from a json factory ChannelModel.fromJson(Map json) => @@ -92,6 +106,22 @@ class ChannelModel { @JsonKey(includeIfNull: false) final int cooldown; + /// True if the channel is disabled + @JsonKey(ignore: true) + bool? get disabled => extraData['disabled'] as bool?; + + /// True if the channel is hidden + @JsonKey(ignore: true) + bool? get hidden => extraData['hidden'] as bool?; + + /// The date of the last time channel got truncated + @JsonKey(ignore: true) + DateTime? get truncatedAt { + final truncatedAt = extraData['truncated_at'] as String?; + if (truncatedAt == null) return null; + return DateTime.parse(truncatedAt); + } + /// Map of custom channel extraData @JsonKey(includeIfNull: false) final Map extraData; @@ -145,6 +175,9 @@ class ChannelModel { Map? extraData, String? team, int? cooldown, + bool? disabled, + bool? hidden, + DateTime? truncatedAt, }) => ChannelModel( id: id ?? this.id, @@ -162,6 +195,14 @@ class ChannelModel { extraData: extraData ?? this.extraData, team: team ?? this.team, cooldown: cooldown ?? this.cooldown, + disabled: disabled ?? extraData?['disabled'] as bool? ?? this.disabled, + hidden: hidden ?? extraData?['hidden'] as bool? ?? this.hidden, + truncatedAt: truncatedAt ?? + (extraData?['truncated_at'] == null + ? null + // ignore: cast_nullable_to_non_nullable + : DateTime.parse(extraData?['truncated_at'] as String)) ?? + this.truncatedAt, ); /// Returns a new [ChannelModel] that is a combination of this channelModel @@ -181,9 +222,12 @@ class ChannelModel { updatedAt: other.updatedAt, deletedAt: other.deletedAt, memberCount: other.memberCount, - extraData: {...extraData, ...other.extraData}, + extraData: other.extraData, team: other.team, cooldown: other.cooldown, + disabled: other.disabled, + hidden: other.hidden, + truncatedAt: other.truncatedAt, ); } } diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 47270d404..13aec2b96 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -180,6 +180,7 @@ class EventChannel extends ChannelModel { super.id, super.type, required String super.cid, + super.ownCapabilities, required ChannelConfig super.config, super.createdBy, super.frozen, @@ -191,6 +192,9 @@ class EventChannel extends ChannelModel { Map? extraData, super.cooldown, super.team, + super.disabled, + super.hidden, + super.truncatedAt, }) : super(extraData: extraData ?? {}); /// Create a new instance from a json diff --git a/packages/stream_chat/lib/src/core/models/event.g.dart b/packages/stream_chat/lib/src/core/models/event.g.dart index d3a531fe8..524b5bd9b 100644 --- a/packages/stream_chat/lib/src/core/models/event.g.dart +++ b/packages/stream_chat/lib/src/core/models/event.g.dart @@ -81,6 +81,9 @@ EventChannel _$EventChannelFromJson(Map json) => EventChannel( id: json['id'] as String?, type: json['type'] as String?, cid: json['cid'] as String, + ownCapabilities: (json['own_capabilities'] as List?) + ?.map((e) => e as String) + .toList(), config: ChannelConfig.fromJson(json['config'] as Map), createdBy: json['created_by'] == null ? null diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index 85b6ba57b..60d152780 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -41,6 +41,9 @@ class OwnUser extends User { factory OwnUser.fromUser(User user) => OwnUser( id: user.id, role: user.role, + // Using extraData value in order to not use id as name. + name: user.extraData['name'] as String?, + image: user.image, createdAt: user.createdAt, updatedAt: user.updatedAt, lastActive: user.lastActive, @@ -76,10 +79,11 @@ class OwnUser extends User { OwnUser( id: id ?? this.id, role: role ?? this.role, - // if null, it will be retrieved from extraData['name'] - name: name, - // if null, it will be retrieved from extraData['image'] - image: image, + name: name ?? + extraData?['name'] as String? ?? + // Using extraData value in order to not use id as name. + this.extraData['name'] as String?, + image: image ?? extraData?['image'] as String? ?? this.image, banned: banned ?? this.banned, banExpires: banExpires ?? this.banExpires, createdAt: createdAt ?? this.createdAt, @@ -103,6 +107,9 @@ class OwnUser extends User { return copyWith( id: other.id, role: other.role, + // Using extraData value in order to not use id as name. + name: other.extraData['name'] as String?, + image: other.image, banned: other.banned, channelMutes: other.channelMutes, createdAt: other.createdAt, diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 0d19d59d4..ff435f67b 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -46,6 +46,7 @@ class User extends Equatable { this.language, }) : createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(), + // TODO: Make them top-level fields in v5 // For backwards compatibility, set 'name', 'image' in [extraData]. extraData = { ...extraData, @@ -171,10 +172,11 @@ class User extends Equatable { User( id: id ?? this.id, role: role ?? this.role, - // if null, it will be retrieved from extraData['name'] - name: name, - // if null, it will be retrieved from extraData['image'] - image: image, + name: name ?? + extraData?['name'] as String? ?? + // Using extraData value in order to not use id as name. + this.extraData['name'] as String?, + image: image ?? extraData?['image'] as String? ?? this.image, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, lastActive: lastActive ?? this.lastActive, diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index 4947bc729..923d399ca 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -2514,7 +2514,7 @@ void main() { }); test( - '''setting the `currentUser` should also compute and update the unreadCounts''', + 'setting the `currentUser` should also compute and update the unreadCounts', () { final state = client.state; final initialUser = OwnUser.fromUser(user); diff --git a/packages/stream_chat/test/src/core/models/channel_test.dart b/packages/stream_chat/test/src/core/models/channel_test.dart index e27f5970c..e67c185e9 100644 --- a/packages/stream_chat/test/src/core/models/channel_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_test.dart @@ -55,4 +55,140 @@ void main() { ); }); }); + + test('hidden property and extraData manipulation', () { + final channel = ChannelModel(cid: 'test:cid', hidden: false); + + expect(channel.hidden, false); + expect(channel.extraData['hidden'], false); + print(channel.toJson()); + expect(channel.toJson(), { + 'id': 'cid', + 'type': 'test', + 'frozen': false, + 'cooldown': 0, + 'hidden': false, + }); + expect(ChannelModel.fromJson(channel.toJson()).toJson(), { + 'id': 'cid', + 'type': 'test', + 'frozen': false, + 'cooldown': 0, + 'hidden': false, + }); + + var newChannel = channel.copyWith( + extraData: {'hidden': true}, + ); + + expect(newChannel.extraData['hidden'], true); + expect(newChannel.hidden, true); + + newChannel = channel.copyWith( + hidden: false, + ); + + expect(newChannel.extraData['hidden'], false); + expect(newChannel.hidden, false); + + newChannel = channel.copyWith( + hidden: true, + extraData: {'hidden': true}, + ); + + expect(newChannel.extraData['hidden'], true); + expect(newChannel.hidden, true); + }); + + test('disabled property and extraData manipulation', () { + final channel = ChannelModel(cid: 'test:cid', disabled: false); + + expect(channel.disabled, false); + expect(channel.extraData['disabled'], false); + print(channel.toJson()); + expect(channel.toJson(), { + 'id': 'cid', + 'type': 'test', + 'frozen': false, + 'cooldown': 0, + 'disabled': false, + }); + expect(ChannelModel.fromJson(channel.toJson()).toJson(), { + 'id': 'cid', + 'type': 'test', + 'frozen': false, + 'cooldown': 0, + 'disabled': false, + }); + + var newChannel = channel.copyWith( + extraData: {'disabled': true}, + ); + + expect(newChannel.extraData['disabled'], true); + expect(newChannel.disabled, true); + + newChannel = channel.copyWith( + hidden: false, + ); + + expect(newChannel.extraData['disabled'], false); + expect(newChannel.disabled, false); + + newChannel = channel.copyWith( + hidden: true, + extraData: {'disabled': true}, + ); + + expect(newChannel.extraData['disabled'], true); + expect(newChannel.disabled, true); + }); + + test('truncatedAt property and extraData manipulation', () { + final currentDate = DateTime.now(); + final channel = ChannelModel(cid: 'test:cid', truncatedAt: currentDate); + + expect(channel.truncatedAt, currentDate); + expect(channel.extraData['truncated_at'], currentDate.toIso8601String()); + print(channel.toJson()); + expect(channel.toJson(), { + 'id': 'cid', + 'type': 'test', + 'frozen': false, + 'cooldown': 0, + 'truncated_at': currentDate.toIso8601String(), + }); + expect(ChannelModel.fromJson(channel.toJson()).toJson(), { + 'id': 'cid', + 'type': 'test', + 'frozen': false, + 'cooldown': 0, + 'truncated_at': currentDate.toIso8601String(), + }); + + final dateOne = DateTime.now(); + var newChannel = channel.copyWith( + extraData: {'truncated_at': dateOne.toIso8601String()}, + ); + + expect(newChannel.extraData['truncated_at'], dateOne.toIso8601String()); + expect(newChannel.truncatedAt, dateOne); + + final dateTwo = DateTime.now(); + newChannel = channel.copyWith( + truncatedAt: dateTwo, + ); + + expect(newChannel.extraData['truncated_at'], dateTwo.toIso8601String()); + expect(newChannel.truncatedAt, dateTwo); + + final dateThree = DateTime.now(); + newChannel = channel.copyWith( + truncatedAt: dateThree, + extraData: {'truncated_at': dateThree.toIso8601String()}, + ); + + expect(newChannel.extraData['truncated_at'], dateThree.toIso8601String()); + expect(newChannel.truncatedAt, dateThree); + }); }