feat(ui): update scrollable_positioned_list with the latest changes.
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
+59
-34
@@ -11,6 +11,7 @@ import 'package:stream_chat_flutter/scrollable_positioned_list/src/indexed_key.d
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_listener.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_notifier.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/scroll_view.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/wrapping.dart';
|
||||
|
||||
/// A list of widgets similar to [ListView], except scroll control
|
||||
/// and position reporting is based on index rather than pixel offset.
|
||||
@@ -35,28 +36,20 @@ class PositionedList extends StatefulWidget {
|
||||
this.alignment = 0,
|
||||
this.scrollDirection = Axis.vertical,
|
||||
this.reverse = false,
|
||||
this.shrinkWrap = false,
|
||||
this.physics,
|
||||
this.padding,
|
||||
this.cacheExtent,
|
||||
this.semanticChildCount,
|
||||
this.findChildIndexCallback,
|
||||
this.addSemanticIndexes = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.keyboardDismissBehavior,
|
||||
}) : assert((positionedIndex == 0) || (positionedIndex < itemCount),
|
||||
'positionedIndex cannot be 0 and must be smaller than itemCount');
|
||||
|
||||
/// Called to find the new index of a child based on its key in case of
|
||||
/// reordering.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order in which children are returned from [builder] changes.
|
||||
/// This may result in state-loss.
|
||||
///
|
||||
/// This callback should take an input [Key], and it should return the
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
this.findChildIndexCallback,
|
||||
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
|
||||
}) : assert(
|
||||
(positionedIndex == 0) || (positionedIndex < itemCount),
|
||||
'positionedIndex must be 0 or a value less than itemCount',
|
||||
);
|
||||
|
||||
/// Number of items the [itemBuilder] can produce.
|
||||
final int itemCount;
|
||||
@@ -98,6 +91,15 @@ class PositionedList extends StatefulWidget {
|
||||
/// See [ScrollView.reverse].
|
||||
final bool reverse;
|
||||
|
||||
/// {@template flutter.widgets.scroll_view.shrinkWrap}
|
||||
/// Whether the extent of the scroll view in the [scrollDirection] should be
|
||||
/// determined by the contents being viewed.
|
||||
///
|
||||
/// Defaults to false.
|
||||
///
|
||||
/// See [ScrollView.shrinkWrap].
|
||||
final bool shrinkWrap;
|
||||
|
||||
/// How the scroll view should respond to user input.
|
||||
///
|
||||
/// For example, determines how the scroll view continues to animate after the
|
||||
@@ -132,9 +134,22 @@ class PositionedList extends StatefulWidget {
|
||||
/// See [SliverChildBuilderDelegate.addAutomaticKeepAlives].
|
||||
final bool addAutomaticKeepAlives;
|
||||
|
||||
/// [ScrollViewKeyboardDismissBehavior] the defines how this [PositionedList] will
|
||||
/// dismiss the keyboard automatically.
|
||||
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
|
||||
/// Called to find the new index of a child based on its key in case of reordering.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order of children returned from the children builder changes.
|
||||
/// This may result in state-loss.
|
||||
///
|
||||
/// This callback should take an input [Key], and it should return the
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
///
|
||||
/// See [SliverChildBuilderDelegate.findChildIndexCallback].
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
|
||||
/// Defines how this [ScrollView] will dismiss the keyboard automatically.
|
||||
///
|
||||
/// See [ScrollView.keyboardDismissBehavior].
|
||||
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _PositionedListState();
|
||||
@@ -175,12 +190,13 @@ class _PositionedListState extends State<PositionedList> {
|
||||
anchor: widget.alignment,
|
||||
center: _centerKey,
|
||||
controller: scrollController,
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
reverse: widget.reverse,
|
||||
cacheExtent: widget.cacheExtent,
|
||||
physics: widget.physics,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
semanticChildCount: widget.semanticChildCount ?? widget.itemCount,
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
slivers: <Widget>[
|
||||
if (widget.positionedIndex > 0)
|
||||
SliverPadding(
|
||||
@@ -196,9 +212,9 @@ class _PositionedListState extends State<PositionedList> {
|
||||
? widget.positionedIndex
|
||||
: widget.positionedIndex * 2,
|
||||
addSemanticIndexes: false,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
addRepaintBoundaries: widget.addRepaintBoundaries,
|
||||
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -213,10 +229,10 @@ class _PositionedListState extends State<PositionedList> {
|
||||
index + widget.positionedIndex * 2,
|
||||
),
|
||||
childCount: widget.itemCount != 0 ? 1 : 0,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
addSemanticIndexes: false,
|
||||
addRepaintBoundaries: widget.addRepaintBoundaries,
|
||||
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -234,10 +250,10 @@ class _PositionedListState extends State<PositionedList> {
|
||||
childCount: widget.separatorBuilder == null
|
||||
? widget.itemCount - widget.positionedIndex - 1
|
||||
: 2 * (widget.itemCount - widget.positionedIndex - 1),
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
addSemanticIndexes: false,
|
||||
addRepaintBoundaries: widget.addRepaintBoundaries,
|
||||
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -319,25 +335,33 @@ class _PositionedListState extends State<PositionedList> {
|
||||
if (!updateScheduled) {
|
||||
updateScheduled = true;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (registeredElements.value == null) {
|
||||
final elements = registeredElements.value;
|
||||
if (elements == null) {
|
||||
updateScheduled = false;
|
||||
return;
|
||||
}
|
||||
final positions = <ItemPosition>[];
|
||||
RenderViewport? viewport;
|
||||
for (final element in registeredElements.value!) {
|
||||
final box = element.renderObject as RenderBox?;
|
||||
viewport ??= RenderAbstractViewport.of(box) as RenderViewport?;
|
||||
if (viewport == null || box == null) {
|
||||
break;
|
||||
RenderViewportBase? viewport;
|
||||
for (final element in elements) {
|
||||
final box = element.renderObject! as RenderBox;
|
||||
viewport ??= RenderAbstractViewport.of(box) as RenderViewportBase?;
|
||||
var anchor = 0.0;
|
||||
if (viewport is RenderViewport) {
|
||||
anchor = viewport.anchor;
|
||||
}
|
||||
final key = element.widget.key as IndexedKey;
|
||||
|
||||
if (viewport is CustomRenderViewport) {
|
||||
anchor = viewport.anchor;
|
||||
}
|
||||
|
||||
final key = element.widget.key! as IndexedKey;
|
||||
// Skip this element if `box` has never been laid out.
|
||||
if (!box.hasSize) continue;
|
||||
if (widget.scrollDirection == Axis.vertical) {
|
||||
final reveal = viewport.getOffsetToReveal(box, 0).offset;
|
||||
final reveal = viewport!.getOffsetToReveal(box, 0).offset;
|
||||
if (!reveal.isFinite) continue;
|
||||
final itemOffset = reveal -
|
||||
viewport.offset.pixels +
|
||||
viewport.anchor * viewport.size.height;
|
||||
final itemOffset =
|
||||
reveal - viewport.offset.pixels + anchor * viewport.size.height;
|
||||
positions.add(ItemPosition(
|
||||
index: key.index,
|
||||
itemLeadingEdge: itemOffset.round() /
|
||||
@@ -348,6 +372,7 @@ class _PositionedListState extends State<PositionedList> {
|
||||
} else {
|
||||
final itemOffset =
|
||||
box.localToGlobal(Offset.zero, ancestor: viewport).dx;
|
||||
if (!itemOffset.isFinite) continue;
|
||||
positions.add(ItemPosition(
|
||||
index: key.index,
|
||||
itemLeadingEdge: (widget.reverse
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/viewport.dart';
|
||||
import 'package:stream_chat_flutter/scrollable_positioned_list/src/wrapping.dart';
|
||||
|
||||
/// {@template custom_scroll_view}
|
||||
/// A version of [CustomScrollView] that does not constrict the extents
|
||||
/// {@template unbounded_custom_scroll_view}
|
||||
/// A version of [CustomScrollView] that allows does not constrict the extents
|
||||
/// to be within 0 and 1. See [CustomScrollView] for more information.
|
||||
/// {@endtemplate}
|
||||
class UnboundedCustomScrollView extends CustomScrollView {
|
||||
/// {@macro custom_scroll_view}
|
||||
/// {@macro unbounded_custom_scroll_view}
|
||||
const UnboundedCustomScrollView({
|
||||
super.key,
|
||||
super.scrollDirection,
|
||||
@@ -19,19 +20,19 @@ class UnboundedCustomScrollView extends CustomScrollView {
|
||||
super.controller,
|
||||
super.primary,
|
||||
super.physics,
|
||||
super.shrinkWrap,
|
||||
bool shrinkWrap = false,
|
||||
super.center,
|
||||
double anchor = 0.0,
|
||||
super.cacheExtent,
|
||||
super.slivers,
|
||||
super.semanticChildCount,
|
||||
super.dragStartBehavior,
|
||||
ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior,
|
||||
}) : _anchor = anchor,
|
||||
super(
|
||||
keyboardDismissBehavior: keyboardDismissBehavior ??
|
||||
ScrollViewKeyboardDismissBehavior.manual,
|
||||
);
|
||||
super.keyboardDismissBehavior,
|
||||
}) : _shrinkWrap = shrinkWrap,
|
||||
_anchor = anchor,
|
||||
super(shrinkWrap: false);
|
||||
|
||||
final bool _shrinkWrap;
|
||||
|
||||
// [CustomScrollView] enforces constraints on [CustomScrollView.anchor], so
|
||||
// we need our own version.
|
||||
@@ -49,11 +50,14 @@ class UnboundedCustomScrollView extends CustomScrollView {
|
||||
AxisDirection axisDirection,
|
||||
List<Widget> slivers,
|
||||
) {
|
||||
if (shrinkWrap) {
|
||||
return ShrinkWrappingViewport(
|
||||
if (_shrinkWrap) {
|
||||
return CustomShrinkWrappingViewport(
|
||||
axisDirection: axisDirection,
|
||||
offset: offset,
|
||||
slivers: slivers,
|
||||
cacheExtent: cacheExtent,
|
||||
center: center,
|
||||
anchor: anchor,
|
||||
);
|
||||
}
|
||||
return UnboundedViewport(
|
||||
|
||||
+129
-96
@@ -37,6 +37,7 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
required this.itemBuilder,
|
||||
super.key,
|
||||
this.itemScrollController,
|
||||
this.shrinkWrap = false,
|
||||
ItemPositionsListener? itemPositionsListener,
|
||||
this.initialScrollIndex = 0,
|
||||
this.initialAlignment = 0,
|
||||
@@ -50,7 +51,7 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
this.addRepaintBoundaries = true,
|
||||
this.minCacheExtent,
|
||||
this.findChildIndexCallback,
|
||||
this.keyboardDismissBehavior,
|
||||
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
|
||||
}) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?,
|
||||
separatorBuilder = null;
|
||||
|
||||
@@ -61,6 +62,7 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
required this.itemBuilder,
|
||||
required IndexedWidgetBuilder this.separatorBuilder,
|
||||
super.key,
|
||||
this.shrinkWrap = false,
|
||||
this.itemScrollController,
|
||||
ItemPositionsListener? itemPositionsListener,
|
||||
this.initialScrollIndex = 0,
|
||||
@@ -75,24 +77,9 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
this.addRepaintBoundaries = true,
|
||||
this.minCacheExtent,
|
||||
this.findChildIndexCallback,
|
||||
this.keyboardDismissBehavior,
|
||||
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
|
||||
}) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?;
|
||||
|
||||
/// Called to find the new index of a child based on its key in case of
|
||||
/// reordering.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order in which children are returned from [builder] changes.
|
||||
/// This may result in state-loss.
|
||||
///
|
||||
/// This callback should take an input [Key], and it should return the
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
|
||||
/// [ScrollViewKeyboardDismissBehavior] the defines how this [PositionedList] will
|
||||
/// dismiss the keyboard automatically.
|
||||
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
|
||||
|
||||
/// Number of items the [itemBuilder] can produce.
|
||||
final int itemCount;
|
||||
|
||||
@@ -131,6 +118,15 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
/// See [ScrollView.reverse].
|
||||
final bool reverse;
|
||||
|
||||
/// {@template flutter.widgets.scroll_view.shrinkWrap}
|
||||
/// Whether the extent of the scroll view in the [scrollDirection] should be
|
||||
/// determined by the contents being viewed.
|
||||
///
|
||||
/// Defaults to false.
|
||||
///
|
||||
/// See [ScrollView.shrinkWrap].
|
||||
final bool shrinkWrap;
|
||||
|
||||
/// How the scroll view should respond to user input.
|
||||
///
|
||||
/// For example, determines how the scroll view continues to animate after the
|
||||
@@ -171,6 +167,23 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
/// cache extent.
|
||||
final double? minCacheExtent;
|
||||
|
||||
/// Called to find the new index of a child based on its key in case of reordering.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order of children returned from the children builder changes.
|
||||
/// This may result in state-loss.
|
||||
///
|
||||
/// This callback should take an input [Key], and it should return the
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
///
|
||||
/// See [SliverChildBuilderDelegate.findChildIndexCallback].
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
|
||||
/// Defines how this [ScrollView] will dismiss the keyboard automatically.
|
||||
///
|
||||
/// See [ScrollView.keyboardDismissBehavior].
|
||||
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ScrollablePositionedListState();
|
||||
}
|
||||
@@ -233,11 +246,15 @@ class ItemScrollController {
|
||||
Curve curve = Curves.linear,
|
||||
List<double> opacityAnimationWeights = const [40, 20, 40],
|
||||
}) {
|
||||
assert(_scrollableListState != null, '_scrollableListState cannot be null');
|
||||
assert(opacityAnimationWeights.length == 3,
|
||||
'opacityAnimationWeights.length is not equal to 3');
|
||||
assert(duration > Duration.zero,
|
||||
'duration needs to be bigger than Duration.zero');
|
||||
assert(
|
||||
_scrollableListState != null,
|
||||
'''ScrollController must be attached to a ScrollablePositionedList to scroll.''',
|
||||
);
|
||||
assert(
|
||||
opacityAnimationWeights.length == 3,
|
||||
'opacityAnimationWeights must have exactly three elements.',
|
||||
);
|
||||
assert(duration > Duration.zero, 'Duration must be greater than zero.');
|
||||
return _scrollableListState!._scrollTo(
|
||||
index: index,
|
||||
alignment: alignment,
|
||||
@@ -249,7 +266,9 @@ class ItemScrollController {
|
||||
|
||||
void _attach(_ScrollablePositionedListState scrollableListState) {
|
||||
assert(
|
||||
_scrollableListState == null, '_scrollableListState needs to be null');
|
||||
_scrollableListState == null,
|
||||
'''ScrollController must not be attached to multiple ScrollablePositionedLists.''',
|
||||
);
|
||||
_scrollableListState = scrollableListState;
|
||||
}
|
||||
|
||||
@@ -273,11 +292,12 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
|
||||
bool _isTransitioning = false;
|
||||
|
||||
AnimationController? _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final ItemPosition? initialPosition =
|
||||
PageStorage.of(context).readState(context);
|
||||
final initialPosition = PageStorage.of(context).readState(context);
|
||||
primary
|
||||
..target = initialPosition?.index ?? widget.initialScrollIndex
|
||||
..alignment = initialPosition?.itemLeadingEdge ?? widget.initialAlignment;
|
||||
@@ -301,6 +321,7 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
.removeListener(_updatePositions);
|
||||
secondary.itemPositionsNotifier.itemPositions
|
||||
.removeListener(_updatePositions);
|
||||
_animationController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -329,84 +350,90 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cacheExtent = _cacheExtent(constraints);
|
||||
return GestureDetector(
|
||||
onPanDown: (_) => _stopScroll(canceled: true),
|
||||
excludeFromSemantics: true,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cacheExtent = _cacheExtent(constraints);
|
||||
return GestureDetector(
|
||||
onPanDown: (_) => _stopScroll(canceled: true),
|
||||
excludeFromSemantics: true,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
PostMountCallback(
|
||||
key: primary.key,
|
||||
callback: startAnimationCallback,
|
||||
child: FadeTransition(
|
||||
opacity: ReverseAnimation(opacity),
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (_) => _isTransitioning,
|
||||
child: PositionedList(
|
||||
itemBuilder: widget.itemBuilder,
|
||||
separatorBuilder: widget.separatorBuilder,
|
||||
itemCount: widget.itemCount,
|
||||
positionedIndex: primary.target,
|
||||
controller: primary.scrollController,
|
||||
itemPositionsNotifier: primary.itemPositionsNotifier,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
reverse: widget.reverse,
|
||||
cacheExtent: cacheExtent,
|
||||
alignment: primary.alignment,
|
||||
physics: widget.physics,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
addSemanticIndexes: widget.addSemanticIndexes,
|
||||
semanticChildCount: widget.semanticChildCount,
|
||||
padding: widget.padding,
|
||||
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
|
||||
addRepaintBoundaries: widget.addRepaintBoundaries,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isTransitioning)
|
||||
PostMountCallback(
|
||||
key: primary.key,
|
||||
key: secondary.key,
|
||||
callback: startAnimationCallback,
|
||||
child: FadeTransition(
|
||||
opacity: ReverseAnimation(opacity),
|
||||
opacity: opacity,
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (_) => _isTransitioning,
|
||||
onNotification: (_) => false,
|
||||
child: PositionedList(
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
separatorBuilder: widget.separatorBuilder,
|
||||
itemCount: widget.itemCount,
|
||||
positionedIndex: primary.target,
|
||||
controller: primary.scrollController,
|
||||
itemPositionsNotifier: primary.itemPositionsNotifier,
|
||||
itemPositionsNotifier: secondary.itemPositionsNotifier,
|
||||
positionedIndex: secondary.target,
|
||||
controller: secondary.scrollController,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
reverse: widget.reverse,
|
||||
cacheExtent: cacheExtent,
|
||||
alignment: primary.alignment,
|
||||
alignment: secondary.alignment,
|
||||
physics: widget.physics,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
addSemanticIndexes: widget.addSemanticIndexes,
|
||||
semanticChildCount: widget.semanticChildCount,
|
||||
padding: widget.padding,
|
||||
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
|
||||
addRepaintBoundaries: widget.addRepaintBoundaries,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isTransitioning)
|
||||
PostMountCallback(
|
||||
key: secondary.key,
|
||||
callback: startAnimationCallback,
|
||||
child: FadeTransition(
|
||||
opacity: opacity,
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (_) => false,
|
||||
child: PositionedList(
|
||||
keyboardDismissBehavior:
|
||||
widget.keyboardDismissBehavior,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
separatorBuilder: widget.separatorBuilder,
|
||||
itemCount: widget.itemCount,
|
||||
itemPositionsNotifier:
|
||||
secondary.itemPositionsNotifier,
|
||||
positionedIndex: secondary.target,
|
||||
controller: secondary.scrollController,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
reverse: widget.reverse,
|
||||
cacheExtent: cacheExtent,
|
||||
alignment: secondary.alignment,
|
||||
physics: widget.physics,
|
||||
addSemanticIndexes: widget.addSemanticIndexes,
|
||||
semanticChildCount: widget.semanticChildCount,
|
||||
padding: widget.padding,
|
||||
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
|
||||
addRepaintBoundaries: widget.addRepaintBoundaries,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
double _cacheExtent(BoxConstraints constraints) => max(
|
||||
constraints.maxHeight * _screenScrollCount,
|
||||
(widget.scrollDirection == Axis.vertical
|
||||
? constraints.maxHeight
|
||||
: constraints.maxWidth) *
|
||||
_screenScrollCount,
|
||||
widget.minCacheExtent ?? 0,
|
||||
);
|
||||
|
||||
@@ -434,16 +461,19 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
index = widget.itemCount - 1;
|
||||
}
|
||||
if (_isTransitioning) {
|
||||
final scrollCompleter = Completer<void>();
|
||||
_stopScroll(canceled: true);
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_startScroll(
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
await _startScroll(
|
||||
index: index,
|
||||
alignment: alignment,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
opacityAnimationWeights: opacityAnimationWeights,
|
||||
);
|
||||
scrollCompleter.complete();
|
||||
});
|
||||
await scrollCompleter.future;
|
||||
} else {
|
||||
await _startScroll(
|
||||
index: index,
|
||||
@@ -486,10 +516,11 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
startAnimationCallback = () {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
startAnimationCallback = () {};
|
||||
|
||||
opacity.parent = _opacityAnimation(opacityAnimationWeights).animate(
|
||||
AnimationController(vsync: this, duration: duration)..forward(),
|
||||
);
|
||||
_animationController?.dispose();
|
||||
_animationController =
|
||||
AnimationController(vsync: this, duration: duration)..forward();
|
||||
opacity.parent = _opacityAnimation(opacityAnimationWeights)
|
||||
.animate(_animationController!);
|
||||
secondary.scrollController.jumpTo(-direction *
|
||||
(_screenScrollCount *
|
||||
primary.scrollController.position.viewportDimension -
|
||||
@@ -532,17 +563,19 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (opacity.value >= 0.5) {
|
||||
// Secondary [ListView] is more visible than the primary; make it the
|
||||
// new primary.
|
||||
final temp = primary;
|
||||
primary = secondary;
|
||||
secondary = temp;
|
||||
}
|
||||
_isTransitioning = false;
|
||||
opacity.parent = const AlwaysStoppedAnimation<double>(0);
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (opacity.value >= 0.5) {
|
||||
// Secondary [ListView] is more visible than the primary; make it the
|
||||
// new primary.
|
||||
final temp = primary;
|
||||
primary = secondary;
|
||||
secondary = temp;
|
||||
}
|
||||
_isTransitioning = false;
|
||||
opacity.parent = const AlwaysStoppedAnimation<double>(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Animatable<double> _opacityAnimation(List<double> opacityAnimationWeights) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
@@ -15,7 +13,7 @@ import 'package:flutter/widgets.dart';
|
||||
/// Version of [Viewport] with some modifications to how extents are
|
||||
/// computed to allow scroll extents outside 0 to 1. See [Viewport]
|
||||
/// for more information.
|
||||
/// description
|
||||
/// {@endtemplate}
|
||||
class UnboundedViewport extends Viewport {
|
||||
/// {@macro unbounded_viewport}
|
||||
UnboundedViewport({
|
||||
@@ -37,15 +35,16 @@ class UnboundedViewport extends Viewport {
|
||||
double get anchor => _anchor;
|
||||
|
||||
@override
|
||||
RenderViewport createRenderObject(BuildContext context) =>
|
||||
UnboundedRenderViewport(
|
||||
axisDirection: axisDirection,
|
||||
crossAxisDirection: crossAxisDirection ??
|
||||
Viewport.getDefaultCrossAxisDirection(context, axisDirection),
|
||||
anchor: anchor,
|
||||
offset: offset,
|
||||
cacheExtent: cacheExtent,
|
||||
);
|
||||
RenderViewport createRenderObject(BuildContext context) {
|
||||
return UnboundedRenderViewport(
|
||||
axisDirection: axisDirection,
|
||||
crossAxisDirection: crossAxisDirection ??
|
||||
Viewport.getDefaultCrossAxisDirection(context, axisDirection),
|
||||
anchor: anchor,
|
||||
offset: offset,
|
||||
cacheExtent: cacheExtent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A render object that is bigger on the inside.
|
||||
@@ -137,14 +136,20 @@ class UnboundedRenderViewport extends RenderViewport {
|
||||
@override
|
||||
void performLayout() {
|
||||
if (center == null) {
|
||||
assert(firstChild == null, 'firstChild cannot be null');
|
||||
assert(
|
||||
firstChild == null,
|
||||
'A RenderViewport with no center render object must have no children.',
|
||||
);
|
||||
_minScrollExtent = 0.0;
|
||||
_maxScrollExtent = 0.0;
|
||||
_hasVisualOverflow = false;
|
||||
offset.applyContentDimensions(0, 0);
|
||||
return;
|
||||
}
|
||||
assert(center!.parent == this, 'center.parent cannot be equal to this');
|
||||
assert(
|
||||
center!.parent == this,
|
||||
'''The "center" property of a RenderViewport must be a child of the viewport.''',
|
||||
);
|
||||
|
||||
late double mainAxisExtent;
|
||||
late double crossAxisExtent;
|
||||
@@ -186,7 +191,7 @@ class UnboundedRenderViewport extends RenderViewport {
|
||||
} while (count < _maxLayoutCycles);
|
||||
assert(() {
|
||||
if (count >= _maxLayoutCycles) {
|
||||
assert(count != 1, 'count not equal to 1');
|
||||
assert(count != 1);
|
||||
throw FlutterError(
|
||||
'A RenderViewport exceeded its maximum number of layout cycles.\n'
|
||||
'RenderViewport render objects, during layout, can retry if either their '
|
||||
@@ -207,7 +212,7 @@ class UnboundedRenderViewport extends RenderViewport {
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}(), 'count needs to be bigger than _maxLayoutCycles');
|
||||
}());
|
||||
}
|
||||
|
||||
double _attemptLayout(
|
||||
@@ -215,11 +220,11 @@ class UnboundedRenderViewport extends RenderViewport {
|
||||
double crossAxisExtent,
|
||||
double correctedOffset,
|
||||
) {
|
||||
assert(!mainAxisExtent.isNaN, 'assert mainAxisExtent.isNaN');
|
||||
assert(mainAxisExtent >= 0.0, 'assert mainAxisExtent >= 0.0');
|
||||
assert(crossAxisExtent.isFinite, 'assert crossAxisExtent.isFinite');
|
||||
assert(crossAxisExtent >= 0.0, 'assert crossAxisExtent >= 0.0');
|
||||
assert(correctedOffset.isFinite, 'assert correctedOffset.isFinite');
|
||||
assert(!mainAxisExtent.isNaN, 'The main axis extent cannot be NaN.');
|
||||
assert(mainAxisExtent >= 0.0, 'The main axis extent cannot be negative.');
|
||||
assert(crossAxisExtent.isFinite, 'The cross axis extent must be finite.');
|
||||
assert(crossAxisExtent >= 0.0, 'The cross axis extent cannot be negative.');
|
||||
assert(correctedOffset.isFinite, 'The corrected offset must be finite.');
|
||||
_minScrollExtent = 0.0;
|
||||
_maxScrollExtent = 0.0;
|
||||
_hasVisualOverflow = false;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,7 @@ enum SpacingType {
|
||||
/// A [StreamChannel] ancestor widget is required in order to provide the
|
||||
/// information about the channels.
|
||||
///
|
||||
/// Uses a [ListView.custom] to render the list of channels.
|
||||
/// Uses a [ScrollablePositionedList] to render the list of channels.
|
||||
///
|
||||
/// The UI is rendered based on the first ancestor of type [StreamChatTheme].
|
||||
/// Modify it to change the widget's appearance.
|
||||
@@ -88,8 +88,10 @@ class StreamMessageListView extends StatefulWidget {
|
||||
this.threadBuilder,
|
||||
this.onThreadTap,
|
||||
this.dateDividerBuilder,
|
||||
this.scrollPhysics =
|
||||
const ClampingScrollPhysics(), // we need to use ClampingScrollPhysics to avoid the list view to animate and break while loading
|
||||
// we need to use ClampingScrollPhysics to avoid the list view to bounce
|
||||
// when we are at the either end of the list view and try to use 'animateTo'
|
||||
// to animate in the same direction.
|
||||
this.scrollPhysics = const ClampingScrollPhysics(),
|
||||
this.initialScrollIndex,
|
||||
this.initialAlignment,
|
||||
this.scrollController,
|
||||
@@ -555,6 +557,10 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
|
||||
if (valueKey != null) {
|
||||
final index = messagesIndex[valueKey.value];
|
||||
if (index != null) {
|
||||
// The calculation is as follows:
|
||||
// * Add 2 to the index retrieved to account for the footer and the bottom loader.
|
||||
// * Multiply the result by 2 to account for the separators between each pair of items.
|
||||
// * Subtract 1 to adjust for the 0-based indexing of the list view.
|
||||
return ((index + 2) * 2) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user