Skip to content

Commit f1517aa

Browse files
committed
fix(ChatScrollObserver): refine the logic of standby
In some cases, the result of observeFirstItem is not required.
1 parent ce98d04 commit f1517aa

File tree

2 files changed

+124
-8
lines changed

2 files changed

+124
-8
lines changed

lib/src/utils/src/chat/chat_scroll_observer.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class ChatScrollObserver {
8383
/// If the return value is null, the default processing will be performed.
8484
ChatScrollObserverCustomAdjustPosition? customAdjustPosition;
8585

86-
/// Observation result of reference subparts after ScrollView children update.
86+
/// Observation result of reference item after ScrollView children update.
8787
ListViewObserveDisplayingChildModel? observeRefItem() {
8888
return observerController.observeItem(
8989
index: refItemIndexAfterUpdate,
@@ -127,20 +127,24 @@ class ChatScrollObserver {
127127
this.changeCount = changeCount;
128128
observeSwitchShrinkWrap();
129129

130-
final firstItemModel = observerController.observeFirstItem(
131-
sliverContext: sliverContext,
132-
);
133-
if (firstItemModel == null) return;
134130
int _innerRefItemIndex;
135131
int _innerRefItemIndexAfterUpdate;
136132
double _innerRefItemLayoutOffset;
137133
switch (mode) {
138134
case ChatScrollObserverHandleMode.normal:
135+
final firstItemModel = observerController.observeFirstItem(
136+
sliverContext: sliverContext,
137+
);
138+
if (firstItemModel == null) return;
139139
_innerRefItemIndex = firstItemModel.index;
140140
_innerRefItemIndexAfterUpdate = _innerRefItemIndex + changeCount;
141141
_innerRefItemLayoutOffset = firstItemModel.layoutOffset;
142142
break;
143143
case ChatScrollObserverHandleMode.generative:
144+
final firstItemModel = observerController.observeFirstItem(
145+
sliverContext: sliverContext,
146+
);
147+
if (firstItemModel == null) return;
144148
int index = firstItemModel.index + changeCount;
145149
final model = observerController.observeItem(
146150
sliverContext: sliverContext,
@@ -161,6 +165,10 @@ class ChatScrollObserver {
161165

162166
switch (refIndexType) {
163167
case ChatScrollObserverRefIndexType.relativeIndexStartFromCacheExtent:
168+
final firstItemModel = observerController.observeFirstItem(
169+
sliverContext: sliverContext,
170+
);
171+
if (firstItemModel == null) return;
164172
int index = firstItemModel.index + _refItemIndex;
165173
final model = observerController.observeItem(
166174
sliverContext: sliverContext,

test/chat_scroll_observer_test.dart

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ void main() {
6161
updateData({
6262
int index = 0,
6363
}) {
64-
final appendStr = 'updateData' * 10;
65-
key.currentState?.dataList[index] += appendStr;
64+
key.currentState?.updateData(
65+
index: index,
66+
needSetState: true,
67+
);
6668
}
6769

6870
observerController.jumpTo(index: firstDisplayingChildIndex);
@@ -165,6 +167,100 @@ void main() {
165167
scrollController.dispose();
166168
});
167169

170+
testWidgets(
171+
'Keeping position with ChatScrollObserverHandleMode.specified when item changes by itself',
172+
(tester) async {
173+
GlobalKey<ChatListViewState> key = GlobalKey();
174+
final scrollController = ScrollController();
175+
final observerController = ListObserverController(
176+
controller: scrollController,
177+
);
178+
final chatScrollObserver = ChatScrollObserver(observerController)
179+
..fixedPositionOffset = -1;
180+
const observeItemSelfIndex = 0;
181+
182+
Widget widget = ChatListView(
183+
key: key,
184+
scrollController: scrollController,
185+
observerController: observerController,
186+
chatScrollObserver: chatScrollObserver,
187+
itemBuilder: (context, index) {
188+
final dataList = key.currentState?.dataList ?? [];
189+
return Text(
190+
dataList[index],
191+
maxLines: 999,
192+
);
193+
},
194+
dataList: ['initData' * 500],
195+
);
196+
await tester.pumpWidget(widget);
197+
198+
void updateData({
199+
int index = 0,
200+
}) {
201+
key.currentState?.updateData(
202+
index: index,
203+
needSetState: true,
204+
);
205+
}
206+
207+
await tester.pumpAndSettle();
208+
var result = await observerController.dispatchOnceObserve(
209+
isDependObserveCallback: false,
210+
isForce: true,
211+
);
212+
expectSync(
213+
result.observeResult?.firstChild?.index ?? 0,
214+
observeItemSelfIndex,
215+
);
216+
expectSync(
217+
result.observeResult?.firstChild?.mainAxisSize,
218+
greaterThanOrEqualTo(
219+
result.observeResult?.viewport.paintBounds.height ?? 0,
220+
),
221+
);
222+
final previousTrailingMarginToViewport =
223+
result.observeResult?.firstChild?.trailingMarginToViewport;
224+
225+
// itemIndex
226+
await chatScrollObserver.standby(
227+
mode: ChatScrollObserverHandleMode.specified,
228+
refIndexType: ChatScrollObserverRefIndexType.itemIndex,
229+
refItemIndex: observeItemSelfIndex,
230+
refItemIndexAfterUpdate: observeItemSelfIndex,
231+
customAdjustPositionDelta: (model) {
232+
return model.newPosition.extentAfter - model.oldPosition.extentAfter;
233+
},
234+
);
235+
236+
updateData();
237+
await tester.pumpAndSettle();
238+
result = await observerController.dispatchOnceObserve(
239+
isDependObserveCallback: false,
240+
isForce: true,
241+
);
242+
243+
expect(chatScrollObserver.refItemIndex, observeItemSelfIndex);
244+
expect(
245+
chatScrollObserver.refItemIndexAfterUpdate,
246+
observeItemSelfIndex,
247+
);
248+
result = await observerController.dispatchOnceObserve(
249+
isDependObserveCallback: false,
250+
isForce: true,
251+
);
252+
expectSync(
253+
result.observeResult?.firstChild?.index,
254+
observeItemSelfIndex,
255+
);
256+
expectSync(
257+
result.observeResult?.firstChild?.trailingMarginToViewport,
258+
previousTrailingMarginToViewport,
259+
);
260+
261+
scrollController.dispose();
262+
});
263+
168264
testWidgets('Keeping position with customAdjustPositionDelta',
169265
(tester) async {
170266
GlobalKey<ChatListViewState> key = GlobalKey();
@@ -388,22 +484,34 @@ class ChatListView extends StatefulWidget {
388484
required this.chatScrollObserver,
389485
this.onReceiveScrollNotification,
390486
this.itemBuilder,
487+
this.dataList,
391488
}) : super(key: key);
392489

393490
final ScrollController scrollController;
394491
final ListObserverController observerController;
395492
final ChatScrollObserver chatScrollObserver;
396493
final Function()? onReceiveScrollNotification;
397494
final NullableIndexedWidgetBuilder? itemBuilder;
495+
final List<String>? dataList;
398496

399497
@override
400498
State<ChatListView> createState() => ChatListViewState();
401499
}
402500

403501
class ChatListViewState extends State<ChatListView> {
404-
List<String> dataList =
502+
late List<String> dataList = widget.dataList ??
405503
List.generate(100, (index) => index.toString()).toList();
406504

505+
void initData({
506+
required List<String> dataList,
507+
bool needSetState = true,
508+
}) {
509+
this.dataList = dataList;
510+
if (needSetState) {
511+
setState(() {});
512+
}
513+
}
514+
407515
void updateData({
408516
int index = 0,
409517
String? appendStr,

0 commit comments

Comments
 (0)