mirror of
https://github.com/venera-app/venera.git
synced 2025-12-16 07:01:16 +00:00
Optimize iOS full-screen back gesture implementation (#643)
* Optimize iOS full-screen back gesture implementation - Fix #613 and #617 * Fix setting page
This commit is contained in:
@@ -20,7 +20,6 @@ class AppPageRoute<T> extends PageRoute<T> with _AppRouteTransitionMixin{
|
|||||||
super.allowSnapshotting = true,
|
super.allowSnapshotting = true,
|
||||||
super.barrierDismissible = false,
|
super.barrierDismissible = false,
|
||||||
this.enableIOSGesture = true,
|
this.enableIOSGesture = true,
|
||||||
this.iosFullScreenPopGesture = true,
|
|
||||||
this.preventRebuild = true,
|
this.preventRebuild = true,
|
||||||
}) {
|
}) {
|
||||||
assert(opaque);
|
assert(opaque);
|
||||||
@@ -50,9 +49,6 @@ class AppPageRoute<T> extends PageRoute<T> with _AppRouteTransitionMixin{
|
|||||||
@override
|
@override
|
||||||
final bool enableIOSGesture;
|
final bool enableIOSGesture;
|
||||||
|
|
||||||
@override
|
|
||||||
final bool iosFullScreenPopGesture;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool preventRebuild;
|
final bool preventRebuild;
|
||||||
}
|
}
|
||||||
@@ -79,8 +75,6 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
|
|||||||
|
|
||||||
bool get enableIOSGesture;
|
bool get enableIOSGesture;
|
||||||
|
|
||||||
bool get iosFullScreenPopGesture;
|
|
||||||
|
|
||||||
bool get preventRebuild;
|
bool get preventRebuild;
|
||||||
|
|
||||||
Widget? _child;
|
Widget? _child;
|
||||||
@@ -140,7 +134,6 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
|
|||||||
gestureWidth: _kBackGestureWidth,
|
gestureWidth: _kBackGestureWidth,
|
||||||
enabledCallback: () => _isPopGestureEnabled<T>(this),
|
enabledCallback: () => _isPopGestureEnabled<T>(this),
|
||||||
onStartPopGesture: () => _startPopGesture(this),
|
onStartPopGesture: () => _startPopGesture(this),
|
||||||
fullScreen: iosFullScreenPopGesture,
|
|
||||||
child: child,
|
child: child,
|
||||||
)
|
)
|
||||||
: child);
|
: child);
|
||||||
@@ -210,32 +203,41 @@ class IOSBackGestureController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class IOSBackGestureDetector extends StatefulWidget {
|
class IOSBackGestureDetector extends StatefulWidget {
|
||||||
const IOSBackGestureDetector(
|
const IOSBackGestureDetector({
|
||||||
{required this.enabledCallback,
|
required this.enabledCallback,
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.gestureWidth,
|
required this.gestureWidth,
|
||||||
required this.onStartPopGesture,
|
required this.onStartPopGesture,
|
||||||
this.fullScreen = false,
|
super.key,
|
||||||
super.key});
|
});
|
||||||
|
|
||||||
final double gestureWidth;
|
final double gestureWidth;
|
||||||
|
|
||||||
final bool Function() enabledCallback;
|
final bool Function() enabledCallback;
|
||||||
|
|
||||||
final IOSBackGestureController Function() onStartPopGesture;
|
final IOSBackGestureController Function() onStartPopGesture;
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
final bool fullScreen;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<IOSBackGestureDetector> createState() => _IOSBackGestureDetectorState();
|
State<IOSBackGestureDetector> createState() => _IOSBackGestureDetectorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _IOSBackGestureDetectorState extends State<IOSBackGestureDetector> {
|
class _IOSBackGestureDetectorState extends State<IOSBackGestureDetector> {
|
||||||
IOSBackGestureController? _backGestureController;
|
IOSBackGestureController? _backGestureController;
|
||||||
|
late _BackSwipeRecognizer _recognizer;
|
||||||
|
|
||||||
late HorizontalDragGestureRecognizer _recognizer;
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_recognizer = _BackSwipeRecognizer(
|
||||||
|
debugOwner: this,
|
||||||
|
gestureWidth: widget.gestureWidth,
|
||||||
|
isPointerInHorizontal: _isPointerInHorizontalScrollable,
|
||||||
|
onStart: _handleDragStart,
|
||||||
|
onUpdate: _handleDragUpdate,
|
||||||
|
onEnd: _handleDragEnd,
|
||||||
|
onCancel: _handleDragCancel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -243,115 +245,208 @@ class _IOSBackGestureDetectorState extends State<IOSBackGestureDetector> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
|
|
||||||
..onStart = _handleDragStart
|
|
||||||
..onUpdate = _handleDragUpdate
|
|
||||||
..onEnd = _handleDragEnd
|
|
||||||
..onCancel = _handleDragCancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var dragAreaWidth = Directionality.of(context) == TextDirection.ltr
|
return RawGestureDetector(
|
||||||
? MediaQuery.of(context).padding.left
|
behavior: HitTestBehavior.translucent,
|
||||||
: MediaQuery.of(context).padding.right;
|
gestures: {
|
||||||
dragAreaWidth = max(dragAreaWidth, widget.gestureWidth);
|
_BackSwipeRecognizer: GestureRecognizerFactoryWithHandlers<_BackSwipeRecognizer>(
|
||||||
final Widget gestureListener = widget.fullScreen
|
() => _recognizer,
|
||||||
? Positioned.fill(
|
(instance) {
|
||||||
child: Listener(
|
instance.gestureWidth = widget.gestureWidth;
|
||||||
onPointerDown: _handlePointerDown,
|
},
|
||||||
behavior: HitTestBehavior.translucent,
|
),
|
||||||
),
|
},
|
||||||
)
|
child: widget.child,
|
||||||
: Positioned(
|
|
||||||
width: dragAreaWidth,
|
|
||||||
top: 0.0,
|
|
||||||
bottom: 0.0,
|
|
||||||
left: Directionality.of(context) == TextDirection.ltr ? 0.0 : null,
|
|
||||||
right: Directionality.of(context) == TextDirection.rtl ? 0.0 : null,
|
|
||||||
child: Listener(
|
|
||||||
onPointerDown: _handlePointerDown,
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Stack(
|
|
||||||
fit: StackFit.passthrough,
|
|
||||||
children: <Widget>[
|
|
||||||
widget.child,
|
|
||||||
gestureListener,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handlePointerDown(PointerDownEvent event) {
|
bool _isPointerInHorizontalScrollable(Offset globalPosition) {
|
||||||
if (!widget.enabledCallback()) return;
|
|
||||||
if (widget.fullScreen && _isPointerOverHorizontalScrollable(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_recognizer.addPointer(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragCancel() {
|
|
||||||
assert(mounted);
|
|
||||||
_backGestureController?.dragEnd(0.0);
|
|
||||||
_backGestureController = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
double _convertToLogical(double value) {
|
|
||||||
switch (Directionality.of(context)) {
|
|
||||||
case TextDirection.rtl:
|
|
||||||
return -value;
|
|
||||||
case TextDirection.ltr:
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragEnd(DragEndDetails details) {
|
|
||||||
assert(mounted);
|
|
||||||
assert(_backGestureController != null);
|
|
||||||
_backGestureController!.dragEnd(_convertToLogical(
|
|
||||||
details.velocity.pixelsPerSecond.dx / context.size!.width));
|
|
||||||
_backGestureController = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragStart(DragStartDetails details) {
|
|
||||||
assert(mounted);
|
|
||||||
assert(_backGestureController == null);
|
|
||||||
_backGestureController = widget.onStartPopGesture();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragUpdate(DragUpdateDetails details) {
|
|
||||||
assert(mounted);
|
|
||||||
assert(_backGestureController != null);
|
|
||||||
_backGestureController!.dragUpdate(
|
|
||||||
_convertToLogical(details.primaryDelta! / context.size!.width));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isPointerOverHorizontalScrollable(PointerDownEvent event) {
|
|
||||||
final HitTestResult result = HitTestResult();
|
final HitTestResult result = HitTestResult();
|
||||||
WidgetsBinding.instance.hitTest(result, event.position);
|
WidgetsBinding.instance.hitTest(result, globalPosition);
|
||||||
|
|
||||||
for (final entry in result.path) {
|
for (final entry in result.path) {
|
||||||
final target = entry.target;
|
final target = entry.target;
|
||||||
if (target is RenderViewport) {
|
if (target is RenderViewport) {
|
||||||
if (_isAxisHorizontal(target.axisDirection)) {
|
if (target.axisDirection == AxisDirection.left ||
|
||||||
|
target.axisDirection == AxisDirection.right) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (target is RenderSliver) {
|
}
|
||||||
if (_isAxisHorizontal(target.constraints.axisDirection)) {
|
else if (target is RenderSliver) {
|
||||||
|
if (target.constraints.axisDirection == AxisDirection.left ||
|
||||||
|
target.constraints.axisDirection == AxisDirection.right) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (target.runtimeType.toString() == '_RenderSingleChildViewport') {
|
||||||
|
try {
|
||||||
|
final dynamic renderObject = target;
|
||||||
|
if (renderObject.axis == Axis.horizontal) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// protected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (target is RenderEditable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isAxisHorizontal(AxisDirection direction) {
|
void _handleDragStart(DragStartDetails details) {
|
||||||
return direction == AxisDirection.left || direction == AxisDirection.right;
|
if (!widget.enabledCallback()) return;
|
||||||
|
if (mounted && _backGestureController == null) {
|
||||||
|
_backGestureController = widget.onStartPopGesture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleDragUpdate(DragUpdateDetails details) {
|
||||||
|
if (mounted && _backGestureController != null) {
|
||||||
|
_backGestureController!.dragUpdate(
|
||||||
|
_convertToLogical(details.primaryDelta! / context.size!.width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDragEnd(DragEndDetails details) {
|
||||||
|
if (mounted && _backGestureController != null) {
|
||||||
|
_backGestureController!.dragEnd(_convertToLogical(
|
||||||
|
details.velocity.pixelsPerSecond.dx / context.size!.width));
|
||||||
|
_backGestureController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDragCancel() {
|
||||||
|
if (mounted && _backGestureController != null) {
|
||||||
|
_backGestureController?.dragEnd(0.0);
|
||||||
|
_backGestureController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _convertToLogical(double value) {
|
||||||
|
switch (Directionality.of(context)) {
|
||||||
|
case TextDirection.rtl: return -value;
|
||||||
|
case TextDirection.ltr: return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackSwipeRecognizer extends OneSequenceGestureRecognizer {
|
||||||
|
_BackSwipeRecognizer({
|
||||||
|
required this.isPointerInHorizontal,
|
||||||
|
required this.gestureWidth,
|
||||||
|
required this.onStart,
|
||||||
|
required this.onUpdate,
|
||||||
|
required this.onEnd,
|
||||||
|
required this.onCancel,
|
||||||
|
super.debugOwner,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool Function(Offset globalPosition) isPointerInHorizontal;
|
||||||
|
double gestureWidth;
|
||||||
|
final ValueSetter<DragStartDetails> onStart;
|
||||||
|
final ValueSetter<DragUpdateDetails> onUpdate;
|
||||||
|
final ValueSetter<DragEndDetails> onEnd;
|
||||||
|
final VoidCallback onCancel;
|
||||||
|
|
||||||
|
Offset? _startGlobal;
|
||||||
|
bool _accepted = false;
|
||||||
|
bool _startedInHorizontal = false;
|
||||||
|
bool _startedNearLeftEdge = false;
|
||||||
|
|
||||||
|
VelocityTracker? _velocityTracker;
|
||||||
|
|
||||||
|
static const double _minDistance = 5.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addPointer(PointerDownEvent event) {
|
||||||
|
startTrackingPointer(event.pointer);
|
||||||
|
_startGlobal = event.position;
|
||||||
|
_accepted = false;
|
||||||
|
|
||||||
|
_startedInHorizontal = isPointerInHorizontal(event.position);
|
||||||
|
_startedNearLeftEdge = event.position.dx <= gestureWidth;
|
||||||
|
|
||||||
|
_velocityTracker = VelocityTracker.withKind(event.kind);
|
||||||
|
_velocityTracker?.addPosition(event.timeStamp, event.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleEvent(PointerEvent event) {
|
||||||
|
if (event is PointerMoveEvent || event is PointerUpEvent) {
|
||||||
|
_velocityTracker?.addPosition(event.timeStamp, event.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event is PointerMoveEvent) {
|
||||||
|
if (_startGlobal == null) return;
|
||||||
|
final delta = event.position - _startGlobal!;
|
||||||
|
final dx = delta.dx;
|
||||||
|
final dy = delta.dy.abs();
|
||||||
|
|
||||||
|
if (!_accepted) {
|
||||||
|
if (delta.distance < _minDistance) return;
|
||||||
|
|
||||||
|
final isRight = dx > 0;
|
||||||
|
final isHorizontal = dx.abs() > dy * 1.5;
|
||||||
|
final bool eligible = _startedNearLeftEdge || (!_startedInHorizontal);
|
||||||
|
|
||||||
|
if (isRight && isHorizontal && eligible) {
|
||||||
|
_accepted = true;
|
||||||
|
resolve(GestureDisposition.accepted);
|
||||||
|
onStart(DragStartDetails(
|
||||||
|
globalPosition: _startGlobal!,
|
||||||
|
localPosition: event.localPosition
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
resolve(GestureDisposition.rejected);
|
||||||
|
stopTrackingPointer(event.pointer);
|
||||||
|
_startGlobal = null;
|
||||||
|
_velocityTracker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_accepted) {
|
||||||
|
onUpdate(DragUpdateDetails(
|
||||||
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
|
primaryDelta: event.delta.dx,
|
||||||
|
delta: event.delta,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if (event is PointerUpEvent) {
|
||||||
|
if (_accepted) {
|
||||||
|
final Velocity velocity = _velocityTracker?.getVelocity() ?? Velocity.zero;
|
||||||
|
|
||||||
|
onEnd(DragEndDetails(
|
||||||
|
velocity: velocity,
|
||||||
|
primaryVelocity: velocity.pixelsPerSecond.dx
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
} else if (event is PointerCancelEvent) {
|
||||||
|
if (_accepted) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
stopTrackingPointer(0);
|
||||||
|
_accepted = false;
|
||||||
|
_startGlobal = null;
|
||||||
|
_startedInHorizontal = false;
|
||||||
|
_startedNearLeftEdge = false;
|
||||||
|
_velocityTracker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugDescription => 'IOSBackSwipe';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didStopTrackingLastPointer(int pointer) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SlidePageTransitionBuilder extends PageTransitionsBuilder {
|
class SlidePageTransitionBuilder extends PageTransitionsBuilder {
|
||||||
|
|||||||
@@ -541,8 +541,7 @@ class PageJumpTarget {
|
|||||||
text: attributes?["text"] ?? attributes?["keyword"] ?? "",
|
text: attributes?["text"] ?? attributes?["keyword"] ?? "",
|
||||||
sourceKey: sourceKey,
|
sourceKey: sourceKey,
|
||||||
options: List.from(attributes?["options"] ?? []),
|
options: List.from(attributes?["options"] ?? []),
|
||||||
),
|
)
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
} else if (page == "category") {
|
} else if (page == "category") {
|
||||||
var key = ComicSource.find(sourceKey)!.categoryData!.key;
|
var key = ComicSource.find(sourceKey)!.categoryData!.key;
|
||||||
|
|||||||
@@ -14,20 +14,14 @@ extension Navigation on BuildContext {
|
|||||||
return Navigator.of(this).canPop();
|
return Navigator.of(this).canPop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> to<T>(Widget Function() builder,
|
Future<T?> to<T>(Widget Function() builder,) {
|
||||||
{bool enableIOSGesture = true, bool iosFullScreenGesture = true}) {
|
|
||||||
return Navigator.of(this).push<T>(AppPageRoute(
|
return Navigator.of(this).push<T>(AppPageRoute(
|
||||||
builder: (context) => builder(),
|
builder: (context) => builder()));
|
||||||
enableIOSGesture: enableIOSGesture,
|
|
||||||
iosFullScreenPopGesture: iosFullScreenGesture));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> toReplacement<T>(Widget Function() builder,
|
Future<void> toReplacement<T>(Widget Function() builder) {
|
||||||
{bool enableIOSGesture = true, bool iosFullScreenGesture = true}) {
|
|
||||||
return Navigator.of(this).pushReplacement(AppPageRoute(
|
return Navigator.of(this).pushReplacement(AppPageRoute(
|
||||||
builder: (context) => builder(),
|
builder: (context) => builder()));
|
||||||
enableIOSGesture: enableIOSGesture,
|
|
||||||
iosFullScreenPopGesture: iosFullScreenGesture));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double get width => MediaQuery.of(this).size.width;
|
double get width => MediaQuery.of(this).size.width;
|
||||||
|
|||||||
@@ -153,9 +153,7 @@ class LocalComic with HistoryMixin implements Comic {
|
|||||||
),
|
),
|
||||||
author: subtitle,
|
author: subtitle,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
|||||||
text: widget.keyword,
|
text: widget.keyword,
|
||||||
sourceKey: widget.source.key,
|
sourceKey: widget.source.key,
|
||||||
),
|
),
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -115,9 +115,7 @@ abstract mixin class _ComicPageActions {
|
|||||||
history: history ?? History.fromModel(model: comic, ep: 0, page: 0),
|
history: history ?? History.fromModel(model: comic, ep: 0, page: 0),
|
||||||
author: comic.findAuthor() ?? '',
|
author: comic.findAuthor() ?? '',
|
||||||
tags: comic.plainTags,
|
tags: comic.plainTags,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
)
|
)
|
||||||
.then((_) {
|
.then((_) {
|
||||||
onReadEnd();
|
onReadEnd();
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
author: localComic.subTitle ?? '',
|
author: localComic.subTitle ?? '',
|
||||||
tags: localComic.tags,
|
tags: localComic.tags,
|
||||||
);
|
);
|
||||||
}, enableIOSGesture: false, iosFullScreenGesture: false);
|
});
|
||||||
App.mainNavigatorKey!.currentContext!.pop();
|
App.mainNavigatorKey!.currentContext!.pop();
|
||||||
});
|
});
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
|
|||||||
@@ -200,7 +200,6 @@ class _BodyState extends State<_Body> {
|
|||||||
await ComicSourceManager().reload();
|
await ComicSourceManager().reload();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}),
|
}),
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -574,9 +574,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
App.rootContext.to(() => ReaderWithLoading(
|
App.rootContext.to(() => ReaderWithLoading(
|
||||||
id: c.id,
|
id: c.id,
|
||||||
sourceKey: c.sourceKey,
|
sourceKey: c.sourceKey,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -589,9 +587,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
App.mainNavigatorKey?.currentContext?.to(() => ComicPage(
|
App.mainNavigatorKey?.currentContext?.to(() => ComicPage(
|
||||||
id: c.id,
|
id: c.id,
|
||||||
sourceKey: c.sourceKey,
|
sourceKey: c.sourceKey,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -693,9 +689,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
() => ReaderWithLoading(
|
() => ReaderWithLoading(
|
||||||
id: c.id,
|
id: c.id,
|
||||||
sourceKey: c.sourceKey,
|
sourceKey: c.sourceKey,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -720,9 +714,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
cover: c.cover,
|
cover: c.cover,
|
||||||
title: c.title,
|
title: c.title,
|
||||||
heroID: heroID,
|
heroID: heroID,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
App.mainNavigatorKey?.currentContext?.to(
|
App.mainNavigatorKey?.currentContext?.to(
|
||||||
|
|||||||
@@ -895,8 +895,7 @@ class _ImageFavoritesState extends State<ImageFavorites> {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.to(
|
context.to(
|
||||||
() => const ImageFavoritesPage(),
|
() => const ImageFavoritesPage()
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1018,7 +1017,6 @@ class _ImageFavoritesState extends State<ImageFavorites> {
|
|||||||
onTap: (text) {
|
onTap: (text) {
|
||||||
context.to(
|
context.to(
|
||||||
() => ImageFavoritesPage(initialKeyword: text),
|
() => ImageFavoritesPage(initialKeyword: text),
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ class _ImageFavoritesItemState extends State<_ImageFavoritesItem> {
|
|||||||
initialEp: ep,
|
initialEp: ep,
|
||||||
initialPage: page,
|
initialPage: page,
|
||||||
),
|
),
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -243,9 +243,7 @@ class _ImageFavoritesPhotoViewState extends State<ImageFavoritesPhotoView> {
|
|||||||
sourceKey: comic.sourceKey,
|
sourceKey: comic.sourceKey,
|
||||||
initialEp: ep,
|
initialEp: ep,
|
||||||
initialPage: page,
|
initialPage: page,
|
||||||
),
|
)
|
||||||
enableIOSGesture: false,
|
|
||||||
iosFullScreenGesture: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
if (aggregatedSearch) {
|
if (aggregatedSearch) {
|
||||||
context
|
context
|
||||||
.to(
|
.to(
|
||||||
() => AggregatedSearchPage(keyword: text ?? controller.text),
|
() => AggregatedSearchPage(keyword: text ?? controller.text)
|
||||||
iosFullScreenGesture: false,
|
|
||||||
)
|
)
|
||||||
.then((_) => update());
|
.then((_) => update());
|
||||||
} else {
|
} else {
|
||||||
@@ -61,8 +60,7 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
text: text ?? controller.text,
|
text: text ?? controller.text,
|
||||||
sourceKey: searchTarget,
|
sourceKey: searchTarget,
|
||||||
options: options,
|
options: options,
|
||||||
),
|
)
|
||||||
iosFullScreenGesture: false,
|
|
||||||
)
|
)
|
||||||
.then((_) => update());
|
.then((_) => update());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
).toSliver(),
|
).toSliver(),
|
||||||
_CallbackSetting(
|
_CallbackSetting(
|
||||||
title: "Custom Image Processing".tl,
|
title: "Custom Image Processing".tl,
|
||||||
callback: () => context.to(() => _CustomImageProcessing(), iosFullScreenGesture: false),
|
callback: () => context.to(() => _CustomImageProcessing()),
|
||||||
actionTitle: "Edit".tl,
|
actionTitle: "Edit".tl,
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
_SliderSetting(
|
_SliderSetting(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
||||||
@@ -41,7 +40,7 @@ class SettingsPage extends StatefulWidget {
|
|||||||
State<SettingsPage> createState() => _SettingsPageState();
|
State<SettingsPage> createState() => _SettingsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
class _SettingsPageState extends State<SettingsPage> {
|
||||||
int currentPage = -1;
|
int currentPage = -1;
|
||||||
|
|
||||||
ColorScheme get colors => Theme.of(context).colorScheme;
|
ColorScheme get colors => Theme.of(context).colorScheme;
|
||||||
@@ -70,84 +69,14 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
Icons.bug_report,
|
Icons.bug_report,
|
||||||
];
|
];
|
||||||
|
|
||||||
double offset = 0;
|
|
||||||
|
|
||||||
late final HorizontalDragGestureRecognizer gestureRecognizer;
|
|
||||||
|
|
||||||
ModalRoute? _route;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
|
|
||||||
if (nextRoute != _route) {
|
|
||||||
_route?.unregisterPopEntry(this);
|
|
||||||
_route = nextRoute;
|
|
||||||
_route?.registerPopEntry(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
currentPage = widget.initialPage;
|
currentPage = widget.initialPage;
|
||||||
gestureRecognizer = HorizontalDragGestureRecognizer(debugOwner: this)
|
|
||||||
..onUpdate = ((details) => setState(() => offset += details.delta.dx))
|
|
||||||
..onEnd = (details) async {
|
|
||||||
if (details.velocity.pixelsPerSecond.dx.abs() > 1 &&
|
|
||||||
details.velocity.pixelsPerSecond.dx >= 0) {
|
|
||||||
setState(() {
|
|
||||||
Future.delayed(const Duration(milliseconds: 300), () => offset = 0);
|
|
||||||
currentPage = -1;
|
|
||||||
});
|
|
||||||
} else if (offset > MediaQuery.of(context).size.width / 2) {
|
|
||||||
setState(() {
|
|
||||||
Future.delayed(const Duration(milliseconds: 300), () => offset = 0);
|
|
||||||
currentPage = -1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
int i = 10;
|
|
||||||
while (offset != 0) {
|
|
||||||
setState(() {
|
|
||||||
offset -= i;
|
|
||||||
i *= 10;
|
|
||||||
if (offset < 0) {
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Future.delayed(const Duration(milliseconds: 10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
..onCancel = () async {
|
|
||||||
int i = 10;
|
|
||||||
while (offset != 0) {
|
|
||||||
setState(() {
|
|
||||||
offset -= i;
|
|
||||||
i *= 10;
|
|
||||||
if (offset < 0) {
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Future.delayed(const Duration(milliseconds: 10));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
dispose() {
|
|
||||||
super.dispose();
|
|
||||||
gestureRecognizer.dispose();
|
|
||||||
_route?.unregisterPopEntry(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (currentPage != -1) {
|
|
||||||
canPop.value = false;
|
|
||||||
} else {
|
|
||||||
canPop.value = true;
|
|
||||||
}
|
|
||||||
return Material(
|
return Material(
|
||||||
child: buildBody(),
|
child: buildBody(),
|
||||||
);
|
);
|
||||||
@@ -209,55 +138,10 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return LayoutBuilder(
|
return buildLeft();
|
||||||
builder: (context, constrains) {
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(child: buildLeft()),
|
|
||||||
Positioned(
|
|
||||||
left: offset,
|
|
||||||
width: constrains.maxWidth,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Listener(
|
|
||||||
onPointerDown: handlePointerDown,
|
|
||||||
child: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
switchInCurve: Curves.fastOutSlowIn,
|
|
||||||
switchOutCurve: Curves.fastOutSlowIn,
|
|
||||||
transitionBuilder: (child, animation) {
|
|
||||||
var tween = Tween<Offset>(
|
|
||||||
begin: const Offset(1, 0), end: const Offset(0, 0));
|
|
||||||
|
|
||||||
return SlideTransition(
|
|
||||||
position: tween.animate(animation),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Material(
|
|
||||||
key: ValueKey(currentPage),
|
|
||||||
child: buildRight(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePointerDown(PointerDownEvent event) {
|
|
||||||
if (!App.isIOS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentPage == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gestureRecognizer.addPointer(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildLeft() {
|
Widget buildLeft() {
|
||||||
return Material(
|
return Material(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -334,7 +218,13 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
? const EdgeInsets.fromLTRB(8, 0, 8, 0)
|
? const EdgeInsets.fromLTRB(8, 0, 8, 0)
|
||||||
: EdgeInsets.zero,
|
: EdgeInsets.zero,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => setState(() => currentPage = id),
|
onTap: () {
|
||||||
|
if (enableTwoViews) {
|
||||||
|
setState(() => currentPage = id);
|
||||||
|
} else {
|
||||||
|
context.to(() => _SettingsDetailPage(pageIndex: id));
|
||||||
|
}
|
||||||
|
},
|
||||||
child: content,
|
child: content,
|
||||||
).paddingVertical(4),
|
).paddingVertical(4),
|
||||||
);
|
);
|
||||||
@@ -348,8 +238,23 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildRight() {
|
Widget buildRight() {
|
||||||
return switch (currentPage) {
|
if (currentPage == -1) {
|
||||||
-1 => const SizedBox(),
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
return Navigator(
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
return PageRouteBuilder(
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
|
return _buildSettingsContent(currentPage);
|
||||||
|
},
|
||||||
|
transitionDuration: Duration.zero,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsContent(int pageIndex) {
|
||||||
|
return switch (pageIndex) {
|
||||||
0 => const ExploreSettings(),
|
0 => const ExploreSettings(),
|
||||||
1 => const ReaderSettings(),
|
1 => const ReaderSettings(),
|
||||||
2 => const AppearanceSettings(),
|
2 => const AppearanceSettings(),
|
||||||
@@ -362,26 +267,31 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var canPop = ValueNotifier(true);
|
}
|
||||||
|
|
||||||
|
class _SettingsDetailPage extends StatelessWidget {
|
||||||
|
const _SettingsDetailPage({required this.pageIndex});
|
||||||
|
|
||||||
|
final int pageIndex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ValueListenable<bool> get canPopNotifier => canPop;
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
@override
|
child: _buildPage(),
|
||||||
void onPopInvokedWithResult(bool didPop, result) {
|
);
|
||||||
if (currentPage != -1) {
|
|
||||||
setState(() {
|
|
||||||
currentPage = -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget _buildPage() {
|
||||||
void onPopInvoked(bool didPop) {
|
return switch (pageIndex) {
|
||||||
if (currentPage != -1) {
|
0 => const ExploreSettings(),
|
||||||
setState(() {
|
1 => const ReaderSettings(),
|
||||||
currentPage = -1;
|
2 => const AppearanceSettings(),
|
||||||
});
|
3 => const LocalFavoritesSettings(),
|
||||||
}
|
4 => const AppSettings(),
|
||||||
|
5 => const NetworkSettings(),
|
||||||
|
6 => const AboutSettings(),
|
||||||
|
7 => const DebugPage(),
|
||||||
|
_ => throw UnimplementedError()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user