Merge remote-tracking branch 'origin/develop' into v4

# Conflicts:
#	packages/stream_chat/CHANGELOG.md
#	packages/stream_chat/lib/src/client/channel.dart
#	packages/stream_chat_flutter/CHANGELOG.md
This commit is contained in:
xsahil03x
2022-04-01 17:31:41 +05:30
19 changed files with 268 additions and 98 deletions
+16 -1
View File
@@ -8,9 +8,24 @@
- Minor fixes and improvements. - Minor fixes and improvements.
## Upcoming
🐞 Fixed
- Fixed reactions not working for threads in offline mode.
- [[#1046]](https://github.com/GetStream/stream-chat-flutter/issues/1046) After `/mute` command on reload cannot access
any channel.
- [[#1047]](https://github.com/GetStream/stream-chat-flutter/issues/1047) `own_capabilities` extraData missing after
channel update.
✅ Added
- Handle `event.message` in `channel.truncate` events
## 3.5.1 ## 3.5.1
🐞 Fixed 🐞 Fixed
- `channel.unreadCount` was being set as using global unread count on a very specific case. - `channel.unreadCount` was being set as using global unread count on a very specific case.
- The reconnection logic for the WebSocket connection is now more robust. - The reconnection logic for the WebSocket connection is now more robust.
@@ -26,7 +41,7 @@
- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages. - [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages.
Thanks [bstolinski](https://github.com/bstolinski). Thanks [bstolinski](https://github.com/bstolinski).
- [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`. - [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`.
- [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891) Fixed reply counter for parent message not - [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891) Fixed reply counter for parent message not
updating correctly after deleting thread message. updating correctly after deleting thread message.
- Fix `channelState.copyWith` with respect to pinnedMessages. - Fix `channelState.copyWith` with respect to pinnedMessages.
@@ -1620,9 +1620,7 @@ class ChannelClientState {
_subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) { _subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) {
final channel = e.channel!; final channel = e.channel!;
updateChannelState(channelState.copyWith( updateChannelState(channelState.copyWith(
channel: channel.copyWith( channel: channelState.channel?.merge(channel),
ownCapabilities: channelState.channel?.ownCapabilities,
),
members: channel.members, members: channel.members,
)); ));
})); }));
@@ -1636,6 +1634,9 @@ class ChannelClientState {
await _channel._client.chatPersistenceClient await _channel._client.chatPersistenceClient
?.deleteMessageByCid(channel.cid); ?.deleteMessageByCid(channel.cid);
truncate(); truncate();
if (event.message != null) {
updateMessage(event.message!);
}
})); }));
} }
@@ -2151,12 +2152,12 @@ class ChannelClientState {
final BehaviorSubject<Map<String, List<Message>>> _threadsController = final BehaviorSubject<Map<String, List<Message>>> _threadsController =
BehaviorSubject.seeded({}); BehaviorSubject.seeded({});
set _threads(Map<String, List<Message>> v) { set _threads(Map<String, List<Message>> threads) {
_channel.client.chatPersistenceClient?.updateMessages( _threadsController.add(threads);
_channel.client.chatPersistenceClient?.updateChannelThreads(
_channel.cid!, _channel.cid!,
v.values.expand((v) => v).toList(), threads,
); );
_threadsController.add(v);
} }
/// Channel related typing users last value. /// Channel related typing users last value.
@@ -181,7 +181,7 @@ class ChannelModel {
updatedAt: other.updatedAt, updatedAt: other.updatedAt,
deletedAt: other.deletedAt, deletedAt: other.deletedAt,
memberCount: other.memberCount, memberCount: other.memberCount,
extraData: other.extraData, extraData: {...extraData, ...other.extraData},
team: other.team, team: other.team,
cooldown: other.cooldown, cooldown: other.cooldown,
); );
@@ -0,0 +1,37 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/core/models/channel_model.dart';
import 'package:stream_chat/src/core/models/user.dart';
part 'channel_mute.g.dart';
/// The class that contains the information about a muted channel
@JsonSerializable(createToJson: false)
class ChannelMute {
/// Constructor used for json serialization
ChannelMute({
required this.user,
required this.channel,
required this.createdAt,
required this.updatedAt,
this.expires,
});
/// Create a new instance from a json
factory ChannelMute.fromJson(Map<String, dynamic> json) =>
_$ChannelMuteFromJson(json);
/// The user that performed the muting action
final User user;
/// The target channel
final ChannelModel channel;
/// The date in which the channel was muted
final DateTime createdAt;
/// The date of the last update
final DateTime updatedAt;
/// The date in which the mute expires
final DateTime? expires;
}
@@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'channel_mute.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ChannelMute _$ChannelMuteFromJson(Map<String, dynamic> json) => ChannelMute(
user: User.fromJson(json['user'] as Map<String, dynamic>),
channel: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
expires: json['expires'] == null
? null
: DateTime.parse(json['expires'] as String),
);
@@ -1,7 +1,5 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/core/models/channel_model.dart';
import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/models/user.dart';
import 'package:stream_chat/src/core/util/serializer.dart';
part 'mute.g.dart'; part 'mute.g.dart';
@@ -11,27 +9,27 @@ class Mute {
/// Constructor used for json serialization /// Constructor used for json serialization
Mute({ Mute({
required this.user, required this.user,
required this.channel, required this.target,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
this.expires,
}); });
/// Create a new instance from a json /// Create a new instance from a json
factory Mute.fromJson(Map<String, dynamic> json) => _$MuteFromJson(json); factory Mute.fromJson(Map<String, dynamic> json) => _$MuteFromJson(json);
/// The user that performed the muting action /// The user that performed the muting action
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
final User user; final User user;
/// The target user /// The target user
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly) final User target;
final ChannelModel channel;
/// The date in which the use was muted /// The date in which the use was muted
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
final DateTime createdAt; final DateTime createdAt;
/// The date of the last update /// The date of the last update
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
final DateTime updatedAt; final DateTime updatedAt;
/// The date in which the mute expires
final DateTime? expires;
} }
@@ -8,7 +8,10 @@ part of 'mute.dart';
Mute _$MuteFromJson(Map<String, dynamic> json) => Mute( Mute _$MuteFromJson(Map<String, dynamic> json) => Mute(
user: User.fromJson(json['user'] as Map<String, dynamic>), user: User.fromJson(json['user'] as Map<String, dynamic>),
channel: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>), target: User.fromJson(json['target'] as Map<String, dynamic>),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
expires: json['expires'] == null
? null
: DateTime.parse(json['expires'] as String),
); );
@@ -1,4 +1,5 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/core/models/channel_mute.dart';
import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/util/serializer.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
@@ -79,7 +80,7 @@ class OwnUser extends User {
bool? banned, bool? banned,
DateTime? banExpires, DateTime? banExpires,
List<String>? teams, List<String>? teams,
List<Mute>? channelMutes, List<ChannelMute>? channelMutes,
List<Device>? devices, List<Device>? devices,
List<Mute>? mutes, List<Mute>? mutes,
int? totalUnreadCount, int? totalUnreadCount,
@@ -142,7 +143,7 @@ class OwnUser extends User {
/// List of channels muted by the user. /// List of channels muted by the user.
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
final List<Mute> channelMutes; final List<ChannelMute> channelMutes;
/// Total unread messages by the user. /// Total unread messages by the user.
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
@@ -18,7 +18,7 @@ OwnUser _$OwnUserFromJson(Map<String, dynamic> json) => OwnUser(
totalUnreadCount: json['total_unread_count'] as int? ?? 0, totalUnreadCount: json['total_unread_count'] as int? ?? 0,
unreadChannels: json['unread_channels'] as int? ?? 0, unreadChannels: json['unread_channels'] as int? ?? 0,
channelMutes: (json['channel_mutes'] as List<dynamic>?) channelMutes: (json['channel_mutes'] as List<dynamic>?)
?.map((e) => Mute.fromJson(e as Map<String, dynamic>)) ?.map((e) => ChannelMute.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
const [], const [],
id: json['id'] as String, id: json['id'] as String,
@@ -197,6 +197,28 @@ abstract class ChatPersistenceClient {
/// Deletes all the members by channel [cids] /// Deletes all the members by channel [cids]
Future<void> deleteMembersByCids(List<String> cids); Future<void> deleteMembersByCids(List<String> cids);
/// Updates the channel [cid] threads data along with reactions and users.
Future<void> updateChannelThreads(
String cid,
Map<String, List<Message>> threads,
) async {
final messages = threads.values.expand((it) => it).toList();
// Removing old reactions before saving the new
final oldReactions = messages.map((it) => it.id).toList();
await deleteReactionsByMessageId(oldReactions);
// Adding new reactions and users data
final reactions = messages.expand(_expandReactions).toList();
final users = messages.map((it) => it.user).withNullifyer.toList();
await Future.wait([
updateMessages(cid, messages),
updateReactions(reactions),
updateUsers(users),
]);
}
/// Update the channel state data using [channelState] /// Update the channel state data using [channelState]
Future<void> updateChannelState(ChannelState channelState) => Future<void> updateChannelState(ChannelState channelState) =>
updateChannelStates([channelState]); updateChannelStates([channelState]);
@@ -239,17 +261,8 @@ abstract class ChatPersistenceClient {
channelWithMessages[cid] = messages; channelWithMessages[cid] = messages;
channelWithPinnedMessages[cid] = pinnedMessages; channelWithPinnedMessages[cid] = pinnedMessages;
List<Reaction> expandReactions(Message message) { reactions.addAll(messages.expand(_expandReactions));
final own = message.ownReactions; pinnedReactions.addAll(pinnedMessages.expand(_expandReactions));
final latest = message.latestReactions;
return [
if (own != null) ...own.where((r) => r.userId != null),
if (latest != null) ...latest.where((r) => r.userId != null),
];
}
reactions.addAll(messages.expand(expandReactions));
pinnedReactions.addAll(pinnedMessages.expand(expandReactions));
users.addAll([ users.addAll([
channel.createdBy, channel.createdBy,
@@ -292,4 +305,13 @@ abstract class ChatPersistenceClient {
), ),
]); ]);
} }
List<Reaction> _expandReactions(Message message) {
final own = message.ownReactions;
final latest = message.latestReactions;
return [
if (own != null) ...own.where((r) => r.userId != null),
if (latest != null) ...latest.where((r) => r.userId != null),
];
}
} }
+74
View File
@@ -0,0 +1,74 @@
{
"user": {
"id": "super-band-9",
"role": "user",
"created_at": "2020-03-03T16:48:28.853674Z",
"updated_at": "2021-05-26T03:22:20.296181Z",
"last_active": "2021-06-16T11:42:29.466165498Z",
"banned": false,
"online": true,
"username": "Rioland",
"image": "https://placehold.jp/150x150.png",
"invisible": false,
"name": "Proud darkness",
"unread_count": 0
},
"channel": {
"id": "!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw",
"type": "messaging",
"cid": "messaging:!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw",
"last_message_at": "2020-12-02T06:56:18.003432Z",
"created_at": "2020-11-30T10:25:32.494601Z",
"updated_at": "2020-11-30T10:25:32.494601Z",
"created_by": {
"id": "super-band-9",
"role": "user",
"created_at": "2020-03-03T16:48:28.853674Z",
"updated_at": "2021-05-26T03:22:20.296181Z",
"last_active": "2021-06-16T11:42:29.466165498Z",
"banned": false,
"online": true,
"image": "https://placehold.jp/150x150.png",
"invisible": false,
"name": "Proud darkness",
"unread_count": 0,
"username": "Rioland"
},
"frozen": false,
"disabled": false,
"member_count": 2,
"config": {
"created_at": "2020-04-15T14:57:17.00966Z",
"updated_at": "2021-05-25T14:25:30.405621Z",
"name": "messaging",
"typing_events": true,
"read_events": true,
"connect_events": true,
"search": true,
"reactions": true,
"replies": true,
"mutes": true,
"uploads": true,
"url_enrichment": true,
"custom_events": false,
"push_notifications": true,
"message_retention": "infinite",
"max_message_length": 5000,
"automod": "disabled",
"automod_behavior": "flag",
"blocklist": "profanity_en_2020_v1",
"blocklist_behavior": "block",
"automod_thresholds": {},
"commands": [
{
"name": "giphy",
"description": "Post a random gif to the channel",
"args": "[text]",
"set": "fun_set"
}
]
}
},
"created_at": "2020-12-04T10:39:06.512021Z",
"updated_at": "2020-12-04T10:39:06.512021Z"
}
+13 -55
View File
@@ -13,61 +13,19 @@
"name": "Proud darkness", "name": "Proud darkness",
"unread_count": 0 "unread_count": 0
}, },
"channel": { "target": {
"id": "!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw", "id": "super-band-10",
"type": "messaging", "role": "user",
"cid": "messaging:!members-Qsp7PpigdPkW0rJk0603y5GnTiF1iRfoDc4SAngMMmw", "created_at": "2020-03-03T16:48:28.853674Z",
"last_message_at": "2020-12-02T06:56:18.003432Z", "updated_at": "2021-05-26T03:22:20.296181Z",
"created_at": "2020-11-30T10:25:32.494601Z", "last_active": "2021-06-16T11:42:29.466165498Z",
"updated_at": "2020-11-30T10:25:32.494601Z", "banned": false,
"created_by": { "online": true,
"id": "super-band-9", "username": "Holland",
"role": "user", "image": "https://placehold.jp/150x150.png",
"created_at": "2020-03-03T16:48:28.853674Z", "invisible": false,
"updated_at": "2021-05-26T03:22:20.296181Z", "name": "Proud brightness",
"last_active": "2021-06-16T11:42:29.466165498Z", "unread_count": 0
"banned": false,
"online": true,
"image": "https://placehold.jp/150x150.png",
"invisible": false,
"name": "Proud darkness",
"unread_count": 0,
"username": "Rioland"
},
"frozen": false,
"disabled": false,
"member_count": 2,
"config": {
"created_at": "2020-04-15T14:57:17.00966Z",
"updated_at": "2021-05-25T14:25:30.405621Z",
"name": "messaging",
"typing_events": true,
"read_events": true,
"connect_events": true,
"search": true,
"reactions": true,
"replies": true,
"mutes": true,
"uploads": true,
"url_enrichment": true,
"custom_events": false,
"push_notifications": true,
"message_retention": "infinite",
"max_message_length": 5000,
"automod": "disabled",
"automod_behavior": "flag",
"blocklist": "profanity_en_2020_v1",
"blocklist_behavior": "block",
"automod_thresholds": {},
"commands": [
{
"name": "giphy",
"description": "Post a random gif to the channel",
"args": "[text]",
"set": "fun_set"
}
]
}
}, },
"created_at": "2020-12-04T10:39:06.512021Z", "created_at": "2020-12-04T10:39:06.512021Z",
"updated_at": "2020-12-04T10:39:06.512021Z" "updated_at": "2020-12-04T10:39:06.512021Z"
@@ -645,8 +645,8 @@ void main() {
when(() => persistence.getChannelThreads(any())) when(() => persistence.getChannelThreads(any()))
.thenAnswer((_) async => {}); .thenAnswer((_) async => {});
when(() => persistence.updateMessages(any(), any())) when(() => persistence.updateChannelThreads(any(), any()))
.thenAnswer((_) => Future.value()); .thenAnswer((_) async => {});
when(() => persistence.getChannelStateByCid(any(), when(() => persistence.getChannelStateByCid(any(),
messagePagination: any(named: 'messagePagination'), messagePagination: any(named: 'messagePagination'),
pinnedMessagePagination: pinnedMessagePagination:
@@ -692,7 +692,7 @@ void main() {
verify(() => persistence.getChannelThreads(any())) verify(() => persistence.getChannelThreads(any()))
.called((persistentChannelStates + channelStates).length); .called((persistentChannelStates + channelStates).length);
verify(() => persistence.updateMessages(any(), any())) verify(() => persistence.updateChannelThreads(any(), any()))
.called((persistentChannelStates + channelStates).length); .called((persistentChannelStates + channelStates).length);
verify( verify(
() => persistence.getChannelStateByCid(any(), () => persistence.getChannelStateByCid(any(),
@@ -733,8 +733,8 @@ void main() {
when(() => persistence.getChannelThreads(any())) when(() => persistence.getChannelThreads(any()))
.thenAnswer((_) async => {}); .thenAnswer((_) async => {});
when(() => persistence.updateMessages(any(), any())) when(() => persistence.updateChannelThreads(any(), any()))
.thenAnswer((_) => Future.value()); .thenAnswer((_) async => {});
when(() => persistence.getChannelStateByCid(any(), when(() => persistence.getChannelStateByCid(any(),
messagePagination: any(named: 'messagePagination'), messagePagination: any(named: 'messagePagination'),
pinnedMessagePagination: pinnedMessagePagination:
@@ -775,7 +775,7 @@ void main() {
verify(() => persistence.getChannelThreads(any())) verify(() => persistence.getChannelThreads(any()))
.called(persistentChannelStates.length); .called(persistentChannelStates.length);
verify(() => persistence.updateMessages(any(), any())) verify(() => persistence.updateChannelThreads(any(), any()))
.called(persistentChannelStates.length); .called(persistentChannelStates.length);
verify( verify(
() => persistence.getChannelStateByCid(any(), () => persistence.getChannelStateByCid(any(),
@@ -0,0 +1,18 @@
import 'package:stream_chat/src/core/models/channel_model.dart';
import 'package:stream_chat/src/core/models/channel_mute.dart';
import 'package:stream_chat/src/core/models/user.dart';
import 'package:test/test.dart';
import '../../utils.dart';
void main() {
group('src/models/channel_mute', () {
test('should parse json correctly', () {
final mute = ChannelMute.fromJson(jsonFixture('channel_mute.json'));
expect(mute.user, isA<User>());
expect(mute.channel, isA<ChannelModel>());
expect(mute.createdAt, DateTime.parse('2020-12-04T10:39:06.512021Z'));
expect(mute.updatedAt, DateTime.parse('2020-12-04T10:39:06.512021Z'));
});
});
}
@@ -1,4 +1,3 @@
import 'package:stream_chat/src/core/models/channel_model.dart';
import 'package:stream_chat/src/core/models/mute.dart'; import 'package:stream_chat/src/core/models/mute.dart';
import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/models/user.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@@ -6,12 +5,13 @@ import 'package:test/test.dart';
import '../../utils.dart'; import '../../utils.dart';
void main() { void main() {
group('src/models/mute', () { group('src/models/channel_mute', () {
test('should parse json correctly', () { test('should parse json correctly', () {
final mute = Mute.fromJson(jsonFixture('mute.json')); final mute = Mute.fromJson(jsonFixture('mute.json'));
expect(mute.channel, isA<ChannelModel>());
expect(mute.user, isA<User>()); expect(mute.user, isA<User>());
expect(mute.target, isA<User>());
expect(mute.createdAt, DateTime.parse('2020-12-04T10:39:06.512021Z')); expect(mute.createdAt, DateTime.parse('2020-12-04T10:39:06.512021Z'));
expect(mute.updatedAt, DateTime.parse('2020-12-04T10:39:06.512021Z'));
}); });
}); });
} }
@@ -1,4 +1,5 @@
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/core/models/channel_mute.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@@ -6,12 +7,14 @@ import '../../utils.dart';
class MockMute extends Mock implements Mute {} class MockMute extends Mock implements Mute {}
class ChannelMockMute extends Mock implements ChannelMute {}
class MockDevice extends Mock implements Device {} class MockDevice extends Mock implements Device {}
void main() { void main() {
final devices = [MockDevice(), MockDevice()]; final devices = [MockDevice(), MockDevice()];
final mutes = [MockMute(), MockMute()]; final mutes = [MockMute(), MockMute()];
final channelMutes = [MockMute()]; final channelMutes = [ChannelMockMute()];
final createdAt = DateTime.parse('2021-05-03 12:39:21.817646'); final createdAt = DateTime.parse('2021-05-03 12:39:21.817646');
final updatedAt = DateTime.parse('2021-04-03 12:39:21.817646'); final updatedAt = DateTime.parse('2021-04-03 12:39:21.817646');
final lastActive = DateTime.parse('2021-03-03 12:39:21.817646'); final lastActive = DateTime.parse('2021-03-03 12:39:21.817646');
@@ -162,6 +162,23 @@ void main() {
expect(channelState, isNotNull); expect(channelState, isNotNull);
}); });
test('updateChannelThreads', () async {
const cid = 'test:cid';
final user = User(id: 'test-user-id');
final threads = {
'parent-test-message': [
Message(
id: 'test-message',
text: 'test-message',
user: user,
ownReactions: [Reaction(type: 'test', user: user)],
latestReactions: [Reaction(type: 'test', user: user)],
)
]
};
persistenceClient.updateChannelThreads(cid, threads);
});
test('updateChannelState', () async { test('updateChannelState', () async {
final channelState = ChannelState(); final channelState = ChannelState();
persistenceClient.updateChannelState(channelState); persistenceClient.updateChannelState(channelState);
@@ -18,6 +18,12 @@
- Minor fixes and improvements - Minor fixes and improvements
## Upcoming
🐞 Fixed
-[[#892]](https://github.com/GetStream/stream-chat-flutter/issues/892): Fix default `initialAlignment` in `MessageListView`.
## 3.5.1 ## 3.5.1
🛑️ Breaking Changes 🛑️ Breaking Changes
@@ -372,7 +372,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
double get _initialAlignment { double get _initialAlignment {
final initialAlignment = widget.initialAlignment; final initialAlignment = widget.initialAlignment;
if (initialAlignment != null) return initialAlignment; if (initialAlignment != null) return initialAlignment;
return 0.1; return streamChannel!.initialMessageId == null ? 0 : 0.1;
} }
bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id;