// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // ignore_for_file: uri_does_not_exist_in_doc_import /// @docImport 'package:flutter/material.dart'; /// /// @docImport 'single_child_scroll_view.dart'; /// @docImport 'text.dart'; library; import 'package:PiliPlus/common/widgets/page/scrollable.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart' hide Scrollable, ScrollableState; import 'package:flutter/rendering.dart'; class _ForceImplicitScrollPhysics extends ScrollPhysics { const _ForceImplicitScrollPhysics( {required this.allowImplicitScrolling, super.parent}); @override _ForceImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) { return _ForceImplicitScrollPhysics( allowImplicitScrolling: allowImplicitScrolling, parent: buildParent(ancestor), ); } @override final bool allowImplicitScrolling; } const PageScrollPhysics _kPagePhysics = PageScrollPhysics(); /// A scrollable list that works page by page. /// /// Each child of a page view is forced to be the same size as the viewport. /// /// You can use a [PageController] to control which page is visible in the view. /// In addition to being able to control the pixel offset of the content inside /// the [CustomPageView], a [PageController] also lets you control the offset in terms /// of pages, which are increments of the viewport size. /// /// The [PageController] can also be used to control the /// [PageController.initialPage], which determines which page is shown when the /// [CustomPageView] is first constructed, and the [PageController.viewportFraction], /// which determines the size of the pages as a fraction of the viewport size. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A} /// /// {@tool dartpad} /// Here is an example of [CustomPageView]. It creates a centered [Text] in each of the three pages /// which scroll horizontally. /// /// ** See code in examples/api/lib/widgets/page_view/page_view.0.dart ** /// {@end-tool} /// /// ## Persisting the scroll position during a session /// /// Scroll views attempt to persist their scroll position using [PageStorage]. /// For a [CustomPageView], this can be disabled by setting [PageController.keepPage] /// to false on the [controller]. If it is enabled, using a [PageStorageKey] for /// the [key] of this widget is recommended to help disambiguate different /// scroll views from each other. /// /// See also: /// /// * [PageController], which controls which page is visible in the view. /// * [SingleChildScrollView], when you need to make a single child scrollable. /// * [ListView], for a scrollable list of boxes. /// * [GridView], for a scrollable grid of boxes. /// * [ScrollNotification] and [NotificationListener], which can be used to watch /// the scroll position without using a [ScrollController]. class CustomPageView extends StatefulWidget { /// Creates a scrollable list that works page by page from an explicit [List] /// of widgets. /// /// This constructor is appropriate for page views with a small number of /// children because constructing the [List] requires doing work for every /// child that could possibly be displayed in the page view, instead of just /// those children that are actually visible. /// /// Like other widgets in the framework, this widget expects that /// the [children] list will not be mutated after it has been passed in here. /// See the documentation at [SliverChildListDelegate.children] for more details. /// /// {@template flutter.widgets.PageView.allowImplicitScrolling} /// If [allowImplicitScrolling] is true, the [CustomPageView] will participate in /// accessibility scrolling more like a [ListView], where implicit scroll /// actions will move to the next page rather than into the contents of the /// [CustomPageView]. /// {@endtemplate} CustomPageView({ super.key, this.scrollDirection = Axis.horizontal, this.reverse = false, this.controller, this.physics, this.pageSnapping = true, this.onPageChanged, List children = const [], this.dragStartBehavior = DragStartBehavior.start, this.allowImplicitScrolling = false, this.restorationId, this.clipBehavior = Clip.hardEdge, this.hitTestBehavior = HitTestBehavior.opaque, this.scrollBehavior, this.padEnds = true, this.header, this.bgColor = Colors.transparent, }) : childrenDelegate = SliverChildListDelegate(children); final Widget? header; final Color bgColor; /// Creates a scrollable list that works page by page using widgets that are /// created on demand. /// /// This constructor is appropriate for page views with a large (or infinite) /// number of children because the builder is called only for those children /// that are actually visible. /// /// Providing a non-null [itemCount] lets the [CustomPageView] compute the maximum /// scroll extent. /// /// [itemBuilder] will be called only with indices greater than or equal to /// zero and less than [itemCount]. /// /// {@macro flutter.widgets.ListView.builder.itemBuilder} /// /// {@template flutter.widgets.PageView.findChildIndexCallback} /// The [findChildIndexCallback] corresponds to the /// [SliverChildBuilderDelegate.findChildIndexCallback] property. If null, /// 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 needs to be implemented if /// the order of the children may change at a later time. /// {@endtemplate} /// /// {@macro flutter.widgets.PageView.allowImplicitScrolling} CustomPageView.builder({ super.key, this.scrollDirection = Axis.horizontal, this.reverse = false, this.controller, this.physics, this.pageSnapping = true, this.onPageChanged, required NullableIndexedWidgetBuilder itemBuilder, ChildIndexGetter? findChildIndexCallback, int? itemCount, this.dragStartBehavior = DragStartBehavior.start, this.allowImplicitScrolling = false, this.restorationId, this.clipBehavior = Clip.hardEdge, this.hitTestBehavior = HitTestBehavior.opaque, this.scrollBehavior, this.padEnds = true, this.header, this.bgColor = Colors.transparent, }) : childrenDelegate = SliverChildBuilderDelegate( itemBuilder, findChildIndexCallback: findChildIndexCallback, childCount: itemCount, ); /// Creates a scrollable list that works page by page with a custom child /// model. /// /// {@tool dartpad} /// This example shows a [CustomPageView] that uses a custom [SliverChildBuilderDelegate] to support child /// reordering. /// /// ** See code in examples/api/lib/widgets/page_view/page_view.1.dart ** /// {@end-tool} /// /// {@macro flutter.widgets.PageView.allowImplicitScrolling} const CustomPageView.custom({ super.key, this.scrollDirection = Axis.horizontal, this.reverse = false, this.controller, this.physics, this.pageSnapping = true, this.onPageChanged, required this.childrenDelegate, this.dragStartBehavior = DragStartBehavior.start, this.allowImplicitScrolling = false, this.restorationId, this.clipBehavior = Clip.hardEdge, this.hitTestBehavior = HitTestBehavior.opaque, this.scrollBehavior, this.padEnds = true, this.header, this.bgColor = Colors.transparent, }); /// Controls whether the widget's pages will respond to /// [RenderObject.showOnScreen], which will allow for implicit accessibility /// scrolling. /// /// With this flag set to false, when accessibility focus reaches the end of /// the current page and the user attempts to move it to the next element, the /// focus will traverse to the next widget outside of the page view. /// /// With this flag set to true, when accessibility focus reaches the end of /// the current page and user attempts to move it to the next element, focus /// will traverse to the next page in the page view. final bool allowImplicitScrolling; /// {@macro flutter.widgets.scrollable.restorationId} final String? restorationId; /// The [Axis] along which the scroll view's offset increases with each page. /// /// For the direction in which active scrolling may be occurring, see /// [ScrollDirection]. /// /// Defaults to [Axis.horizontal]. final Axis scrollDirection; /// Whether the page view scrolls in the reading direction. /// /// For example, if the reading direction is left-to-right and /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from /// left to right when [reverse] is false and from right to left when /// [reverse] is true. /// /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view /// scrolls from top to bottom when [reverse] is false and from bottom to top /// when [reverse] is true. /// /// Defaults to false. final bool reverse; /// An object that can be used to control the position to which this page /// view is scrolled. final PageController? controller; /// How the page view should respond to user input. /// /// For example, determines how the page view continues to animate after the /// user stops dragging the page view. /// /// The physics are modified to snap to page boundaries using /// [PageScrollPhysics] prior to being used. /// /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the /// [ScrollPhysics] provided by that behavior will take precedence after /// [physics]. /// /// Defaults to matching platform conventions. final ScrollPhysics? physics; /// Set to false to disable page snapping, useful for custom scroll behavior. /// /// If the [padEnds] is false and [PageController.viewportFraction] < 1.0, /// the page will snap to the beginning of the viewport; otherwise, the page /// will snap to the center of the viewport. final bool pageSnapping; /// Called whenever the page in the center of the viewport changes. final ValueChanged? onPageChanged; /// A delegate that provides the children for the [CustomPageView]. /// /// The [PageView.custom] constructor lets you specify this delegate /// explicitly. The [CustomPageView] and [PageView.builder] constructors create a /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder], /// respectively. final SliverChildDelegate childrenDelegate; /// {@macro flutter.widgets.scrollable.dragStartBehavior} final DragStartBehavior dragStartBehavior; /// {@macro flutter.material.Material.clipBehavior} /// /// Defaults to [Clip.hardEdge]. final Clip clipBehavior; /// {@macro flutter.widgets.scrollable.hitTestBehavior} /// /// Defaults to [HitTestBehavior.opaque]. final HitTestBehavior hitTestBehavior; /// {@macro flutter.widgets.scrollable.scrollBehavior} /// /// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be /// modified by default to not apply a [Scrollbar]. final ScrollBehavior? scrollBehavior; /// Whether to add padding to both ends of the list. /// /// If this is set to true and [PageController.viewportFraction] < 1.0, padding will be added /// such that the first and last child slivers will be in the center of /// the viewport when scrolled all the way to the start or end, respectively. /// /// If [PageController.viewportFraction] >= 1.0, this property has no effect. /// /// This property defaults to true. final bool padEnds; @override State createState() => _CustomPageViewState(); } class _CustomPageViewState extends State { int _lastReportedPage = 0; late PageController _controller; @override void initState() { super.initState(); _initController(); _lastReportedPage = _controller.initialPage; } @override void dispose() { if (widget.controller == null) { _controller.dispose(); } super.dispose(); } void _initController() { _controller = widget.controller ?? PageController(); } @override void didUpdateWidget(CustomPageView oldWidget) { if (oldWidget.controller != widget.controller) { if (oldWidget.controller == null) { _controller.dispose(); } _initController(); } super.didUpdateWidget(oldWidget); } AxisDirection _getDirection(BuildContext context) { switch (widget.scrollDirection) { case Axis.horizontal: assert(debugCheckHasDirectionality(context)); final TextDirection textDirection = Directionality.of(context); final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection); return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection; case Axis.vertical: return widget.reverse ? AxisDirection.up : AxisDirection.down; } } @override Widget build(BuildContext context) { final AxisDirection axisDirection = _getDirection(context); final ScrollPhysics physics = _ForceImplicitScrollPhysics( allowImplicitScrolling: widget.allowImplicitScrolling, ).applyTo( widget.pageSnapping ? _kPagePhysics.applyTo( widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context), ) : widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context), ); return NotificationListener( onNotification: (ScrollNotification notification) { if (notification.depth == 0 && widget.onPageChanged != null && notification is ScrollUpdateNotification) { final PageMetrics metrics = notification.metrics as PageMetrics; final int currentPage = metrics.page!.round(); if (currentPage != _lastReportedPage) { _lastReportedPage = currentPage; widget.onPageChanged!(currentPage); } } return false; }, child: CustomScrollable( header: widget.header, bgColor: widget.bgColor, dragStartBehavior: widget.dragStartBehavior, axisDirection: axisDirection, controller: _controller, physics: physics, restorationId: widget.restorationId, hitTestBehavior: widget.hitTestBehavior, scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(scrollbars: false), viewportBuilder: (BuildContext context, ViewportOffset position) { return Viewport( // TODO(dnfield): we should provide a way to set cacheExtent // independent of implicit scrolling: // https://github.com/flutter/flutter/issues/45632 cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0, cacheExtentStyle: CacheExtentStyle.viewport, axisDirection: axisDirection, offset: position, clipBehavior: widget.clipBehavior, slivers: [ SliverFillViewport( viewportFraction: _controller.viewportFraction, delegate: widget.childrenDelegate, padEnds: widget.padEnds, ), ], ); }, ), ); } @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); description ..add(EnumProperty('scrollDirection', widget.scrollDirection)) ..add(FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed')) ..add( DiagnosticsProperty('controller', _controller, showName: false), ) ..add(DiagnosticsProperty('physics', widget.physics, showName: false)) ..add( FlagProperty('pageSnapping', value: widget.pageSnapping, ifFalse: 'snapping disabled'), ) ..add( FlagProperty( 'allowImplicitScrolling', value: widget.allowImplicitScrolling, ifTrue: 'allow implicit scrolling', ), ); } }