mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-05 18:20:10 +08:00
@@ -11,7 +11,8 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart'
|
||||||
|
show DragStartBehavior, HorizontalDragGestureRecognizer;
|
||||||
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
|
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
@@ -41,18 +42,18 @@ const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
|
|||||||
///
|
///
|
||||||
/// You can use a [PageController] to control which page is visible in the view.
|
/// 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
|
/// 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
|
/// the [PageView], a [PageController] also lets you control the offset in terms
|
||||||
/// of pages, which are increments of the viewport size.
|
/// of pages, which are increments of the viewport size.
|
||||||
///
|
///
|
||||||
/// The [PageController] can also be used to control the
|
/// The [PageController] can also be used to control the
|
||||||
/// [PageController.initialPage], which determines which page is shown when the
|
/// [PageController.initialPage], which determines which page is shown when the
|
||||||
/// [CustomPageView] is first constructed, and the [PageController.viewportFraction],
|
/// [PageView] is first constructed, and the [PageController.viewportFraction],
|
||||||
/// which determines the size of the pages as a fraction of the viewport size.
|
/// 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}
|
/// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A}
|
||||||
///
|
///
|
||||||
/// {@tool dartpad}
|
/// {@tool dartpad}
|
||||||
/// Here is an example of [CustomPageView]. It creates a centered [Text] in each of the three pages
|
/// Here is an example of [PageView]. It creates a centered [Text] in each of the three pages
|
||||||
/// which scroll horizontally.
|
/// which scroll horizontally.
|
||||||
///
|
///
|
||||||
/// ** See code in examples/api/lib/widgets/page_view/page_view.0.dart **
|
/// ** See code in examples/api/lib/widgets/page_view/page_view.0.dart **
|
||||||
@@ -61,7 +62,7 @@ const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
|
|||||||
/// ## Persisting the scroll position during a session
|
/// ## Persisting the scroll position during a session
|
||||||
///
|
///
|
||||||
/// Scroll views attempt to persist their scroll position using [PageStorage].
|
/// Scroll views attempt to persist their scroll position using [PageStorage].
|
||||||
/// For a [CustomPageView], this can be disabled by setting [PageController.keepPage]
|
/// For a [PageView], this can be disabled by setting [PageController.keepPage]
|
||||||
/// to false on the [controller]. If it is enabled, using a [PageStorageKey] for
|
/// to false on the [controller]. If it is enabled, using a [PageStorageKey] for
|
||||||
/// the [key] of this widget is recommended to help disambiguate different
|
/// the [key] of this widget is recommended to help disambiguate different
|
||||||
/// scroll views from each other.
|
/// scroll views from each other.
|
||||||
@@ -74,7 +75,8 @@ const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
|
|||||||
/// * [GridView], for a scrollable grid of boxes.
|
/// * [GridView], for a scrollable grid of boxes.
|
||||||
/// * [ScrollNotification] and [NotificationListener], which can be used to watch
|
/// * [ScrollNotification] and [NotificationListener], which can be used to watch
|
||||||
/// the scroll position without using a [ScrollController].
|
/// the scroll position without using a [ScrollController].
|
||||||
class CustomPageView extends StatefulWidget {
|
class PageView<T extends HorizontalDragGestureRecognizer>
|
||||||
|
extends StatefulWidget {
|
||||||
/// Creates a scrollable list that works page by page from an explicit [List]
|
/// Creates a scrollable list that works page by page from an explicit [List]
|
||||||
/// of widgets.
|
/// of widgets.
|
||||||
///
|
///
|
||||||
@@ -88,12 +90,12 @@ class CustomPageView extends StatefulWidget {
|
|||||||
/// See the documentation at [SliverChildListDelegate.children] for more details.
|
/// See the documentation at [SliverChildListDelegate.children] for more details.
|
||||||
///
|
///
|
||||||
/// {@template flutter.widgets.PageView.allowImplicitScrolling}
|
/// {@template flutter.widgets.PageView.allowImplicitScrolling}
|
||||||
/// If [allowImplicitScrolling] is true, the [CustomPageView] will participate in
|
/// If [allowImplicitScrolling] is true, the [PageView] will participate in
|
||||||
/// accessibility scrolling more like a [ListView], where implicit scroll
|
/// accessibility scrolling more like a [ListView], where implicit scroll
|
||||||
/// actions will move to the next page rather than into the contents of the
|
/// actions will move to the next page rather than into the contents of the
|
||||||
/// [CustomPageView].
|
/// [PageView].
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
CustomPageView({
|
PageView({
|
||||||
super.key,
|
super.key,
|
||||||
this.scrollDirection = Axis.horizontal,
|
this.scrollDirection = Axis.horizontal,
|
||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
@@ -109,12 +111,10 @@ class CustomPageView extends StatefulWidget {
|
|||||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||||
this.scrollBehavior,
|
this.scrollBehavior,
|
||||||
this.padEnds = true,
|
this.padEnds = true,
|
||||||
this.header,
|
required this.horizontalDragGestureRecognizer,
|
||||||
this.bgColor = Colors.transparent,
|
|
||||||
}) : childrenDelegate = SliverChildListDelegate(children);
|
}) : childrenDelegate = SliverChildListDelegate(children);
|
||||||
|
|
||||||
final Widget? header;
|
final T horizontalDragGestureRecognizer;
|
||||||
final Color bgColor;
|
|
||||||
|
|
||||||
/// Creates a scrollable list that works page by page using widgets that are
|
/// Creates a scrollable list that works page by page using widgets that are
|
||||||
/// created on demand.
|
/// created on demand.
|
||||||
@@ -123,7 +123,7 @@ class CustomPageView extends StatefulWidget {
|
|||||||
/// number of children because the builder is called only for those children
|
/// number of children because the builder is called only for those children
|
||||||
/// that are actually visible.
|
/// that are actually visible.
|
||||||
///
|
///
|
||||||
/// Providing a non-null [itemCount] lets the [CustomPageView] compute the maximum
|
/// Providing a non-null [itemCount] lets the [PageView] compute the maximum
|
||||||
/// scroll extent.
|
/// scroll extent.
|
||||||
///
|
///
|
||||||
/// [itemBuilder] will be called only with indices greater than or equal to
|
/// [itemBuilder] will be called only with indices greater than or equal to
|
||||||
@@ -141,7 +141,7 @@ class CustomPageView extends StatefulWidget {
|
|||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
|
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
|
||||||
CustomPageView.builder({
|
PageView.builder({
|
||||||
super.key,
|
super.key,
|
||||||
this.scrollDirection = Axis.horizontal,
|
this.scrollDirection = Axis.horizontal,
|
||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
@@ -159,8 +159,7 @@ class CustomPageView extends StatefulWidget {
|
|||||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||||
this.scrollBehavior,
|
this.scrollBehavior,
|
||||||
this.padEnds = true,
|
this.padEnds = true,
|
||||||
this.header,
|
required this.horizontalDragGestureRecognizer,
|
||||||
this.bgColor = Colors.transparent,
|
|
||||||
}) : childrenDelegate = SliverChildBuilderDelegate(
|
}) : childrenDelegate = SliverChildBuilderDelegate(
|
||||||
itemBuilder,
|
itemBuilder,
|
||||||
findChildIndexCallback: findChildIndexCallback,
|
findChildIndexCallback: findChildIndexCallback,
|
||||||
@@ -171,14 +170,14 @@ class CustomPageView extends StatefulWidget {
|
|||||||
/// model.
|
/// model.
|
||||||
///
|
///
|
||||||
/// {@tool dartpad}
|
/// {@tool dartpad}
|
||||||
/// This example shows a [CustomPageView] that uses a custom [SliverChildBuilderDelegate] to support child
|
/// This example shows a [PageView] that uses a custom [SliverChildBuilderDelegate] to support child
|
||||||
/// reordering.
|
/// reordering.
|
||||||
///
|
///
|
||||||
/// ** See code in examples/api/lib/widgets/page_view/page_view.1.dart **
|
/// ** See code in examples/api/lib/widgets/page_view/page_view.1.dart **
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
///
|
///
|
||||||
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
|
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
|
||||||
const CustomPageView.custom({
|
const PageView.custom({
|
||||||
super.key,
|
super.key,
|
||||||
this.scrollDirection = Axis.horizontal,
|
this.scrollDirection = Axis.horizontal,
|
||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
@@ -194,8 +193,7 @@ class CustomPageView extends StatefulWidget {
|
|||||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||||
this.scrollBehavior,
|
this.scrollBehavior,
|
||||||
this.padEnds = true,
|
this.padEnds = true,
|
||||||
this.header,
|
required this.horizontalDragGestureRecognizer,
|
||||||
this.bgColor = Colors.transparent,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Controls whether the widget's pages will respond to
|
/// Controls whether the widget's pages will respond to
|
||||||
@@ -265,10 +263,10 @@ class CustomPageView extends StatefulWidget {
|
|||||||
/// Called whenever the page in the center of the viewport changes.
|
/// Called whenever the page in the center of the viewport changes.
|
||||||
final ValueChanged<int>? onPageChanged;
|
final ValueChanged<int>? onPageChanged;
|
||||||
|
|
||||||
/// A delegate that provides the children for the [CustomPageView].
|
/// A delegate that provides the children for the [PageView].
|
||||||
///
|
///
|
||||||
/// The [PageView.custom] constructor lets you specify this delegate
|
/// The [PageView.custom] constructor lets you specify this delegate
|
||||||
/// explicitly. The [CustomPageView] and [PageView.builder] constructors create a
|
/// explicitly. The [PageView] and [PageView.builder] constructors create a
|
||||||
/// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
|
/// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
|
||||||
/// respectively.
|
/// respectively.
|
||||||
final SliverChildDelegate childrenDelegate;
|
final SliverChildDelegate childrenDelegate;
|
||||||
@@ -304,10 +302,11 @@ class CustomPageView extends StatefulWidget {
|
|||||||
final bool padEnds;
|
final bool padEnds;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CustomPageView> createState() => _CustomPageViewState();
|
State<PageView<T>> createState() => _PageViewState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CustomPageViewState extends State<CustomPageView> {
|
class _PageViewState<T extends HorizontalDragGestureRecognizer>
|
||||||
|
extends State<PageView<T>> {
|
||||||
int _lastReportedPage = 0;
|
int _lastReportedPage = 0;
|
||||||
|
|
||||||
late PageController _controller;
|
late PageController _controller;
|
||||||
@@ -332,7 +331,7 @@ class _CustomPageViewState extends State<CustomPageView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(CustomPageView oldWidget) {
|
void didUpdateWidget(PageView<T> oldWidget) {
|
||||||
if (oldWidget.controller != widget.controller) {
|
if (oldWidget.controller != widget.controller) {
|
||||||
if (oldWidget.controller == null) {
|
if (oldWidget.controller == null) {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
@@ -388,9 +387,7 @@ class _CustomPageViewState extends State<CustomPageView> {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: CustomScrollable(
|
child: Scrollable<T>(
|
||||||
header: widget.header,
|
|
||||||
bgColor: widget.bgColor,
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
axisDirection: axisDirection,
|
axisDirection: axisDirection,
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
@@ -419,6 +416,7 @@ class _CustomPageViewState extends State<CustomPageView> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,32 +28,30 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
export 'package:flutter/physics.dart' show Tolerance;
|
|
||||||
|
|
||||||
// The return type of _performEnsureVisible.
|
// The return type of _performEnsureVisible.
|
||||||
//
|
//
|
||||||
// The list of futures represents each pending ScrollPosition call to
|
// The list of futures represents each pending ScrollPosition call to
|
||||||
// ensureVisible. The returned ScrollableState's context is used to find the
|
// ensureVisible. The returned ScrollableState's context is used to find the
|
||||||
// next potential ancestor Scrollable.
|
// next potential ancestor Scrollable.
|
||||||
typedef _EnsureVisibleResults = (List<Future<void>>, CustomScrollableState);
|
typedef _EnsureVisibleResults = (List<Future<void>>, ScrollableState);
|
||||||
|
|
||||||
/// A widget that manages scrolling in one dimension and informs the [Viewport]
|
/// A widget that manages scrolling in one dimension and informs the [Viewport]
|
||||||
/// through which the content is viewed.
|
/// through which the content is viewed.
|
||||||
///
|
///
|
||||||
/// [CustomScrollable] implements the interaction model for a scrollable widget,
|
/// [Scrollable] implements the interaction model for a scrollable widget,
|
||||||
/// including gesture recognition, but does not have an opinion about how the
|
/// including gesture recognition, but does not have an opinion about how the
|
||||||
/// viewport, which actually displays the children, is constructed.
|
/// viewport, which actually displays the children, is constructed.
|
||||||
///
|
///
|
||||||
/// It's rare to construct a [CustomScrollable] directly. Instead, consider [ListView]
|
/// It's rare to construct a [Scrollable] directly. Instead, consider [ListView]
|
||||||
/// or [GridView], which combine scrolling, viewporting, and a layout model. To
|
/// or [GridView], which combine scrolling, viewporting, and a layout model. To
|
||||||
/// combine layout models (or to use a custom layout mode), consider using
|
/// combine layout models (or to use a custom layout mode), consider using
|
||||||
/// [CustomScrollView].
|
/// [CustomScrollView].
|
||||||
///
|
///
|
||||||
/// The static [CustomScrollable.of] and [CustomScrollable.ensureVisible] functions are
|
/// The static [Scrollable.of] and [Scrollable.ensureVisible] functions are
|
||||||
/// often used to interact with the [CustomScrollable] widget inside a [ListView] or
|
/// often used to interact with the [Scrollable] widget inside a [ListView] or
|
||||||
/// a [GridView].
|
/// a [GridView].
|
||||||
///
|
///
|
||||||
/// To further customize scrolling behavior with a [CustomScrollable]:
|
/// To further customize scrolling behavior with a [Scrollable]:
|
||||||
///
|
///
|
||||||
/// 1. You can provide a [viewportBuilder] to customize the child model. For
|
/// 1. You can provide a [viewportBuilder] to customize the child model. For
|
||||||
/// example, [SingleChildScrollView] uses a viewport that displays a single
|
/// example, [SingleChildScrollView] uses a viewport that displays a single
|
||||||
@@ -63,7 +61,7 @@ typedef _EnsureVisibleResults = (List<Future<void>>, CustomScrollableState);
|
|||||||
/// 2. You can provide a custom [ScrollController] that creates a custom
|
/// 2. You can provide a custom [ScrollController] that creates a custom
|
||||||
/// [ScrollPosition] subclass. For example, [PageView] uses a
|
/// [ScrollPosition] subclass. For example, [PageView] uses a
|
||||||
/// [PageController], which creates a page-oriented scroll position subclass
|
/// [PageController], which creates a page-oriented scroll position subclass
|
||||||
/// that keeps the same page visible when the [CustomScrollable] resizes.
|
/// that keeps the same page visible when the [Scrollable] resizes.
|
||||||
///
|
///
|
||||||
/// ## Persisting the scroll position during a session
|
/// ## Persisting the scroll position during a session
|
||||||
///
|
///
|
||||||
@@ -71,7 +69,7 @@ typedef _EnsureVisibleResults = (List<Future<void>>, CustomScrollableState);
|
|||||||
/// This can be disabled by setting [ScrollController.keepScrollOffset] to false
|
/// This can be disabled by setting [ScrollController.keepScrollOffset] to false
|
||||||
/// on the [controller]. If it is enabled, using a [PageStorageKey] for the
|
/// on the [controller]. If it is enabled, using a [PageStorageKey] for the
|
||||||
/// [key] of this widget (or one of its ancestors, e.g. a [ScrollView]) is
|
/// [key] of this widget (or one of its ancestors, e.g. a [ScrollView]) is
|
||||||
/// recommended to help disambiguate different [CustomScrollable]s from each other.
|
/// recommended to help disambiguate different [Scrollable]s from each other.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
@@ -87,9 +85,10 @@ typedef _EnsureVisibleResults = (List<Future<void>>, CustomScrollableState);
|
|||||||
/// child.
|
/// child.
|
||||||
/// * [ScrollNotification] and [NotificationListener], which can be used to watch
|
/// * [ScrollNotification] and [NotificationListener], which can be used to watch
|
||||||
/// the scroll position without using a [ScrollController].
|
/// the scroll position without using a [ScrollController].
|
||||||
class CustomScrollable extends StatefulWidget {
|
class Scrollable<T extends HorizontalDragGestureRecognizer>
|
||||||
|
extends StatefulWidget {
|
||||||
/// Creates a widget that scrolls.
|
/// Creates a widget that scrolls.
|
||||||
const CustomScrollable({
|
const Scrollable({
|
||||||
super.key,
|
super.key,
|
||||||
this.axisDirection = AxisDirection.down,
|
this.axisDirection = AxisDirection.down,
|
||||||
this.controller,
|
this.controller,
|
||||||
@@ -103,19 +102,15 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
this.scrollBehavior,
|
this.scrollBehavior,
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||||
this.enableSlide,
|
required this.horizontalDragGestureRecognizer,
|
||||||
this.header,
|
|
||||||
this.bgColor = Colors.transparent,
|
|
||||||
}) : assert(semanticChildCount == null || semanticChildCount >= 0);
|
}) : assert(semanticChildCount == null || semanticChildCount >= 0);
|
||||||
|
|
||||||
final Widget? header;
|
final T horizontalDragGestureRecognizer;
|
||||||
final bool? enableSlide;
|
|
||||||
final Color bgColor;
|
|
||||||
|
|
||||||
/// {@template flutter.widgets.Scrollable.axisDirection}
|
/// {@template flutter.widgets.Scrollable.axisDirection}
|
||||||
/// The direction in which this widget scrolls.
|
/// The direction in which this widget scrolls.
|
||||||
///
|
///
|
||||||
/// For example, if the [CustomScrollable.axisDirection] is [AxisDirection.down],
|
/// For example, if the [Scrollable.axisDirection] is [AxisDirection.down],
|
||||||
/// increasing the scroll position will cause content below the bottom of the
|
/// increasing the scroll position will cause content below the bottom of the
|
||||||
/// viewport to become visible through the viewport. Similarly, if the
|
/// viewport to become visible through the viewport. Similarly, if the
|
||||||
/// axisDirection is [AxisDirection.right], increasing the scroll position
|
/// axisDirection is [AxisDirection.right], increasing the scroll position
|
||||||
@@ -138,12 +133,12 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// scroll position (see [ScrollController.offset]), or change it (see
|
/// scroll position (see [ScrollController.offset]), or change it (see
|
||||||
/// [ScrollController.animateTo]).
|
/// [ScrollController.animateTo]).
|
||||||
///
|
///
|
||||||
/// If null, a [ScrollController] will be created internally by [CustomScrollable]
|
/// If null, a [ScrollController] will be created internally by [Scrollable]
|
||||||
/// in order to create and manage the [ScrollPosition].
|
/// in order to create and manage the [ScrollPosition].
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [CustomScrollable.ensureVisible], which animates the scroll position to
|
/// * [Scrollable.ensureVisible], which animates the scroll position to
|
||||||
/// reveal a given [BuildContext].
|
/// reveal a given [BuildContext].
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
final ScrollController? controller;
|
final ScrollController? controller;
|
||||||
@@ -158,8 +153,8 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// the ambient [ScrollConfiguration].
|
/// the ambient [ScrollConfiguration].
|
||||||
///
|
///
|
||||||
/// If an explicit [ScrollBehavior] is provided to
|
/// If an explicit [ScrollBehavior] is provided to
|
||||||
/// [CustomScrollable.scrollBehavior], the [ScrollPhysics] provided by that behavior
|
/// [Scrollable.scrollBehavior], the [ScrollPhysics] provided by that behavior
|
||||||
/// will take precedence after [CustomScrollable.physics].
|
/// will take precedence after [Scrollable.physics].
|
||||||
///
|
///
|
||||||
/// The physics can be changed dynamically, but new physics will only take
|
/// The physics can be changed dynamically, but new physics will only take
|
||||||
/// effect if the _class_ of the provided object changes. Merely constructing
|
/// effect if the _class_ of the provided object changes. Merely constructing
|
||||||
@@ -194,7 +189,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// scroll when the scrollable is asked to scroll via the keyboard using a
|
/// scroll when the scrollable is asked to scroll via the keyboard using a
|
||||||
/// [ScrollAction].
|
/// [ScrollAction].
|
||||||
///
|
///
|
||||||
/// If not supplied, the [CustomScrollable] will scroll a default amount when a
|
/// If not supplied, the [Scrollable] will scroll a default amount when a
|
||||||
/// keyboard navigation key is pressed (e.g. pageUp/pageDown, control-upArrow,
|
/// keyboard navigation key is pressed (e.g. pageUp/pageDown, control-upArrow,
|
||||||
/// etc.), or otherwise invoked by a [ScrollAction].
|
/// etc.), or otherwise invoked by a [ScrollAction].
|
||||||
///
|
///
|
||||||
@@ -205,7 +200,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
final ScrollIncrementCalculator? incrementCalculator;
|
final ScrollIncrementCalculator? incrementCalculator;
|
||||||
|
|
||||||
/// {@template flutter.widgets.scrollable.excludeFromSemantics}
|
/// {@template flutter.widgets.scrollable.excludeFromSemantics}
|
||||||
/// Whether the scroll actions introduced by this [CustomScrollable] are exposed
|
/// Whether the scroll actions introduced by this [Scrollable] are exposed
|
||||||
/// in the semantics tree.
|
/// in the semantics tree.
|
||||||
///
|
///
|
||||||
/// Text fields with an overflow are usually scrollable to make sure that the
|
/// Text fields with an overflow are usually scrollable to make sure that the
|
||||||
@@ -220,10 +215,10 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
final bool excludeFromSemantics;
|
final bool excludeFromSemantics;
|
||||||
|
|
||||||
/// {@template flutter.widgets.scrollable.hitTestBehavior}
|
/// {@template flutter.widgets.scrollable.hitTestBehavior}
|
||||||
/// Defines the behavior of gesture detector used in this [CustomScrollable].
|
/// Defines the behavior of gesture detector used in this [Scrollable].
|
||||||
///
|
///
|
||||||
/// This defaults to [HitTestBehavior.opaque] which means it prevents targets
|
/// This defaults to [HitTestBehavior.opaque] which means it prevents targets
|
||||||
/// behind this [CustomScrollable] from receiving events.
|
/// behind this [Scrollable] from receiving events.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
@@ -305,7 +300,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// Defaults to [Clip.hardEdge].
|
/// Defaults to [Clip.hardEdge].
|
||||||
///
|
///
|
||||||
/// This is passed to decorators in [ScrollableDetails], and does not directly affect
|
/// This is passed to decorators in [ScrollableDetails], and does not directly affect
|
||||||
/// clipping of the [CustomScrollable]. This reflects the same [Clip] that is provided
|
/// clipping of the [Scrollable]. This reflects the same [Clip] that is provided
|
||||||
/// to [ScrollView.clipBehavior] and is supplied to the [Viewport].
|
/// to [ScrollView.clipBehavior] and is supplied to the [Viewport].
|
||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
|
|
||||||
@@ -315,7 +310,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
Axis get axis => axisDirectionToAxis(axisDirection);
|
Axis get axis => axisDirectionToAxis(axisDirection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CustomScrollableState createState() => CustomScrollableState();
|
ScrollableState<T> createState() => ScrollableState<T>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
@@ -335,31 +330,31 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// ScrollableState? scrollable = Scrollable.maybeOf(context);
|
/// ScrollableState? scrollable = Scrollable.maybeOf(context);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Calling this method will create a dependency on the [CustomScrollableState]
|
/// Calling this method will create a dependency on the [ScrollableState]
|
||||||
/// that is returned, if there is one. This is typically the closest
|
/// that is returned, if there is one. This is typically the closest
|
||||||
/// [CustomScrollable], but may be a more distant ancestor if [axis] is used to
|
/// [Scrollable], but may be a more distant ancestor if [axis] is used to
|
||||||
/// target a specific [CustomScrollable].
|
/// target a specific [Scrollable].
|
||||||
///
|
///
|
||||||
/// Using the optional [Axis] is useful when Scrollables are nested and the
|
/// Using the optional [Axis] is useful when Scrollables are nested and the
|
||||||
/// target [CustomScrollable] is not the closest instance. When [axis] is provided,
|
/// target [Scrollable] is not the closest instance. When [axis] is provided,
|
||||||
/// the nearest enclosing [CustomScrollableState] in that [Axis] is returned, or
|
/// the nearest enclosing [ScrollableState] in that [Axis] is returned, or
|
||||||
/// null if there is none.
|
/// null if there is none.
|
||||||
///
|
///
|
||||||
/// This finds the nearest _ancestor_ [CustomScrollable] of the `context`. This
|
/// This finds the nearest _ancestor_ [Scrollable] of the `context`. This
|
||||||
/// means that if the `context` is that of a [CustomScrollable], it will _not_ find
|
/// means that if the `context` is that of a [Scrollable], it will _not_ find
|
||||||
/// _that_ [CustomScrollable].
|
/// _that_ [Scrollable].
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [CustomScrollable.of], which is similar to this method, but asserts
|
/// * [Scrollable.of], which is similar to this method, but asserts
|
||||||
/// if no [CustomScrollable] ancestor is found.
|
/// if no [Scrollable] ancestor is found.
|
||||||
static CustomScrollableState? maybeOf(BuildContext context, {Axis? axis}) {
|
static ScrollableState? maybeOf(BuildContext context, {Axis? axis}) {
|
||||||
// This is the context that will need to establish the dependency.
|
// This is the context that will need to establish the dependency.
|
||||||
final BuildContext originalContext = context;
|
final BuildContext originalContext = context;
|
||||||
InheritedElement? element = context
|
InheritedElement? element = context
|
||||||
.getElementForInheritedWidgetOfExactType<_ScrollableScope>();
|
.getElementForInheritedWidgetOfExactType<_ScrollableScope>();
|
||||||
while (element != null) {
|
while (element != null) {
|
||||||
final CustomScrollableState scrollable =
|
final ScrollableState scrollable =
|
||||||
(element.widget as _ScrollableScope).scrollable;
|
(element.widget as _ScrollableScope).scrollable;
|
||||||
if (axis == null ||
|
if (axis == null ||
|
||||||
axisDirectionToAxis(scrollable.axisDirection) == axis) {
|
axisDirectionToAxis(scrollable.axisDirection) == axis) {
|
||||||
@@ -383,28 +378,28 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// ScrollableState scrollable = Scrollable.of(context);
|
/// ScrollableState scrollable = Scrollable.of(context);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Calling this method will create a dependency on the [CustomScrollableState]
|
/// Calling this method will create a dependency on the [ScrollableState]
|
||||||
/// that is returned, if there is one. This is typically the closest
|
/// that is returned, if there is one. This is typically the closest
|
||||||
/// [CustomScrollable], but may be a more distant ancestor if [axis] is used to
|
/// [Scrollable], but may be a more distant ancestor if [axis] is used to
|
||||||
/// target a specific [CustomScrollable].
|
/// target a specific [Scrollable].
|
||||||
///
|
///
|
||||||
/// Using the optional [Axis] is useful when Scrollables are nested and the
|
/// Using the optional [Axis] is useful when Scrollables are nested and the
|
||||||
/// target [CustomScrollable] is not the closest instance. When [axis] is provided,
|
/// target [Scrollable] is not the closest instance. When [axis] is provided,
|
||||||
/// the nearest enclosing [CustomScrollableState] in that [Axis] is returned.
|
/// the nearest enclosing [ScrollableState] in that [Axis] is returned.
|
||||||
///
|
///
|
||||||
/// This finds the nearest _ancestor_ [CustomScrollable] of the `context`. This
|
/// This finds the nearest _ancestor_ [Scrollable] of the `context`. This
|
||||||
/// means that if the `context` is that of a [CustomScrollable], it will _not_ find
|
/// means that if the `context` is that of a [Scrollable], it will _not_ find
|
||||||
/// _that_ [CustomScrollable].
|
/// _that_ [Scrollable].
|
||||||
///
|
///
|
||||||
/// If no [CustomScrollable] ancestor is found, then this method will assert in
|
/// If no [Scrollable] ancestor is found, then this method will assert in
|
||||||
/// debug mode, and throw an exception in release mode.
|
/// debug mode, and throw an exception in release mode.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [CustomScrollable.maybeOf], which is similar to this method, but returns null
|
/// * [Scrollable.maybeOf], which is similar to this method, but returns null
|
||||||
/// if no [CustomScrollable] ancestor is found.
|
/// if no [Scrollable] ancestor is found.
|
||||||
static CustomScrollableState of(BuildContext context, {Axis? axis}) {
|
static ScrollableState of(BuildContext context, {Axis? axis}) {
|
||||||
final CustomScrollableState? scrollableState = maybeOf(context, axis: axis);
|
final ScrollableState? scrollableState = maybeOf(context, axis: axis);
|
||||||
assert(() {
|
assert(() {
|
||||||
if (scrollableState == null) {
|
if (scrollableState == null) {
|
||||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
@@ -440,16 +435,16 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// This also means that the value returned is only good for the point in time
|
/// This also means that the value returned is only good for the point in time
|
||||||
/// when it is called, and callers will not get updated if the value changes.
|
/// when it is called, and callers will not get updated if the value changes.
|
||||||
///
|
///
|
||||||
/// The heuristic used is determined by the [physics] of this [CustomScrollable]
|
/// The heuristic used is determined by the [physics] of this [Scrollable]
|
||||||
/// via [ScrollPhysics.recommendDeferredLoading]. That method is called with
|
/// via [ScrollPhysics.recommendDeferredLoading]. That method is called with
|
||||||
/// the current [ScrollPosition.activity]'s [ScrollActivity.velocity].
|
/// the current [ScrollPosition.activity]'s [ScrollActivity.velocity].
|
||||||
///
|
///
|
||||||
/// The optional [Axis] allows targeting of a specific [CustomScrollable] of that
|
/// The optional [Axis] allows targeting of a specific [Scrollable] of that
|
||||||
/// axis, useful when Scrollables are nested. When [axis] is provided,
|
/// axis, useful when Scrollables are nested. When [axis] is provided,
|
||||||
/// [ScrollPosition.recommendDeferredLoading] is called for the nearest
|
/// [ScrollPosition.recommendDeferredLoading] is called for the nearest
|
||||||
/// [CustomScrollable] in that [Axis].
|
/// [Scrollable] in that [Axis].
|
||||||
///
|
///
|
||||||
/// If there is no [CustomScrollable] in the widget tree above the [context], this
|
/// If there is no [Scrollable] in the widget tree above the [context], this
|
||||||
/// method returns false.
|
/// method returns false.
|
||||||
static bool recommendDeferredLoadingForContext(
|
static bool recommendDeferredLoadingForContext(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -471,7 +466,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
/// Scrolls all scrollables that enclose the given context so as to make the
|
/// Scrolls all scrollables that enclose the given context so as to make the
|
||||||
/// given context visible.
|
/// given context visible.
|
||||||
///
|
///
|
||||||
/// If a [CustomScrollable] enclosing the provided [BuildContext] is a
|
/// If a [Scrollable] enclosing the provided [BuildContext] is a
|
||||||
/// [TwoDimensionalScrollable], both vertical and horizontal axes will ensure
|
/// [TwoDimensionalScrollable], both vertical and horizontal axes will ensure
|
||||||
/// the target is made visible.
|
/// the target is made visible.
|
||||||
static Future<void> ensureVisible(
|
static Future<void> ensureVisible(
|
||||||
@@ -492,7 +487,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
//
|
//
|
||||||
// Also see https://github.com/flutter/flutter/issues/65100
|
// Also see https://github.com/flutter/flutter/issues/65100
|
||||||
RenderObject? targetRenderObject;
|
RenderObject? targetRenderObject;
|
||||||
CustomScrollableState? scrollable = CustomScrollable.maybeOf(context);
|
ScrollableState? scrollable = Scrollable.maybeOf(context);
|
||||||
while (scrollable != null) {
|
while (scrollable != null) {
|
||||||
final List<Future<void>> newFutures;
|
final List<Future<void>> newFutures;
|
||||||
(newFutures, scrollable) = scrollable._performEnsureVisible(
|
(newFutures, scrollable) = scrollable._performEnsureVisible(
|
||||||
@@ -507,7 +502,7 @@ class CustomScrollable extends StatefulWidget {
|
|||||||
|
|
||||||
targetRenderObject ??= context.findRenderObject();
|
targetRenderObject ??= context.findRenderObject();
|
||||||
context = scrollable.context;
|
context = scrollable.context;
|
||||||
scrollable = CustomScrollable.maybeOf(context);
|
scrollable = Scrollable.maybeOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (futures.isEmpty || duration == Duration.zero) {
|
if (futures.isEmpty || duration == Duration.zero) {
|
||||||
@@ -529,7 +524,7 @@ class _ScrollableScope extends InheritedWidget {
|
|||||||
required super.child,
|
required super.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
final CustomScrollableState scrollable;
|
final ScrollableState scrollable;
|
||||||
final ScrollPosition position;
|
final ScrollPosition position;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -538,30 +533,31 @@ class _ScrollableScope extends InheritedWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State object for a [CustomScrollable] widget.
|
/// State object for a [Scrollable] widget.
|
||||||
///
|
///
|
||||||
/// To manipulate a [CustomScrollable] widget's scroll position, use the object
|
/// To manipulate a [Scrollable] widget's scroll position, use the object
|
||||||
/// obtained from the [position] property.
|
/// obtained from the [position] property.
|
||||||
///
|
///
|
||||||
/// To be informed of when a [CustomScrollable] widget is scrolling, use a
|
/// To be informed of when a [Scrollable] widget is scrolling, use a
|
||||||
/// [NotificationListener] to listen for [ScrollNotification] notifications.
|
/// [NotificationListener] to listen for [ScrollNotification] notifications.
|
||||||
///
|
///
|
||||||
/// This class is not intended to be subclassed. To specialize the behavior of a
|
/// This class is not intended to be subclassed. To specialize the behavior of a
|
||||||
/// [CustomScrollable], provide it with a [ScrollPhysics].
|
/// [Scrollable], provide it with a [ScrollPhysics].
|
||||||
class CustomScrollableState extends State<CustomScrollable>
|
class ScrollableState<T extends HorizontalDragGestureRecognizer>
|
||||||
|
extends State<Scrollable<T>>
|
||||||
with TickerProviderStateMixin, RestorationMixin
|
with TickerProviderStateMixin, RestorationMixin
|
||||||
implements ScrollContext {
|
implements ScrollContext {
|
||||||
// GETTERS
|
// GETTERS
|
||||||
|
|
||||||
/// The manager for this [CustomScrollable] widget's viewport position.
|
/// The manager for this [Scrollable] widget's viewport position.
|
||||||
///
|
///
|
||||||
/// To control what kind of [ScrollPosition] is created for a [CustomScrollable],
|
/// To control what kind of [ScrollPosition] is created for a [Scrollable],
|
||||||
/// provide it with custom [ScrollController] that creates the appropriate
|
/// provide it with custom [ScrollController] that creates the appropriate
|
||||||
/// [ScrollPosition] in its [ScrollController.createScrollPosition] method.
|
/// [ScrollPosition] in its [ScrollController.createScrollPosition] method.
|
||||||
ScrollPosition get position => _position!;
|
ScrollPosition get position => _position!;
|
||||||
ScrollPosition? _position;
|
ScrollPosition? _position;
|
||||||
|
|
||||||
/// The resolved [ScrollPhysics] of the [CustomScrollableState].
|
/// The resolved [ScrollPhysics] of the [ScrollableState].
|
||||||
ScrollPhysics? get resolvedPhysics => _physics;
|
ScrollPhysics? get resolvedPhysics => _physics;
|
||||||
ScrollPhysics? _physics;
|
ScrollPhysics? _physics;
|
||||||
|
|
||||||
@@ -661,10 +657,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
_fallbackScrollController = ScrollController();
|
_fallbackScrollController = ScrollController();
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
_animController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
reverseDuration: const Duration(milliseconds: 500),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
@@ -678,7 +670,7 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldUpdatePosition(CustomScrollable oldWidget) {
|
bool _shouldUpdatePosition(Scrollable oldWidget) {
|
||||||
if ((widget.scrollBehavior == null) != (oldWidget.scrollBehavior == null)) {
|
if ((widget.scrollBehavior == null) != (oldWidget.scrollBehavior == null)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -705,7 +697,7 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
|
|
||||||
@protected
|
@protected
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(CustomScrollable oldWidget) {
|
void didUpdateWidget(Scrollable<T> oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
if (widget.controller != oldWidget.controller) {
|
if (widget.controller != oldWidget.controller) {
|
||||||
@@ -747,8 +739,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
|
|
||||||
position.dispose();
|
position.dispose();
|
||||||
_persistedScrollOffset.dispose();
|
_persistedScrollOffset.dispose();
|
||||||
|
|
||||||
_animController.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,12 +768,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
bool? _lastCanDrag;
|
bool? _lastCanDrag;
|
||||||
Axis? _lastAxisDirection;
|
Axis? _lastAxisDirection;
|
||||||
|
|
||||||
late bool _isRTL = false;
|
|
||||||
Offset? _downPos;
|
|
||||||
bool? _isSliding;
|
|
||||||
|
|
||||||
late AnimationController _animController;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@protected
|
@protected
|
||||||
void setCanDrag(bool value) {
|
void setCanDrag(bool value) {
|
||||||
@@ -830,14 +814,9 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
};
|
};
|
||||||
case Axis.horizontal:
|
case Axis.horizontal:
|
||||||
_gestureRecognizers = <Type, GestureRecognizerFactory>{
|
_gestureRecognizers = <Type, GestureRecognizerFactory>{
|
||||||
HorizontalDragGestureRecognizer:
|
T: GestureRecognizerFactoryWithHandlers<T>(
|
||||||
GestureRecognizerFactoryWithHandlers<
|
() => widget.horizontalDragGestureRecognizer,
|
||||||
HorizontalDragGestureRecognizer
|
(T instance) {
|
||||||
>(
|
|
||||||
() => HorizontalDragGestureRecognizer(
|
|
||||||
supportedDevices: _configuration.dragDevices,
|
|
||||||
),
|
|
||||||
(HorizontalDragGestureRecognizer instance) {
|
|
||||||
instance
|
instance
|
||||||
..onDown = _handleDragDown
|
..onDown = _handleDragDown
|
||||||
..onStart = _handleDragStart
|
..onStart = _handleDragStart
|
||||||
@@ -889,65 +868,12 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
ScrollHoldController? _hold;
|
ScrollHoldController? _hold;
|
||||||
|
|
||||||
void _handleDragDown(DragDownDetails details) {
|
void _handleDragDown(DragDownDetails details) {
|
||||||
final dx = details.localPosition.dx;
|
|
||||||
const offset = 30;
|
|
||||||
final isLTR = dx <= offset;
|
|
||||||
final isRTL = dx >= _maxWidth - offset;
|
|
||||||
if (isLTR || isRTL) {
|
|
||||||
_isRTL = isRTL;
|
|
||||||
_downPos = details.localPosition;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(_drag == null);
|
assert(_drag == null);
|
||||||
assert(_hold == null);
|
assert(_hold == null);
|
||||||
_hold = position.hold(_disposeHold);
|
_hold = position.hold(_disposeHold);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPan(Offset localPosition) {
|
|
||||||
if (_isSliding == false) {
|
|
||||||
return;
|
|
||||||
} else if (_isSliding == null) {
|
|
||||||
if (_downPos != null) {
|
|
||||||
Offset cumulativeDelta = localPosition - _downPos!;
|
|
||||||
if (cumulativeDelta.dx.abs() >= cumulativeDelta.dy.abs()) {
|
|
||||||
_downPos = localPosition;
|
|
||||||
_isSliding = true;
|
|
||||||
} else {
|
|
||||||
_downPos = null;
|
|
||||||
_isSliding = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_downPos = null;
|
|
||||||
_isSliding = false;
|
|
||||||
}
|
|
||||||
} else if (_isSliding == true) {
|
|
||||||
final from = _downPos!.dx;
|
|
||||||
final to = localPosition.dx;
|
|
||||||
_animController.value =
|
|
||||||
math.max(0, _isRTL ? from - to : to - from) / _maxWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDismiss() {
|
|
||||||
if (_isSliding == true) {
|
|
||||||
final dx = _downPos!.dx;
|
|
||||||
if (_animController.value * _maxWidth +
|
|
||||||
(_isRTL ? (_maxWidth - dx) : dx) >=
|
|
||||||
100) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
} else {
|
|
||||||
_animController.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_downPos = null;
|
|
||||||
_isSliding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragStart(DragStartDetails details) {
|
void _handleDragStart(DragStartDetails details) {
|
||||||
if (_downPos != null) {
|
|
||||||
_onPan(details.localPosition);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// It's possible for _hold to become null between _handleDragDown and
|
// It's possible for _hold to become null between _handleDragDown and
|
||||||
// _handleDragStart, for example if some user code calls jumpTo or otherwise
|
// _handleDragStart, for example if some user code calls jumpTo or otherwise
|
||||||
// triggers a new activity to begin.
|
// triggers a new activity to begin.
|
||||||
@@ -961,20 +887,12 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragUpdate(DragUpdateDetails details) {
|
void _handleDragUpdate(DragUpdateDetails details) {
|
||||||
if (_downPos != null) {
|
|
||||||
_onPan(details.localPosition);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// _drag might be null if the drag activity ended and called _disposeDrag.
|
// _drag might be null if the drag activity ended and called _disposeDrag.
|
||||||
assert(_hold == null || _drag == null);
|
assert(_hold == null || _drag == null);
|
||||||
_drag?.update(details);
|
_drag?.update(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragEnd(DragEndDetails details) {
|
void _handleDragEnd(DragEndDetails details) {
|
||||||
if (_downPos != null) {
|
|
||||||
_onDismiss();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// _drag might be null if the drag activity ended and called _disposeDrag.
|
// _drag might be null if the drag activity ended and called _disposeDrag.
|
||||||
assert(_hold == null || _drag == null);
|
assert(_hold == null || _drag == null);
|
||||||
_drag?.end(details);
|
_drag?.end(details);
|
||||||
@@ -982,10 +900,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragCancel() {
|
void _handleDragCancel() {
|
||||||
if (_downPos != null) {
|
|
||||||
_onDismiss();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_gestureDetectorKey.currentContext == null) {
|
if (_gestureDetectorKey.currentContext == null) {
|
||||||
// The cancel was caused by the GestureDetector getting disposed, which
|
// The cancel was caused by the GestureDetector getting disposed, which
|
||||||
// means we will get disposed momentarily as well and shouldn't do
|
// means we will get disposed momentarily as well and shouldn't do
|
||||||
@@ -1099,8 +1013,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
late double _maxWidth;
|
|
||||||
|
|
||||||
Widget _buildChrome(BuildContext context, Widget child) {
|
Widget _buildChrome(BuildContext context, Widget child) {
|
||||||
final ScrollableDetails details = ScrollableDetails(
|
final ScrollableDetails details = ScrollableDetails(
|
||||||
direction: widget.axisDirection,
|
direction: widget.axisDirection,
|
||||||
@@ -1178,32 +1090,7 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return LayoutBuilder(
|
return result;
|
||||||
builder: (context, constraints) {
|
|
||||||
_maxWidth = constraints.maxWidth;
|
|
||||||
return AnimatedBuilder(
|
|
||||||
animation: _animController,
|
|
||||||
builder: (context, child) {
|
|
||||||
return Align(
|
|
||||||
alignment: AlignmentDirectional.topStart,
|
|
||||||
heightFactor: 1 - _animController.value,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Material(
|
|
||||||
color: widget.bgColor,
|
|
||||||
child: widget.header != null
|
|
||||||
? Column(
|
|
||||||
children: [
|
|
||||||
widget.header!,
|
|
||||||
Expanded(child: result),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: result,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the Future from calling ensureVisible for the ScrollPosition, as
|
// Returns the Future from calling ensureVisible for the ScrollPosition, as
|
||||||
@@ -1251,7 +1138,7 @@ class _ScrollableSelectionHandler extends StatefulWidget {
|
|||||||
required this.child,
|
required this.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
final CustomScrollableState state;
|
final ScrollableState state;
|
||||||
final ScrollPosition position;
|
final ScrollPosition position;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final SelectionRegistrar registrar;
|
final SelectionRegistrar registrar;
|
||||||
@@ -1324,7 +1211,7 @@ class _ScrollableSelectionContainerDelegate
|
|||||||
// An eye-balled value for a smooth scrolling speed.
|
// An eye-balled value for a smooth scrolling speed.
|
||||||
static const double _kDefaultSelectToScrollVelocityScalar = 30;
|
static const double _kDefaultSelectToScrollVelocityScalar = 30;
|
||||||
|
|
||||||
final CustomScrollableState state;
|
final ScrollableState state;
|
||||||
final EdgeDraggingAutoScroller _autoScroller;
|
final EdgeDraggingAutoScroller _autoScroller;
|
||||||
bool _scheduledLayoutChange = false;
|
bool _scheduledLayoutChange = false;
|
||||||
Offset? _currentDragStartRelatedToOrigin;
|
Offset? _currentDragStartRelatedToOrigin;
|
||||||
@@ -1776,7 +1663,7 @@ class _ScrollableSelectionContainerDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Offset _getDeltaToScrollOrigin(CustomScrollableState scrollableState) {
|
Offset _getDeltaToScrollOrigin(ScrollableState scrollableState) {
|
||||||
return switch (scrollableState.axisDirection) {
|
return switch (scrollableState.axisDirection) {
|
||||||
AxisDirection.up => Offset(0, -scrollableState.position.pixels),
|
AxisDirection.up => Offset(0, -scrollableState.position.pixels),
|
||||||
AxisDirection.down => Offset(0, scrollableState.position.pixels),
|
AxisDirection.down => Offset(0, scrollableState.position.pixels),
|
||||||
@@ -1795,7 +1682,7 @@ Offset _getDeltaToScrollOrigin(CustomScrollableState scrollableState) {
|
|||||||
/// [RenderObject.describeSemanticsConfiguration].
|
/// [RenderObject.describeSemanticsConfiguration].
|
||||||
///
|
///
|
||||||
/// If the tag [RenderViewport.useTwoPaneSemantics] is present on the viewport,
|
/// If the tag [RenderViewport.useTwoPaneSemantics] is present on the viewport,
|
||||||
/// two semantics nodes will be used to represent the [CustomScrollable]: The outer
|
/// two semantics nodes will be used to represent the [Scrollable]: The outer
|
||||||
/// node will contain all children, that are excluded from scrolling. The inner
|
/// node will contain all children, that are excluded from scrolling. The inner
|
||||||
/// node, which is annotated with the scrolling actions, will house the
|
/// node, which is annotated with the scrolling actions, will house the
|
||||||
/// scrollable children.
|
/// scrollable children.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'dart:math' as math;
|
|||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide ScrollableState;
|
||||||
|
|
||||||
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
|
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
|
||||||
/// to its edge.
|
/// to its edge.
|
||||||
@@ -30,7 +30,7 @@ class EdgeDraggingAutoScroller {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// The [CustomScrollable] this auto scroller is scrolling.
|
/// The [CustomScrollable] this auto scroller is scrolling.
|
||||||
final CustomScrollableState scrollable;
|
final ScrollableState scrollable;
|
||||||
|
|
||||||
/// Called when a scroll view is scrolled.
|
/// Called when a scroll view is scrolled.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:ui' show SemanticsRole;
|
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||||
import 'package:flutter/foundation.dart' show clampDouble;
|
import 'package:flutter/foundation.dart' show clampDouble;
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart'
|
||||||
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
show DragStartBehavior, HorizontalDragGestureRecognizer;
|
||||||
|
import 'package:flutter/material.dart' hide PageView;
|
||||||
|
|
||||||
/// A page view that displays the widget which corresponds to the currently
|
/// A page view that displays the widget which corresponds to the currently
|
||||||
/// selected tab.
|
/// selected tab.
|
||||||
@@ -23,11 +22,12 @@ import 'package:flutter/material.dart' hide TabBarView, PageView;
|
|||||||
/// [children] list and the length of the [TabBar.tabs] list.
|
/// [children] list and the length of the [TabBar.tabs] list.
|
||||||
///
|
///
|
||||||
/// To see a sample implementation, visit the [TabController] documentation.
|
/// To see a sample implementation, visit the [TabController] documentation.
|
||||||
class CustomTabBarView extends StatefulWidget {
|
class TabBarView<T extends HorizontalDragGestureRecognizer>
|
||||||
|
extends StatefulWidget {
|
||||||
/// Creates a page view with one child per tab.
|
/// Creates a page view with one child per tab.
|
||||||
///
|
///
|
||||||
/// The length of [children] must be the same as the [controller]'s length.
|
/// The length of [children] must be the same as the [controller]'s length.
|
||||||
const CustomTabBarView({
|
const TabBarView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.children,
|
required this.children,
|
||||||
this.controller,
|
this.controller,
|
||||||
@@ -35,13 +35,10 @@ class CustomTabBarView extends StatefulWidget {
|
|||||||
this.dragStartBehavior = DragStartBehavior.start,
|
this.dragStartBehavior = DragStartBehavior.start,
|
||||||
this.viewportFraction = 1.0,
|
this.viewportFraction = 1.0,
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.scrollDirection = Axis.horizontal,
|
required this.horizontalDragGestureRecognizer,
|
||||||
this.header,
|
|
||||||
this.bgColor = Colors.transparent,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget? header;
|
final T horizontalDragGestureRecognizer;
|
||||||
final Color bgColor;
|
|
||||||
|
|
||||||
/// This widget's selection and animation state.
|
/// This widget's selection and animation state.
|
||||||
///
|
///
|
||||||
@@ -77,13 +74,12 @@ class CustomTabBarView extends StatefulWidget {
|
|||||||
/// Defaults to [Clip.hardEdge].
|
/// Defaults to [Clip.hardEdge].
|
||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
|
|
||||||
final Axis scrollDirection;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CustomTabBarView> createState() => _CustomTabBarViewState();
|
State<TabBarView<T>> createState() => _TabBarViewState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CustomTabBarViewState extends State<CustomTabBarView> {
|
class _TabBarViewState<T extends HorizontalDragGestureRecognizer>
|
||||||
|
extends State<TabBarView<T>> {
|
||||||
TabController? _controller;
|
TabController? _controller;
|
||||||
PageController? _pageController;
|
PageController? _pageController;
|
||||||
late List<Widget> _childrenWithKey;
|
late List<Widget> _childrenWithKey;
|
||||||
@@ -168,7 +164,7 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(CustomTabBarView oldWidget) {
|
void didUpdateWidget(TabBarView<T> oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.controller != oldWidget.controller) {
|
if (widget.controller != oldWidget.controller) {
|
||||||
_updateTabController();
|
_updateTabController();
|
||||||
@@ -203,7 +199,7 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
|||||||
void _updateChildren() {
|
void _updateChildren() {
|
||||||
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(
|
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(
|
||||||
widget.children.map<Widget>((Widget child) {
|
widget.children.map<Widget>((Widget child) {
|
||||||
return Semantics(role: SemanticsRole.tabPanel, child: child);
|
return Semantics(role: .tabPanel, child: child);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -361,16 +357,14 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
|||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: _handleScrollNotification,
|
onNotification: _handleScrollNotification,
|
||||||
child: CustomPageView(
|
child: PageView<T>(
|
||||||
scrollDirection: widget.scrollDirection,
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
clipBehavior: widget.clipBehavior,
|
clipBehavior: widget.clipBehavior,
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
physics: widget.physics == null
|
physics: widget.physics == null
|
||||||
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
|
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
|
||||||
: const PageScrollPhysics().applyTo(widget.physics),
|
: const PageScrollPhysics().applyTo(widget.physics),
|
||||||
header: widget.header,
|
horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
|
||||||
bgColor: widget.bgColor,
|
|
||||||
children: _childrenWithKey,
|
children: _childrenWithKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
import 'dart:ui' show SemanticsRole;
|
import 'dart:ui' show SemanticsRole;
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:flutter/foundation.dart' show clampDouble;
|
import 'package:flutter/foundation.dart' show clampDouble;
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||||
import 'package:flutter/material.dart' hide TabBarView;
|
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
||||||
|
|
||||||
/// A page view that displays the widget which corresponds to the currently
|
/// A page view that displays the widget which corresponds to the currently
|
||||||
/// selected tab.
|
/// selected tab.
|
||||||
@@ -355,7 +357,7 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
|||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: _handleScrollNotification,
|
onNotification: _handleScrollNotification,
|
||||||
child: PageView(
|
child: PageView<CustomHorizontalDragGestureRecognizer>(
|
||||||
scrollDirection: widget.scrollDirection,
|
scrollDirection: widget.scrollDirection,
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
clipBehavior: widget.clipBehavior,
|
clipBehavior: widget.clipBehavior,
|
||||||
@@ -363,6 +365,8 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
|||||||
physics: widget.physics == null
|
physics: widget.physics == null
|
||||||
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
|
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
|
||||||
: const PageScrollPhysics().applyTo(widget.physics),
|
: const PageScrollPhysics().applyTo(widget.physics),
|
||||||
|
horizontalDragGestureRecognizer:
|
||||||
|
CustomHorizontalDragGestureRecognizer(),
|
||||||
children: _childrenWithKey,
|
children: _childrenWithKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
class CustomHorizontalDragGestureRecognizer
|
||||||
|
extends HorizontalDragGestureRecognizer {
|
||||||
|
CustomHorizontalDragGestureRecognizer({
|
||||||
|
super.debugOwner,
|
||||||
|
super.supportedDevices,
|
||||||
|
super.allowedButtonsFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
Offset? _initialPosition;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
|
super.addAllowedPointer(event);
|
||||||
|
_initialPosition = event.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hasSufficientGlobalDistanceToAccept(
|
||||||
|
PointerDeviceKind pointerDeviceKind,
|
||||||
|
double? deviceTouchSlop,
|
||||||
|
) {
|
||||||
|
return globalDistanceMoved.abs() > _computeHitSlop(pointerDeviceKind) &&
|
||||||
|
_cacl(_initialPosition!, lastPosition.global, gestureSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _cacl(
|
||||||
|
Offset initialPosition,
|
||||||
|
Offset lastPosition,
|
||||||
|
DeviceGestureSettings? gestureSettings,
|
||||||
|
) {
|
||||||
|
final offset = lastPosition - initialPosition;
|
||||||
|
return offset.dx.abs() > offset.dy.abs() * 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double touchSlopH = Pref.touchSlopH;
|
||||||
|
|
||||||
|
double _computeHitSlop(PointerDeviceKind kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case PointerDeviceKind.mouse:
|
||||||
|
return kPrecisePointerHitSlop;
|
||||||
|
case PointerDeviceKind.stylus:
|
||||||
|
case PointerDeviceKind.invertedStylus:
|
||||||
|
case PointerDeviceKind.unknown:
|
||||||
|
case PointerDeviceKind.touch:
|
||||||
|
case PointerDeviceKind.trackpad:
|
||||||
|
return touchSlopH;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide TabBarView;
|
||||||
|
|
||||||
Widget videoTabBarView({
|
Widget videoTabBarView({
|
||||||
required List<Widget> children,
|
required List<Widget> children,
|
||||||
TabController? controller,
|
TabController? controller,
|
||||||
}) => TabBarView(
|
}) => TabBarView<CustomHorizontalDragGestureRecognizer>(
|
||||||
physics: const CustomTabBarViewScrollPhysics(
|
|
||||||
parent: ClampingScrollPhysics(),
|
|
||||||
),
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
physics: const CustomTabBarViewScrollPhysics(parent: ClampingScrollPhysics()),
|
||||||
|
horizontalDragGestureRecognizer: CustomHorizontalDragGestureRecognizer(),
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget tabBarView({
|
Widget tabBarView({
|
||||||
required List<Widget> children,
|
required List<Widget> children,
|
||||||
TabController? controller,
|
TabController? controller,
|
||||||
}) => TabBarView(
|
}) => TabBarView<CustomHorizontalDragGestureRecognizer>(
|
||||||
physics: const CustomTabBarViewScrollPhysics(),
|
physics: const CustomTabBarViewScrollPhysics(),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
horizontalDragGestureRecognizer: CustomHorizontalDragGestureRecognizer(),
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:math' show max;
|
import 'dart:math' show max;
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
import 'package:flutter/gestures.dart' show HorizontalDragGestureRecognizer;
|
import 'package:flutter/gestures.dart' show HorizontalDragGestureRecognizer;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -12,8 +13,10 @@ abstract class CommonSlidePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
|
mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
|
||||||
|
static const double offset = 30.0;
|
||||||
double? _downDx;
|
double? _downDx;
|
||||||
late double _maxWidth;
|
late double _maxWidth;
|
||||||
|
double get maxWidth => _maxWidth;
|
||||||
late bool _isRTL = false;
|
late bool _isRTL = false;
|
||||||
late final bool enableSlide;
|
late final bool enableSlide;
|
||||||
late final AnimationController _animController;
|
late final AnimationController _animController;
|
||||||
@@ -33,7 +36,6 @@ mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
|
|||||||
_slideDragGestureRecognizer =
|
_slideDragGestureRecognizer =
|
||||||
SlideDragGestureRecognizer(
|
SlideDragGestureRecognizer(
|
||||||
isDxAllowed: (double dx) {
|
isDxAllowed: (double dx) {
|
||||||
const offset = 30;
|
|
||||||
final isLTR = dx <= offset;
|
final isLTR = dx <= offset;
|
||||||
final isRTL = dx >= _maxWidth - offset;
|
final isRTL = dx >= _maxWidth - offset;
|
||||||
if (isLTR || isRTL) {
|
if (isLTR || isRTL) {
|
||||||
@@ -111,6 +113,8 @@ mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef IsDxAllowed = bool Function(double dx);
|
||||||
|
|
||||||
class SlideDragGestureRecognizer extends HorizontalDragGestureRecognizer {
|
class SlideDragGestureRecognizer extends HorizontalDragGestureRecognizer {
|
||||||
SlideDragGestureRecognizer({
|
SlideDragGestureRecognizer({
|
||||||
super.debugOwner,
|
super.debugOwner,
|
||||||
@@ -119,7 +123,24 @@ class SlideDragGestureRecognizer extends HorizontalDragGestureRecognizer {
|
|||||||
required this.isDxAllowed,
|
required this.isDxAllowed,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool Function(double dx) isDxAllowed;
|
final IsDxAllowed isDxAllowed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isPointerAllowed(PointerEvent event) {
|
||||||
|
return isDxAllowed(event.localPosition.dx) && super.isPointerAllowed(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabBarDragGestureRecognizer
|
||||||
|
extends CustomHorizontalDragGestureRecognizer {
|
||||||
|
TabBarDragGestureRecognizer({
|
||||||
|
super.debugOwner,
|
||||||
|
super.supportedDevices,
|
||||||
|
super.allowedButtonsFilter,
|
||||||
|
required this.isDxAllowed,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IsDxAllowed isDxAllowed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isPointerAllowed(PointerEvent event) {
|
bool isPointerAllowed(PointerEvent event) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||||
import 'package:PiliPlus/http/fav.dart';
|
import 'package:PiliPlus/http/fav.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
@@ -205,64 +204,26 @@ class _EpisodePanelState extends State<EpisodePanel>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late final _isMulti =
|
||||||
|
widget.type == EpisodeType.season && widget.list.length > 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildPage(ThemeData theme) {
|
Widget buildPage(ThemeData theme) {
|
||||||
final isMulti = widget.type == EpisodeType.season && widget.list.length > 1;
|
|
||||||
|
|
||||||
Widget tabBar() => TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
padding: const EdgeInsets.only(right: 60),
|
|
||||||
isScrollable: true,
|
|
||||||
tabs: widget.list.map((item) => Tab(text: item.title)).toList(),
|
|
||||||
dividerHeight: 1,
|
|
||||||
dividerColor: theme.dividerColor.withValues(alpha: 0.1),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isMulti && enableSlide) {
|
|
||||||
return CustomTabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
physics: const CustomTabBarViewScrollPhysics(),
|
|
||||||
bgColor: theme.colorScheme.surface,
|
|
||||||
header: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_buildToolbar(theme),
|
|
||||||
tabBar(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
children: List.generate(
|
|
||||||
widget.list.length,
|
|
||||||
(index) => _buildBody(
|
|
||||||
theme,
|
|
||||||
index,
|
|
||||||
widget.list[index].episodes,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
color: showTitle ? theme.colorScheme.surface : null,
|
color: showTitle ? theme.colorScheme.surface : null,
|
||||||
type: showTitle ? MaterialType.canvas : MaterialType.transparency,
|
type: showTitle ? MaterialType.canvas : MaterialType.transparency,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildToolbar(theme),
|
_buildToolbar(theme),
|
||||||
if (isMulti) ...[
|
if (_isMulti)
|
||||||
tabBar(),
|
TabBar(
|
||||||
Expanded(
|
|
||||||
child: tabBarView(
|
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: List.generate(
|
padding: const EdgeInsets.only(right: 60),
|
||||||
widget.list.length,
|
isScrollable: true,
|
||||||
(index) => _buildBody(
|
tabs: widget.list.map((item) => Tab(text: item.title)).toList(),
|
||||||
theme,
|
dividerHeight: 1,
|
||||||
index,
|
dividerColor: theme.dividerColor.withValues(alpha: 0.1),
|
||||||
widget.list[index].episodes,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
] else
|
|
||||||
Expanded(child: enableSlide ? slideList(theme) : buildList(theme)),
|
Expanded(child: enableSlide ? slideList(theme) : buildList(theme)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -271,6 +232,25 @@ class _EpisodePanelState extends State<EpisodePanel>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildList(ThemeData theme) {
|
Widget buildList(ThemeData theme) {
|
||||||
|
if (_isMulti) {
|
||||||
|
return TabBarView<TabBarDragGestureRecognizer>(
|
||||||
|
controller: _tabController,
|
||||||
|
horizontalDragGestureRecognizer: TabBarDragGestureRecognizer(
|
||||||
|
isDxAllowed: (double dx) => enableSlide
|
||||||
|
? dx > CommonSlideMixin.offset &&
|
||||||
|
dx < maxWidth - CommonSlideMixin.offset
|
||||||
|
: true,
|
||||||
|
),
|
||||||
|
children: List.generate(
|
||||||
|
widget.list.length,
|
||||||
|
(index) => _buildBody(
|
||||||
|
theme,
|
||||||
|
index,
|
||||||
|
widget.list[index].episodes,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return _buildBody(theme, 0, _getCurrEpisodes);
|
return _buildBody(theme, 0, _getCurrEpisodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
|
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
@@ -10,7 +12,7 @@ import 'package:PiliPlus/pages/history/controller.dart';
|
|||||||
import 'package:PiliPlus/pages/history/widgets/item.dart';
|
import 'package:PiliPlus/pages/history/widgets/item.dart';
|
||||||
import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart';
|
import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide TabBarView;
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class HistoryPage extends StatefulWidget {
|
class HistoryPage extends StatefulWidget {
|
||||||
@@ -130,6 +132,8 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
? const NeverScrollableScrollPhysics()
|
? const NeverScrollableScrollPhysics()
|
||||||
: const CustomTabBarViewScrollPhysics(),
|
: const CustomTabBarViewScrollPhysics(),
|
||||||
controller: _historyController.tabController,
|
controller: _historyController.tabController,
|
||||||
|
horizontalDragGestureRecognizer:
|
||||||
|
CustomHorizontalDragGestureRecognizer(),
|
||||||
children: [
|
children: [
|
||||||
KeepAliveWrapper(builder: (context) => child),
|
KeepAliveWrapper(builder: (context) => child),
|
||||||
..._historyController.tabs.map(
|
..._historyController.tabs.map(
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
|
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
|
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
|
||||||
import 'package:PiliPlus/models/common/later_view_type.dart';
|
import 'package:PiliPlus/models/common/later_view_type.dart';
|
||||||
@@ -11,7 +13,7 @@ import 'package:PiliPlus/utils/accounts.dart';
|
|||||||
import 'package:PiliPlus/utils/extension/get_ext.dart';
|
import 'package:PiliPlus/utils/extension/get_ext.dart';
|
||||||
import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart';
|
import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart';
|
||||||
import 'package:PiliPlus/utils/request_utils.dart';
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide TabBarView;
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||||
|
|
||||||
@@ -137,6 +139,8 @@ class _LaterPageState extends State<LaterPage>
|
|||||||
? const NeverScrollableScrollPhysics()
|
? const NeverScrollableScrollPhysics()
|
||||||
: const CustomTabBarViewScrollPhysics(),
|
: const CustomTabBarViewScrollPhysics(),
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
|
horizontalDragGestureRecognizer:
|
||||||
|
CustomHorizontalDragGestureRecognizer(),
|
||||||
children: LaterViewType.values
|
children: LaterViewType.values
|
||||||
.map((item) => item.page)
|
.map((item) => item.page)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import 'dart:ui';
|
|||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
@@ -41,7 +43,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide PageView;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart';
|
import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart';
|
||||||
@@ -721,7 +723,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12, top: isPortrait ? 12 : 0),
|
padding: EdgeInsets.only(bottom: 12, top: isPortrait ? 12 : 0),
|
||||||
child: _liveRoomController.showSuperChat
|
child: _liveRoomController.showSuperChat
|
||||||
? PageView(
|
? PageView<CustomHorizontalDragGestureRecognizer>(
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
controller: _liveRoomController.pageController,
|
controller: _liveRoomController.pageController,
|
||||||
physics: const CustomTabBarViewScrollPhysics(
|
physics: const CustomTabBarViewScrollPhysics(
|
||||||
@@ -729,6 +731,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
),
|
),
|
||||||
onPageChanged: (value) =>
|
onPageChanged: (value) =>
|
||||||
_liveRoomController.pageIndex.value = value,
|
_liveRoomController.pageIndex.value = value,
|
||||||
|
horizontalDragGestureRecognizer:
|
||||||
|
CustomHorizontalDragGestureRecognizer(),
|
||||||
children: [
|
children: [
|
||||||
KeepAliveWrapper(builder: (context) => chat()),
|
KeepAliveWrapper(builder: (context) => chat()),
|
||||||
SuperChatPanel(
|
SuperChatPanel(
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart';
|
import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/tabs.dart';
|
import 'package:PiliPlus/common/widgets/flutter/tabs.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/models/common/nav_bar_config.dart';
|
import 'package:PiliPlus/models/common/nav_bar_config.dart';
|
||||||
import 'package:PiliPlus/pages/home/view.dart';
|
import 'package:PiliPlus/pages/home/view.dart';
|
||||||
@@ -18,7 +20,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
|
|||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/storage_key.dart';
|
import 'package:PiliPlus/utils/storage_key.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide PageView;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
@@ -393,9 +395,11 @@ class _MainAppState extends PopScopeState<MainApp>
|
|||||||
children: _mainController.navigationBars.map((i) => i.page).toList(),
|
children: _mainController.navigationBars.map((i) => i.page).toList(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
child = PageView(
|
child = PageView<CustomHorizontalDragGestureRecognizer>(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
controller: _mainController.controller,
|
controller: _mainController.controller,
|
||||||
|
horizontalDragGestureRecognizer:
|
||||||
|
CustomHorizontalDragGestureRecognizer(),
|
||||||
children: _mainController.navigationBars.map((i) => i.page).toList(),
|
children: _mainController.navigationBars.map((i) => i.page).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'dart:math' show pi, max;
|
|||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'
|
||||||
|
show touchSlopH;
|
||||||
import 'package:PiliPlus/common/widgets/image/custom_grid_view.dart'
|
import 'package:PiliPlus/common/widgets/image/custom_grid_view.dart'
|
||||||
show CustomGridView, ImageModel;
|
show CustomGridView, ImageModel;
|
||||||
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
||||||
@@ -187,11 +189,9 @@ List<SettingsModel> get extraSettings => [
|
|||||||
leading: const Icon(Icons.notifications_none),
|
leading: const Icon(Icons.notifications_none),
|
||||||
setKey: SettingBoxKey.checkDynamic,
|
setKey: SettingBoxKey.checkDynamic,
|
||||||
defaultVal: true,
|
defaultVal: true,
|
||||||
onChanged: (value) {
|
onChanged: (value) => Get.find<MainController>().checkDynamic = value,
|
||||||
Get.find<MainController>().checkDynamic = value;
|
|
||||||
},
|
|
||||||
onTap: (context) {
|
onTap: (context) {
|
||||||
int dynamicPeriod = Pref.dynamicPeriod;
|
String dynamicPeriod = Pref.dynamicPeriod.toString();
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -199,11 +199,9 @@ List<SettingsModel> get extraSettings => [
|
|||||||
title: const Text('检查周期'),
|
title: const Text('检查周期'),
|
||||||
content: TextFormField(
|
content: TextFormField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
initialValue: dynamicPeriod.toString(),
|
initialValue: dynamicPeriod,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged: (value) {
|
onChanged: (value) => dynamicPeriod = value,
|
||||||
dynamicPeriod = int.tryParse(value) ?? 5;
|
|
||||||
},
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
decoration: const InputDecoration(suffixText: 'min'),
|
decoration: const InputDecoration(suffixText: 'min'),
|
||||||
),
|
),
|
||||||
@@ -219,13 +217,14 @@ List<SettingsModel> get extraSettings => [
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
try {
|
||||||
|
final val = int.parse(dynamicPeriod);
|
||||||
Get.back();
|
Get.back();
|
||||||
GStorage.setting.put(
|
GStorage.setting.put(SettingBoxKey.dynamicPeriod, val);
|
||||||
SettingBoxKey.dynamicPeriod,
|
Get.find<MainController>().dynamicPeriod = val * 60 * 1000;
|
||||||
dynamicPeriod,
|
} catch (e) {
|
||||||
);
|
SmartDialog.showToast(e.toString());
|
||||||
Get.find<MainController>().dynamicPeriod =
|
}
|
||||||
dynamicPeriod * 60 * 1000;
|
|
||||||
},
|
},
|
||||||
child: const Text('确定'),
|
child: const Text('确定'),
|
||||||
),
|
),
|
||||||
@@ -312,9 +311,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
initialValue: replyLengthLimit,
|
initialValue: replyLengthLimit,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged: (value) {
|
onChanged: (value) => replyLengthLimit = value,
|
||||||
replyLengthLimit = value;
|
|
||||||
},
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
decoration: const InputDecoration(suffixText: '行'),
|
decoration: const InputDecoration(suffixText: '行'),
|
||||||
),
|
),
|
||||||
@@ -365,12 +362,8 @@ List<SettingsModel> get extraSettings => [
|
|||||||
content: TextFormField(
|
content: TextFormField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
initialValue: danmakuLineHeight,
|
initialValue: danmakuLineHeight,
|
||||||
keyboardType: const TextInputType.numberWithOptions(
|
keyboardType: const .numberWithOptions(decimal: true),
|
||||||
decimal: true,
|
onChanged: (value) => danmakuLineHeight = value,
|
||||||
),
|
|
||||||
onChanged: (value) {
|
|
||||||
danmakuLineHeight = value;
|
|
||||||
},
|
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')),
|
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')),
|
||||||
],
|
],
|
||||||
@@ -460,6 +453,56 @@ List<SettingsModel> get extraSettings => [
|
|||||||
setKey: SettingBoxKey.openInBrowser,
|
setKey: SettingBoxKey.openInBrowser,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
),
|
),
|
||||||
|
NormalModel(
|
||||||
|
title: '横向滑动阈值',
|
||||||
|
getSubtitle: () => '当前:「${Pref.touchSlopH}」',
|
||||||
|
onTap: (context, setState) {
|
||||||
|
String initialValue = Pref.touchSlopH.toString();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('横向滑动阈值'),
|
||||||
|
content: TextFormField(
|
||||||
|
autofocus: true,
|
||||||
|
initialValue: initialValue,
|
||||||
|
keyboardType: const .numberWithOptions(decimal: true),
|
||||||
|
onChanged: (value) => initialValue = value,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
final val = double.parse(initialValue);
|
||||||
|
Get.back();
|
||||||
|
touchSlopH = val;
|
||||||
|
await GStorage.setting.put(SettingBoxKey.touchSlopH, val);
|
||||||
|
setState();
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast(e.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
leading: const Icon(Icons.pan_tool_alt_outlined),
|
||||||
|
),
|
||||||
NormalModel(
|
NormalModel(
|
||||||
title: '刷新滑动距离',
|
title: '刷新滑动距离',
|
||||||
leading: const Icon(Icons.refresh),
|
leading: const Icon(Icons.refresh),
|
||||||
@@ -687,9 +730,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
),
|
),
|
||||||
setKey: SettingBoxKey.antiGoodsDyn,
|
setKey: SettingBoxKey.antiGoodsDyn,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
onChanged: (value) {
|
onChanged: (value) => DynamicsDataModel.antiGoodsDyn = value,
|
||||||
DynamicsDataModel.antiGoodsDyn = value;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SwitchModel(
|
SwitchModel(
|
||||||
title: '屏蔽带货评论',
|
title: '屏蔽带货评论',
|
||||||
@@ -703,9 +744,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
),
|
),
|
||||||
setKey: SettingBoxKey.antiGoodsReply,
|
setKey: SettingBoxKey.antiGoodsReply,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
onChanged: (value) {
|
onChanged: (value) => ReplyGrpc.antiGoodsReply = value,
|
||||||
ReplyGrpc.antiGoodsReply = value;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SwitchModel(
|
SwitchModel(
|
||||||
title: '侧滑关闭二级页面',
|
title: '侧滑关闭二级页面',
|
||||||
@@ -715,9 +754,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
),
|
),
|
||||||
setKey: SettingBoxKey.slideDismissReplyPage,
|
setKey: SettingBoxKey.slideDismissReplyPage,
|
||||||
defaultVal: Platform.isIOS,
|
defaultVal: Platform.isIOS,
|
||||||
onChanged: (value) {
|
onChanged: (value) => CommonSlideMixin.slideDismissReplyPage = value,
|
||||||
CommonSlideMixin.slideDismissReplyPage = value;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SwitchModel(
|
const SwitchModel(
|
||||||
title: '启用双指缩小视频',
|
title: '启用双指缩小视频',
|
||||||
@@ -856,9 +893,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
leading: const Icon(Icons.search_outlined),
|
leading: const Icon(Icons.search_outlined),
|
||||||
setKey: SettingBoxKey.enableWordRe,
|
setKey: SettingBoxKey.enableWordRe,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
onChanged: (value) {
|
onChanged: (value) => ReplyItemGrpc.enableWordRe = value,
|
||||||
ReplyItemGrpc.enableWordRe = value;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SwitchModel(
|
const SwitchModel(
|
||||||
title: '启用AI总结',
|
title: '启用AI总结',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PgcIntroPanel extends CommonSlidePage {
|
|||||||
const PgcIntroPanel({
|
const PgcIntroPanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
super.enableSlide = false,
|
super.enableSlide,
|
||||||
this.videoTags,
|
this.videoTags,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,11 +50,11 @@ class _IntroDetailState extends State<PgcIntroPanel>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildPage(ThemeData theme) {
|
Widget buildPage(ThemeData theme) {
|
||||||
return CustomTabBarView(
|
return Material(
|
||||||
controller: _tabController,
|
color: theme.colorScheme.surface,
|
||||||
physics: const CustomTabBarViewScrollPhysics(),
|
child: Column(
|
||||||
bgColor: theme.colorScheme.surface,
|
children: [
|
||||||
header: Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
@@ -84,8 +84,27 @@ class _IntroDetailState extends State<PgcIntroPanel>
|
|||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: enableSlide ? slideList(theme) : buildList(theme),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildList(ThemeData theme) {
|
||||||
|
return TabBarView<TabBarDragGestureRecognizer>(
|
||||||
|
controller: _tabController,
|
||||||
|
physics: const CustomTabBarViewScrollPhysics(),
|
||||||
|
horizontalDragGestureRecognizer: TabBarDragGestureRecognizer(
|
||||||
|
isDxAllowed: (double dx) => enableSlide
|
||||||
|
? dx > CommonSlideMixin.offset &&
|
||||||
|
dx < maxWidth - CommonSlideMixin.offset
|
||||||
|
: true,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
KeepAliveWrapper(builder: (context) => buildList(theme)),
|
KeepAliveWrapper(builder: (context) => _buildInfo(theme)),
|
||||||
PgcReviewPage(
|
PgcReviewPage(
|
||||||
name: widget.item.title!,
|
name: widget.item.title!,
|
||||||
mediaId: widget.item.mediaId,
|
mediaId: widget.item.mediaId,
|
||||||
@@ -94,8 +113,7 @@ class _IntroDetailState extends State<PgcIntroPanel>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget _buildInfo(ThemeData theme) {
|
||||||
Widget buildList(ThemeData theme) {
|
|
||||||
final TextStyle smallTitle = TextStyle(
|
final TextStyle smallTitle = TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ abstract final class SettingBoxKey {
|
|||||||
downloadPath = 'downloadPath',
|
downloadPath = 'downloadPath',
|
||||||
followOrderType = 'followOrderType',
|
followOrderType = 'followOrderType',
|
||||||
enableImgMenu = 'enableImgMenu',
|
enableImgMenu = 'enableImgMenu',
|
||||||
showDynDispute = 'showDynDispute';
|
showDynDispute = 'showDynDispute',
|
||||||
|
touchSlopH = 'touchSlopH';
|
||||||
|
|
||||||
static const String minimizeOnExit = 'minimizeOnExit',
|
static const String minimizeOnExit = 'minimizeOnExit',
|
||||||
windowSize = 'windowSize',
|
windowSize = 'windowSize',
|
||||||
|
|||||||
@@ -947,4 +947,7 @@ abstract final class Pref {
|
|||||||
|
|
||||||
static bool get showDynDispute =>
|
static bool get showDynDispute =>
|
||||||
_setting.get(SettingBoxKey.showDynDispute, defaultValue: false);
|
_setting.get(SettingBoxKey.showDynDispute, defaultValue: false);
|
||||||
|
|
||||||
|
static double get touchSlopH =>
|
||||||
|
_setting.get(SettingBoxKey.touchSlopH, defaultValue: 24.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user