Merge branch 'develop' into release/4.2.0
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<bool> 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<bool> 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<DateTime?> get truncatedAtStream {
|
||||
_checkInitialized();
|
||||
return state!.channelStateStream.map((cs) => cs.channel?.truncatedAt);
|
||||
}
|
||||
|
||||
/// Cooldown count
|
||||
int get cooldown {
|
||||
_checkInitialized();
|
||||
|
||||
@@ -22,9 +22,12 @@ class ChannelModel {
|
||||
DateTime? updatedAt,
|
||||
this.deletedAt,
|
||||
this.memberCount = 0,
|
||||
this.extraData = const {},
|
||||
Map<String, Object?> 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<String, dynamic> 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<String, Object?> extraData;
|
||||
@@ -145,6 +175,9 @@ class ChannelModel {
|
||||
Map<String, Object?>? 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object?>? extraData,
|
||||
super.cooldown,
|
||||
super.team,
|
||||
super.disabled,
|
||||
super.hidden,
|
||||
super.truncatedAt,
|
||||
}) : super(extraData: extraData ?? {});
|
||||
|
||||
/// Create a new instance from a json
|
||||
|
||||
@@ -81,6 +81,9 @@ EventChannel _$EventChannelFromJson(Map<String, dynamic> json) => EventChannel(
|
||||
id: json['id'] as String?,
|
||||
type: json['type'] as String?,
|
||||
cid: json['cid'] as String,
|
||||
ownCapabilities: (json['own_capabilities'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
config: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
|
||||
createdBy: json['created_by'] == null
|
||||
? null
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user