From beee0e41c43bcc545abaeb08c2d55a4946291bd6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 19 May 2022 15:50:51 +0530 Subject: [PATCH 1/5] feat(llc): create disabled, hidden, truncatedAt a field in channel Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/channel.dart | 36 +++++++++++++++++++ .../lib/src/core/models/channel_model.dart | 26 +++++++++++++- .../lib/src/core/models/channel_model.g.dart | 8 +++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index f5d874047..f664daf8a 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -195,6 +195,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..9fca07b51 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -25,6 +25,9 @@ class ChannelModel { this.extraData = const {}, this.team, this.cooldown = 0, + this.disabled = false, + this.hidden = false, + this.truncatedAt, }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), 'provide either a cid or an id and type', @@ -92,6 +95,15 @@ class ChannelModel { @JsonKey(includeIfNull: false) final int cooldown; + /// True if the channel is disabled + final bool disabled; + + /// True if the channel is hidden + final bool hidden; + + /// The date of the last time channel got truncated + final DateTime? truncatedAt; + /// Map of custom channel extraData @JsonKey(includeIfNull: false) final Map extraData; @@ -117,6 +129,9 @@ class ChannelModel { 'member_count', 'team', 'cooldown', + 'disabled', + 'hidden', + 'truncated_at', ]; /// Shortcut for channel name @@ -145,6 +160,9 @@ class ChannelModel { Map? extraData, String? team, int? cooldown, + bool? disabled, + bool? hidden, + DateTime? truncatedAt, }) => ChannelModel( id: id ?? this.id, @@ -162,6 +180,9 @@ class ChannelModel { extraData: extraData ?? this.extraData, team: team ?? this.team, cooldown: cooldown ?? this.cooldown, + disabled: disabled ?? this.disabled, + hidden: hidden ?? this.hidden, + truncatedAt: truncatedAt ?? this.truncatedAt, ); /// Returns a new [ChannelModel] that is a combination of this channelModel @@ -181,9 +202,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/channel_model.g.dart b/packages/stream_chat/lib/src/core/models/channel_model.g.dart index 9bd8f062e..ee888ae7f 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.g.dart @@ -36,6 +36,11 @@ ChannelModel _$ChannelModelFromJson(Map json) => ChannelModel( extraData: json['extra_data'] as Map? ?? const {}, team: json['team'] as String?, cooldown: json['cooldown'] as int? ?? 0, + disabled: json['disabled'] as bool? ?? false, + hidden: json['hidden'] as bool? ?? false, + truncatedAt: json['truncated_at'] == null + ? null + : DateTime.parse(json['truncated_at'] as String), ); Map _$ChannelModelToJson(ChannelModel instance) { @@ -61,6 +66,9 @@ Map _$ChannelModelToJson(ChannelModel instance) { writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); val['cooldown'] = instance.cooldown; + val['disabled'] = instance.disabled; + val['hidden'] = instance.hidden; + val['truncated_at'] = instance.truncatedAt?.toIso8601String(); val['extra_data'] = instance.extraData; writeNotNull('team', readonly(instance.team)); return val; From de3e1c9862a044f1dda59a01837e01e128f313d9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 19 May 2022 18:07:22 +0530 Subject: [PATCH 2/5] refactor(llc): convert the implementation into non-breaking Signed-off-by: xsahil03x --- .../lib/src/core/models/channel_model.dart | 48 +++++-- .../lib/src/core/models/channel_model.g.dart | 8 -- .../lib/src/core/models/event.dart | 4 + .../lib/src/core/models/event.g.dart | 3 + .../lib/src/core/models/own_user.dart | 6 +- .../stream_chat/lib/src/core/models/user.dart | 7 +- .../test/src/core/models/channel_test.dart | 136 ++++++++++++++++++ 7 files changed, 182 insertions(+), 30 deletions(-) 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 9fca07b51..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,12 +22,12 @@ class ChannelModel { DateTime? updatedAt, this.deletedAt, this.memberCount = 0, - this.extraData = const {}, + Map extraData = const {}, this.team, this.cooldown = 0, - this.disabled = false, - this.hidden = false, - this.truncatedAt, + bool? disabled, + bool? hidden, + DateTime? truncatedAt, }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), 'provide either a cid or an id and type', @@ -37,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) => @@ -96,13 +107,20 @@ class ChannelModel { final int cooldown; /// True if the channel is disabled - final bool disabled; + @JsonKey(ignore: true) + bool? get disabled => extraData['disabled'] as bool?; /// True if the channel is hidden - final bool hidden; + @JsonKey(ignore: true) + bool? get hidden => extraData['hidden'] as bool?; /// The date of the last time channel got truncated - final DateTime? truncatedAt; + @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) @@ -129,9 +147,6 @@ class ChannelModel { 'member_count', 'team', 'cooldown', - 'disabled', - 'hidden', - 'truncated_at', ]; /// Shortcut for channel name @@ -180,9 +195,14 @@ class ChannelModel { extraData: extraData ?? this.extraData, team: team ?? this.team, cooldown: cooldown ?? this.cooldown, - disabled: disabled ?? this.disabled, - hidden: hidden ?? this.hidden, - truncatedAt: truncatedAt ?? this.truncatedAt, + 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 diff --git a/packages/stream_chat/lib/src/core/models/channel_model.g.dart b/packages/stream_chat/lib/src/core/models/channel_model.g.dart index ee888ae7f..9bd8f062e 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.g.dart @@ -36,11 +36,6 @@ ChannelModel _$ChannelModelFromJson(Map json) => ChannelModel( extraData: json['extra_data'] as Map? ?? const {}, team: json['team'] as String?, cooldown: json['cooldown'] as int? ?? 0, - disabled: json['disabled'] as bool? ?? false, - hidden: json['hidden'] as bool? ?? false, - truncatedAt: json['truncated_at'] == null - ? null - : DateTime.parse(json['truncated_at'] as String), ); Map _$ChannelModelToJson(ChannelModel instance) { @@ -66,9 +61,6 @@ Map _$ChannelModelToJson(ChannelModel instance) { writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); val['cooldown'] = instance.cooldown; - val['disabled'] = instance.disabled; - val['hidden'] = instance.hidden; - val['truncated_at'] = instance.truncatedAt?.toIso8601String(); val['extra_data'] = instance.extraData; writeNotNull('team', readonly(instance.team)); return val; 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..783b9371e 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -76,10 +76,8 @@ 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? ?? this.name, + image: image ?? extraData?['image'] as String? ?? this.image, banned: banned ?? this.banned, banExpires: banExpires ?? this.banExpires, createdAt: createdAt ?? this.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..2c702d019 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,8 @@ 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? ?? this.name, + 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/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); + }); } From 8ba8402d80728aaeb6705ade6b81866945be39c3 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 19 May 2022 18:11:51 +0530 Subject: [PATCH 3/5] chore(llc): update CHANGELOG.md Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 48c4dd4da..5deaa48dc 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -7,10 +7,16 @@ - 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`. 🔄 Changed -- Deprecated `PaginationParams.before` and `PaginationParams.after`. Use `PaginationParams.limit` instead. +- 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 From b3e598629043a4c2cfb9216ea7463fa96be6296d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 19 May 2022 18:53:05 +0530 Subject: [PATCH 4/5] test(llc): fix own_user test Signed-off-by: xsahil03x --- packages/stream_chat/lib/src/core/models/own_user.dart | 4 ++++ 1 file changed, 4 insertions(+) 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 783b9371e..f79d8eab7 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,8 @@ class OwnUser extends User { factory OwnUser.fromUser(User user) => OwnUser( id: user.id, role: user.role, + name: user.name, + image: user.image, createdAt: user.createdAt, updatedAt: user.updatedAt, lastActive: user.lastActive, @@ -101,6 +103,8 @@ class OwnUser extends User { return copyWith( id: other.id, role: other.role, + name: other.name, + image: other.image, banned: other.banned, channelMutes: other.channelMutes, createdAt: other.createdAt, From 89a3d0947837609f41796c1d30e3bb17c56588ec Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 20 May 2022 15:43:27 +0530 Subject: [PATCH 5/5] test(llc): fix user, own_user tests Signed-off-by: xsahil03x --- .../stream_chat/lib/src/core/models/own_user.dart | 11 ++++++++--- packages/stream_chat/lib/src/core/models/user.dart | 5 ++++- packages/stream_chat/test/src/client/client_test.dart | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) 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 f79d8eab7..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,7 +41,8 @@ class OwnUser extends User { factory OwnUser.fromUser(User user) => OwnUser( id: user.id, role: user.role, - name: user.name, + // 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, @@ -78,7 +79,10 @@ class OwnUser extends User { OwnUser( id: id ?? this.id, role: role ?? this.role, - name: name ?? extraData?['name'] as String? ?? this.name, + 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, @@ -103,7 +107,8 @@ class OwnUser extends User { return copyWith( id: other.id, role: other.role, - name: other.name, + // 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, diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 2c702d019..ff435f67b 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -172,7 +172,10 @@ class User extends Equatable { User( id: id ?? this.id, role: role ?? this.role, - name: name ?? extraData?['name'] as String? ?? this.name, + 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, 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);