Skip to content

Commit f0b80d5

Browse files
xsahil03xclaude
andcommitted
merge master
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d713fee commit f0b80d5

9 files changed

Lines changed: 109 additions & 131 deletions

File tree

packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,7 @@ class UnboundedRenderViewport extends RenderViewport {
227227
}
228228

229229
final top = _minScrollExtent + mainAxisExtent * effectiveAnchor;
230-
final bottom =
231-
_maxScrollExtent - mainAxisExtent * (1.0 - effectiveAnchor);
230+
final bottom = _maxScrollExtent - mainAxisExtent * (1.0 - effectiveAnchor);
232231
final maxScrollOffset = math.max<double>(math.min(0, top), bottom);
233232
final minScrollOffset = math.min<double>(top, maxScrollOffset);
234233
if (offset.applyContentDimensions(minScrollOffset, maxScrollOffset)) {

packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
306306

307307
MessageListController get _messageListController => widget.messageListController ?? _defaultController;
308308

309-
StreamSubscription? _messageNewListener;
309+
StreamSubscription<Message>? _messageNewListener;
310310
StreamSubscription? _userReadListener;
311311

312312
@override
@@ -331,9 +331,6 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
331331
debouncedMarkRead.cancel();
332332
debouncedMarkThreadRead.cancel();
333333

334-
_messageNewListener?.cancel();
335-
_userReadListener?.cancel();
336-
337334
_unreadState.value = _readUnreadSnapshot();
338335

339336
final highlightInitialMessage = widget.config.highlightInitialMessage;
@@ -350,11 +347,14 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
350347
});
351348
}
352349

353-
_messageNewListener = streamChannel!.channel.on(EventType.messageNew).listen((event) {
354-
final message = event.message;
355-
if (message == null) return;
356-
if (message.parentId != widget.parentMessage?.id) return;
350+
final state = streamChannel?.channel.state;
351+
final newMessageStream = switch (widget.parentMessage?.id) {
352+
final parentId? => state?.newThreadMessageStream(parentId),
353+
_ => state?.newMessageStream,
354+
};
357355

356+
_messageNewListener?.cancel();
357+
_messageNewListener = newMessageStream?.listen((message) {
358358
// Don't fight a scroll already in motion (drag, fling, or
359359
// still-running animated scrollTo).
360360
if (_scrollController?.isScrolling == true) return;
@@ -378,7 +378,8 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
378378
}
379379
});
380380

381-
_userReadListener = streamChannel!.channel.state?.currentUserReadStream.listen((_) {
381+
_userReadListener?.cancel();
382+
_userReadListener = state?.currentUserReadStream.listen((_) {
382383
_unreadState.value = _readUnreadSnapshot();
383384
});
384385
}

packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,7 @@ extension NewMessageStreamX on ChannelClientState {
175175
// ignored.
176176
bool _isNewTailArrival(Message candidate, Message? previous) {
177177
if (previous == null) return true;
178-
return candidate.id != previous.id &&
179-
candidate.createdAt.isAfter(previous.createdAt);
178+
return candidate.id != previous.id && candidate.createdAt.isAfter(previous.createdAt);
180179
}
181180

182181
/// A stream that emits each newly arrived bottom message in
@@ -242,8 +241,7 @@ extension NewMessageStreamX on ChannelClientState {
242241
/// carries replies for [parentMessageId]; that first snapshot seeds
243242
/// the baseline without yielding.
244243
Stream<Message> newThreadMessageStream(String parentMessageId) async* {
245-
final threadMessages =
246-
threadsStream.mapNotNull((it) => it[parentMessageId]);
244+
final threadMessages = threadsStream.mapNotNull((it) => it[parentMessageId]);
247245

248246
var lastSeen = threads[parentMessageId]?.lastOrNull;
249247
await for (final updated in threadMessages) {

packages/stream_chat_flutter/test/scrollable_positioned_list/item_key_builder_test.dart

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,14 @@ class _CountingItemState extends State<_CountingItem> {
3838

3939
@override
4040
Widget build(BuildContext context) => SizedBox(
41-
height: _itemHeight,
42-
child: Text(widget.label),
43-
);
41+
height: _itemHeight,
42+
child: Text(widget.label),
43+
);
4444
}
4545

4646
ItemPosition _topmost(ItemPositionsListener listener) {
47-
final visible = listener.itemPositions.value
48-
.where((p) => p.itemLeadingEdge < 1 && p.itemTrailingEdge > 0);
49-
return visible
50-
.reduce((a, b) => a.itemLeadingEdge < b.itemLeadingEdge ? a : b);
47+
final visible = listener.itemPositions.value.where((p) => p.itemLeadingEdge < 1 && p.itemTrailingEdge > 0);
48+
return visible.reduce((a, b) => a.itemLeadingEdge < b.itemLeadingEdge ? a : b);
5149
}
5250

5351
void main() {
@@ -243,7 +241,8 @@ void main() {
243241
expect(
244242
delta,
245243
lessThanOrEqualTo(3),
246-
reason: 'Existing items should be reused via key-based '
244+
reason:
245+
'Existing items should be reused via key-based '
247246
'reconciliation; only the newly prepended items can mount.',
248247
);
249248
},
@@ -300,16 +299,16 @@ void main() {
300299
addTearDown(tester.view.resetDevicePixelRatio);
301300

302301
Widget build(List<String> items) => MaterialApp(
303-
home: ScrollablePositionedList.builder(
304-
itemCount: items.length,
305-
// No itemKeyBuilder provided.
306-
itemPositionsListener: listener,
307-
itemBuilder: (ctx, i) => SizedBox(
308-
height: _itemHeight,
309-
child: Text(items[i]),
310-
),
311-
),
312-
);
302+
home: ScrollablePositionedList.builder(
303+
itemCount: items.length,
304+
// No itemKeyBuilder provided.
305+
itemPositionsListener: listener,
306+
itemBuilder: (ctx, i) => SizedBox(
307+
height: _itemHeight,
308+
child: Text(items[i]),
309+
),
310+
),
311+
);
313312

314313
await tester.pumpWidget(build(items));
315314
await tester.pumpAndSettle();
@@ -460,7 +459,8 @@ void main() {
460459
expect(
461460
delta,
462461
lessThanOrEqualTo(2),
463-
reason: 'Surviving items should keep their State across '
462+
reason:
463+
'Surviving items should keep their State across '
464464
'prepend in a separated list.',
465465
);
466466
},
@@ -498,7 +498,8 @@ void main() {
498498
expect(
499499
afterRebuild.itemLeadingEdge,
500500
closeTo(beforeRebuild.itemLeadingEdge, _tolerance),
501-
reason: 'Same-itemCount rebuilds must not perturb the '
501+
reason:
502+
'Same-itemCount rebuilds must not perturb the '
502503
'scroll position.',
503504
);
504505
},
@@ -524,16 +525,16 @@ void main() {
524525
var items = <String>[for (var i = 0; i < 20; i++) 'msg-$i'];
525526

526527
Widget build(List<String> items) => MaterialApp(
527-
home: ScrollablePositionedList.builder(
528-
itemCount: items.length,
529-
itemKeyBuilder: (i) => items[i],
530-
itemPositionsListener: listener,
531-
itemBuilder: (ctx, i) => SizedBox(
532-
height: _itemHeight,
533-
child: Text(items[i]),
534-
),
535-
),
536-
);
528+
home: ScrollablePositionedList.builder(
529+
itemCount: items.length,
530+
itemKeyBuilder: (i) => items[i],
531+
itemPositionsListener: listener,
532+
itemBuilder: (ctx, i) => SizedBox(
533+
height: _itemHeight,
534+
child: Text(items[i]),
535+
),
536+
),
537+
);
537538

538539
await tester.pumpWidget(build(items));
539540
await tester.pumpAndSettle();

packages/stream_chat_flutter_core/test/stream_channel_test.dart

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -794,13 +794,13 @@ void main() {
794794
}
795795

796796
List<Message> _generateMessages(int count) => List.generate(
797-
count,
798-
(i) => Message(
799-
id: 'msg-$i',
800-
createdAt: DateTime(2024).add(Duration(seconds: i)),
801-
user: User(id: 'otherUserId'),
802-
),
803-
);
797+
count,
798+
(i) => Message(
799+
id: 'msg-$i',
800+
createdAt: DateTime(2024).add(Duration(seconds: i)),
801+
user: User(id: 'otherUserId'),
802+
),
803+
);
804804

805805
setUp(() {
806806
when(() => mockChannel.cid).thenReturn('test:channel');
@@ -920,15 +920,14 @@ void main() {
920920
// the previously loaded window leaks past the reload (the original bug).
921921
var stateMessages = <Message>[];
922922

923-
List<Message> _generateMessages(int count, {required String prefix}) =>
924-
List.generate(
925-
count,
926-
(i) => Message(
927-
id: '$prefix-$i',
928-
createdAt: DateTime(2024).add(Duration(seconds: i)),
929-
user: User(id: 'otherUserId'),
930-
),
931-
);
923+
List<Message> _generateMessages(int count, {required String prefix}) => List.generate(
924+
count,
925+
(i) => Message(
926+
id: '$prefix-$i',
927+
createdAt: DateTime(2024).add(Duration(seconds: i)),
928+
user: User(id: 'otherUserId'),
929+
),
930+
);
932931

933932
setUp(() {
934933
when(() => mockChannel.cid).thenReturn('test:channel');
@@ -989,9 +988,9 @@ void main() {
989988
verifyInOrder([
990989
() => mockChannel.state.truncate(),
991990
() => mockChannel.query(
992-
preferOffline: any(named: 'preferOffline'),
993-
messagesPagination: any(named: 'messagesPagination'),
994-
),
991+
preferOffline: any(named: 'preferOffline'),
992+
messagesPagination: any(named: 'messagesPagination'),
993+
),
995994
]);
996995
},
997996
);
@@ -1003,12 +1002,14 @@ void main() {
10031002

10041003
await streamChannel.reloadChannel();
10051004

1006-
final captured = verify(
1007-
() => mockChannel.query(
1008-
preferOffline: any(named: 'preferOffline'),
1009-
messagesPagination: captureAny(named: 'messagesPagination'),
1010-
),
1011-
).captured.single as PaginationParams;
1005+
final captured =
1006+
verify(
1007+
() => mockChannel.query(
1008+
preferOffline: any(named: 'preferOffline'),
1009+
messagesPagination: captureAny(named: 'messagesPagination'),
1010+
),
1011+
).captured.single
1012+
as PaginationParams;
10121013

10131014
expect(captured.idAround, isNull);
10141015
expect(captured.createdAtAround, isNull);

packages/stream_chat_persistence/lib/src/dao/member_dao.dart

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,19 @@ class MemberDao extends DatabaseAccessor<DriftChatDatabase> with _$MemberDaoMixi
3939
) async {
4040
if (cids.isEmpty) return const {};
4141

42-
final rows = await (select(members).join([
43-
leftOuterJoin(users, members.userId.equalsExp(users.id)),
44-
])
45-
..where(
46-
members.channelCid.isIn(cids) & members.userId.equals(userId),
47-
))
48-
.get();
42+
final rows =
43+
await (select(members).join([
44+
leftOuterJoin(users, members.userId.equalsExp(users.id)),
45+
])..where(
46+
members.channelCid.isIn(cids) & members.userId.equals(userId),
47+
))
48+
.get();
4949

5050
return {
5151
for (final row in rows)
52-
row.readTable(members).channelCid: row.readTable(members).toMember(
52+
row.readTable(members).channelCid: row
53+
.readTable(members)
54+
.toMember(
5355
user: row.readTableOrNull(users)?.toUser(),
5456
),
5557
};

packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
313313
final channelModels = await db!.channelQueryDao.getChannels(filter: filter);
314314

315315
// 2) Wrap each model in a sort envelope. No state loaded yet.
316-
var envelopes = channelModels
317-
.map((m) => ChannelState(channel: m))
318-
.toList(growable: false);
316+
var envelopes = channelModels.map((m) => ChannelState(channel: m)).toList(growable: false);
319317

320318
// 3) If sort uses `pinnedAt`, preload the current user's memberships in
321319
// one batched query and attach them to the envelopes.
@@ -334,8 +332,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
334332
final total = envelopes.length;
335333
final offset = (paginationParams?.offset ?? 0).clamp(0, total);
336334
final limit = paginationParams?.limit ?? (total - offset);
337-
final pagedCids =
338-
envelopes.skip(offset).take(limit).map((s) => s.channel!.cid).toList();
335+
final pagedCids = envelopes.skip(offset).take(limit).map((s) => s.channel!.cid).toList();
339336

340337
// 6) Hydrate ONLY the page.
341338
return Future.wait(pagedCids.map(getChannelStateByCid));
@@ -556,17 +553,13 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
556553
List<ChannelState> envelopes,
557554
String currentUserId,
558555
) async {
559-
final cids = envelopes
560-
.map((s) => s.channel?.cid)
561-
.whereType<String>()
562-
.toList(growable: false);
556+
final cids = envelopes.map((s) => s.channel?.cid).whereType<String>().toList(growable: false);
563557
final memberships = await db!.memberDao.getMembershipsForChannels(
564558
cids,
565559
currentUserId,
566560
);
567561
return [
568-
for (final s in envelopes)
569-
s.copyWith(membership: memberships[s.channel?.cid]),
562+
for (final s in envelopes) s.copyWith(membership: memberships[s.channel?.cid]),
570563
];
571564
}
572565
}

packages/stream_chat_persistence/test/src/dao/member_dao_test.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ void main() {
102102
final targetUser = User(id: targetUserId);
103103
final otherUser = User(id: 'otherUserId');
104104
Member memberFor(User user) => Member(
105-
user: user,
106-
createdAt: DateTime.now(),
107-
updatedAt: DateTime.now(),
108-
);
105+
user: user,
106+
createdAt: DateTime.now(),
107+
updatedAt: DateTime.now(),
108+
);
109109
await database.userDao.updateUsers([targetUser, otherUser]);
110110
await database.channelDao.updateChannels([
111111
ChannelModel(cid: cid1),

0 commit comments

Comments
 (0)