stabilized LoadableStateConsumer widget hierarchy to prevent scroll resets, added pull-to-refresh configuration, and disabled it in chat view

This commit is contained in:
2026-05-13 18:22:25 +02:00
parent 58fb843f3d
commit 6c7d217463
2 changed files with 30 additions and 22 deletions
@@ -21,6 +21,7 @@ class LoadableStateConsumer<
final Widget Function(TState state, bool loading) child;
final void Function(TState state)? onLoad;
final bool wrapWithScrollView;
final bool enablePullToRefresh;
/// Optional predicate for callers whose [TState] always contains a non-null
/// envelope but where actual content (e.g. a nested response) is loaded
@@ -33,6 +34,7 @@ class LoadableStateConsumer<
required this.child,
this.onLoad,
this.wrapWithScrollView = false,
this.enablePullToRefresh = true,
this.isReady,
super.key,
});
@@ -62,29 +64,34 @@ class LoadableStateConsumer<
final showError = hasError && !hasContent;
final showErrorBar = hasError && hasContent;
var childWidget = ConditionalWrapper(
condition: loadableState.reFetch != null,
wrapper: (child) => RefreshIndicator(
onRefresh: () {
if (loadableState.reFetch != null) loadableState.reFetch!();
return Future.value();
},
child: ConditionalWrapper(
condition: wrapWithScrollView,
wrapper: (child) => SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: child,
),
child: child,
),
),
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: hasContent
? child(typedData as TState, isLoading)
: const SizedBox.shrink(),
),
// Keep the wrapper hierarchy stable across refresh cycles. The bloc clears
// reFetch to null while a refetch is in flight and restores it on
// completion; flipping the RefreshIndicator in and out on that signal
// would change the widget tree under the ListView and reset its scroll
// position every refresh.
final content = SizedBox(
height: MediaQuery.of(context).size.height,
child: hasContent
? child(typedData as TState, isLoading)
: const SizedBox.shrink(),
);
final scrollable = ConditionalWrapper(
condition: wrapWithScrollView,
wrapper: (child) => SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: child,
),
child: content,
);
final childWidget = enablePullToRefresh
? RefreshIndicator(
onRefresh: () {
loadableState.reFetch?.call();
return Future.value();
},
child: scrollable,
)
: scrollable;
return BlocModule<LoadableStateBloc, LoadableStateState>(
create: (context) => LoadableStateBloc(),
+1
View File
@@ -375,6 +375,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
isReady: (state) =>
state.chatResponse != null &&
state.currentToken == widget.room.token,
enablePullToRefresh: false,
child: (state, _) {
final items =
_buildMessages(state.chatResponse!).reversed.toList();