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:
RuriNyan
2025-11-29 14:18:44 +08:00
committed by GitHub
parent b3239757a8
commit 7e928d2c9c
15 changed files with 271 additions and 295 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -153,9 +153,7 @@ class LocalComic with HistoryMixin implements Comic {
), ),
author: subtitle, author: subtitle,
tags: tags, tags: tags,
), )
enableIOSGesture: false,
iosFullScreenGesture: false,
); );
} }

View File

@@ -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(

View File

@@ -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();

View File

@@ -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;

View File

@@ -200,7 +200,6 @@ class _BodyState extends State<_Body> {
await ComicSourceManager().reload(); await ComicSourceManager().reload();
setState(() {}); setState(() {});
}), }),
iosFullScreenGesture: false,
); );
} }

View File

@@ -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(

View File

@@ -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,
); );
}, },
); );

View File

@@ -37,8 +37,6 @@ class _ImageFavoritesItemState extends State<_ImageFavoritesItem> {
initialEp: ep, initialEp: ep,
initialPage: page, initialPage: page,
), ),
enableIOSGesture: false,
iosFullScreenGesture: false,
); );
} }

View File

@@ -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,
); );
}, },
), ),

View File

@@ -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());
} }

View File

@@ -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(

View File

@@ -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()
};
} }
} }