[iOS] Enable full screen swipe back gesture

This commit is contained in:
LiuliFox
2025-10-20 10:08:25 +08:00
parent 09a1d2821c
commit 3d194d7f6a
9 changed files with 94 additions and 51 deletions

View File

@@ -19,6 +19,7 @@ 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);
@@ -48,6 +49,9 @@ 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;
} }
@@ -74,6 +78,8 @@ 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;
@@ -121,20 +127,22 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
builder = PredictiveBackPageTransitionsBuilder(); builder = PredictiveBackPageTransitionsBuilder();
} else { } else {
builder = SlidePageTransitionBuilder(); builder = SlidePageTransitionBuilder();
} }
return builder.buildTransitions( return builder.buildTransitions(
this, this,
context, context,
animation, animation,
secondaryAnimation, secondaryAnimation,
enableIOSGesture && App.isIOS enableIOSGesture && App.isIOS
? IOSBackGestureDetector( ? IOSBackGestureDetector(
gestureWidth: _kBackGestureWidth, gestureWidth: _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(this), enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture(this), onStartPopGesture: () => _startPopGesture(this),
child: child) fullScreen: iosFullScreenPopGesture,
: child); child: child,
)
: child);
} }
IOSBackGestureController _startPopGesture(PageRoute<T> route) { IOSBackGestureController _startPopGesture(PageRoute<T> route) {
@@ -206,6 +214,7 @@ class IOSBackGestureDetector extends StatefulWidget {
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;
@@ -216,6 +225,8 @@ class IOSBackGestureDetector extends StatefulWidget {
final Widget child; final Widget child;
final bool fullScreen;
@override @override
State<IOSBackGestureDetector> createState() => _IOSBackGestureDetectorState(); State<IOSBackGestureDetector> createState() => _IOSBackGestureDetectorState();
} }
@@ -247,20 +258,30 @@ class _IOSBackGestureDetectorState extends State<IOSBackGestureDetector> {
? MediaQuery.of(context).padding.left ? MediaQuery.of(context).padding.left
: MediaQuery.of(context).padding.right; : MediaQuery.of(context).padding.right;
dragAreaWidth = max(dragAreaWidth, widget.gestureWidth); dragAreaWidth = max(dragAreaWidth, widget.gestureWidth);
final Widget gestureListener = widget.fullScreen
? Positioned.fill(
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
)
: 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( return Stack(
fit: StackFit.passthrough, fit: StackFit.passthrough,
children: <Widget>[ children: <Widget>[
widget.child, widget.child,
Positioned( gestureListener,
width: dragAreaWidth,
top: 0.0,
bottom: 0.0,
left: 0,
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
),
], ],
); );
} }
@@ -314,30 +335,31 @@ class SlidePageTransitionBuilder extends PageTransitionsBuilder {
Animation<double> animation, Animation<double> animation,
Animation<double> secondaryAnimation, Animation<double> secondaryAnimation,
Widget child) { Widget child) {
final Animation<double> primaryAnimation = App.isIOS
? animation
: CurvedAnimation(parent: animation, curve: Curves.ease);
final Animation<double> secondaryCurve = App.isIOS
? secondaryAnimation
: CurvedAnimation(parent: secondaryAnimation, curve: Curves.ease);
return SlideTransition( return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(primaryAnimation),
child: SlideTransition(
position: Tween<Offset>( position: Tween<Offset>(
begin: const Offset(1, 0), begin: Offset.zero,
end: Offset.zero, end: const Offset(-0.4, 0),
).animate(CurvedAnimation( ).animate(secondaryCurve),
parent: animation, child: PhysicalModel(
curve: Curves.ease, color: Colors.transparent,
)), borderRadius: BorderRadius.zero,
child: SlideTransition( clipBehavior: Clip.hardEdge,
position: Tween<Offset>( elevation: 6,
begin: Offset.zero, child: Material(child: child),
end: const Offset(-0.4, 0), ),
).animate(CurvedAnimation( ),
parent: secondaryAnimation,
curve: Curves.ease,
)),
child: PhysicalModel(
color: Colors.transparent,
borderRadius: BorderRadius.zero,
clipBehavior: Clip.hardEdge,
elevation: 6,
child: Material(child: child,),
),
)
); );
} }
} }

View File

@@ -14,14 +14,20 @@ 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,
return Navigator.of(this) {bool enableIOSGesture = true, bool iosFullScreenGesture = true}) {
.push<T>(AppPageRoute(builder: (context) => builder())); return Navigator.of(this).push<T>(AppPageRoute(
builder: (context) => builder(),
enableIOSGesture: enableIOSGesture,
iosFullScreenPopGesture: iosFullScreenGesture));
} }
Future<void> toReplacement<T>(Widget Function() builder) { Future<void> toReplacement<T>(Widget Function() builder,
return Navigator.of(this) {bool enableIOSGesture = true, bool iosFullScreenGesture = true}) {
.pushReplacement(AppPageRoute(builder: (context) => builder())); return Navigator.of(this).pushReplacement(AppPageRoute(
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

@@ -154,6 +154,8 @@ class LocalComic with HistoryMixin implements Comic {
author: subtitle, author: subtitle,
tags: tags, tags: tags,
), ),
enableIOSGesture: false,
iosFullScreenGesture: false,
); );
} }

View File

@@ -116,6 +116,8 @@ abstract mixin class _ComicPageActions {
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

@@ -521,7 +521,9 @@ 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);
}, },
), ),
]), ]),
@@ -622,6 +624,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
id: c.id, id: c.id,
sourceKey: c.sourceKey, sourceKey: c.sourceKey,
), ),
enableIOSGesture: false,
iosFullScreenGesture: false,
); );
}, },
), ),
@@ -647,6 +651,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
id: c.id, id: c.id,
sourceKey: c.sourceKey, sourceKey: c.sourceKey,
), ),
enableIOSGesture: false,
iosFullScreenGesture: false,
); );
} }
}, },

View File

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

View File

@@ -244,6 +244,8 @@ class _ImageFavoritesPhotoViewState extends State<ImageFavoritesPhotoView> {
initialEp: ep, initialEp: ep,
initialPage: page, initialPage: page,
), ),
enableIOSGesture: false,
iosFullScreenGesture: false,
); );
}, },
), ),

View File

@@ -252,9 +252,10 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
if (!App.isIOS) { if (!App.isIOS) {
return; return;
} }
if (event.position.dx < 20) { if (currentPage == -1) {
gestureRecognizer.addPointer(event); return;
} }
gestureRecognizer.addPointer(event);
} }
Widget buildLeft() { Widget buildLeft() {