diff --git a/lib/foundation/app_page_route.dart b/lib/foundation/app_page_route.dart index bbb8135..158ced5 100644 --- a/lib/foundation/app_page_route.dart +++ b/lib/foundation/app_page_route.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'dart:ui'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:venera/foundation/app.dart'; const double _kBackGestureWidth = 20.0; @@ -19,6 +20,7 @@ class AppPageRoute extends PageRoute with _AppRouteTransitionMixin{ super.allowSnapshotting = true, super.barrierDismissible = false, this.enableIOSGesture = true, + this.iosFullScreenPopGesture = true, this.preventRebuild = true, }) { assert(opaque); @@ -48,6 +50,9 @@ class AppPageRoute extends PageRoute with _AppRouteTransitionMixin{ @override final bool enableIOSGesture; + @override + final bool iosFullScreenPopGesture; + @override final bool preventRebuild; } @@ -74,6 +79,8 @@ mixin _AppRouteTransitionMixin on PageRoute { bool get enableIOSGesture; + bool get iosFullScreenPopGesture; + bool get preventRebuild; Widget? _child; @@ -121,20 +128,22 @@ mixin _AppRouteTransitionMixin on PageRoute { builder = PredictiveBackPageTransitionsBuilder(); } else { builder = SlidePageTransitionBuilder(); - } + } - return builder.buildTransitions( + return builder.buildTransitions( this, context, animation, secondaryAnimation, - enableIOSGesture && App.isIOS - ? IOSBackGestureDetector( - gestureWidth: _kBackGestureWidth, - enabledCallback: () => _isPopGestureEnabled(this), - onStartPopGesture: () => _startPopGesture(this), - child: child) - : child); + enableIOSGesture && App.isIOS + ? IOSBackGestureDetector( + gestureWidth: _kBackGestureWidth, + enabledCallback: () => _isPopGestureEnabled(this), + onStartPopGesture: () => _startPopGesture(this), + fullScreen: iosFullScreenPopGesture, + child: child, + ) + : child); } IOSBackGestureController _startPopGesture(PageRoute route) { @@ -206,6 +215,7 @@ class IOSBackGestureDetector extends StatefulWidget { required this.child, required this.gestureWidth, required this.onStartPopGesture, + this.fullScreen = false, super.key}); final double gestureWidth; @@ -216,6 +226,8 @@ class IOSBackGestureDetector extends StatefulWidget { final Widget child; + final bool fullScreen; + @override State createState() => _IOSBackGestureDetectorState(); } @@ -247,26 +259,40 @@ class _IOSBackGestureDetectorState extends State { ? MediaQuery.of(context).padding.left : MediaQuery.of(context).padding.right; 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( fit: StackFit.passthrough, children: [ widget.child, - Positioned( - width: dragAreaWidth, - top: 0.0, - bottom: 0.0, - left: 0, - child: Listener( - onPointerDown: _handlePointerDown, - behavior: HitTestBehavior.translucent, - ), - ), + gestureListener, ], ); } void _handlePointerDown(PointerDownEvent event) { - if (widget.enabledCallback()) _recognizer.addPointer(event); + if (!widget.enabledCallback()) return; + if (widget.fullScreen && _isPointerOverHorizontalScrollable(event)) { + return; + } + _recognizer.addPointer(event); } void _handleDragCancel() { @@ -304,6 +330,28 @@ class _IOSBackGestureDetectorState extends State { _backGestureController!.dragUpdate( _convertToLogical(details.primaryDelta! / context.size!.width)); } + + bool _isPointerOverHorizontalScrollable(PointerDownEvent event) { + final HitTestResult result = HitTestResult(); + WidgetsBinding.instance.hitTest(result, event.position); + for (final entry in result.path) { + final target = entry.target; + if (target is RenderViewport) { + if (_isAxisHorizontal(target.axisDirection)) { + return true; + } + } else if (target is RenderSliver) { + if (_isAxisHorizontal(target.constraints.axisDirection)) { + return true; + } + } + } + return false; + } + + bool _isAxisHorizontal(AxisDirection direction) { + return direction == AxisDirection.left || direction == AxisDirection.right; + } } class SlidePageTransitionBuilder extends PageTransitionsBuilder { @@ -314,30 +362,31 @@ class SlidePageTransitionBuilder extends PageTransitionsBuilder { Animation animation, Animation secondaryAnimation, Widget child) { + final Animation primaryAnimation = App.isIOS + ? animation + : CurvedAnimation(parent: animation, curve: Curves.ease); + final Animation secondaryCurve = App.isIOS + ? secondaryAnimation + : CurvedAnimation(parent: secondaryAnimation, curve: Curves.ease); + return SlideTransition( + position: Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(primaryAnimation), + child: SlideTransition( position: Tween( - begin: const Offset(1, 0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.ease, - )), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - 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,), - ), - ) + begin: Offset.zero, + end: const Offset(-0.4, 0), + ).animate(secondaryCurve), + child: PhysicalModel( + color: Colors.transparent, + borderRadius: BorderRadius.zero, + clipBehavior: Clip.hardEdge, + elevation: 6, + child: Material(child: child), + ), + ), ); } } diff --git a/lib/foundation/comic_source/models.dart b/lib/foundation/comic_source/models.dart index 52cf276..c430090 100644 --- a/lib/foundation/comic_source/models.dart +++ b/lib/foundation/comic_source/models.dart @@ -542,6 +542,7 @@ class PageJumpTarget { sourceKey: sourceKey, options: List.from(attributes?["options"] ?? []), ), + iosFullScreenGesture: false, ); } else if (page == "category") { var key = ComicSource.find(sourceKey)!.categoryData!.key; diff --git a/lib/foundation/context.dart b/lib/foundation/context.dart index b8b1a58..4fd3198 100644 --- a/lib/foundation/context.dart +++ b/lib/foundation/context.dart @@ -14,14 +14,20 @@ extension Navigation on BuildContext { return Navigator.of(this).canPop(); } - Future to(Widget Function() builder) { - return Navigator.of(this) - .push(AppPageRoute(builder: (context) => builder())); + Future to(Widget Function() builder, + {bool enableIOSGesture = true, bool iosFullScreenGesture = true}) { + return Navigator.of(this).push(AppPageRoute( + builder: (context) => builder(), + enableIOSGesture: enableIOSGesture, + iosFullScreenPopGesture: iosFullScreenGesture)); } - Future toReplacement(Widget Function() builder) { - return Navigator.of(this) - .pushReplacement(AppPageRoute(builder: (context) => builder())); + Future toReplacement(Widget Function() builder, + {bool enableIOSGesture = true, bool iosFullScreenGesture = true}) { + return Navigator.of(this).pushReplacement(AppPageRoute( + builder: (context) => builder(), + enableIOSGesture: enableIOSGesture, + iosFullScreenPopGesture: iosFullScreenGesture)); } double get width => MediaQuery.of(this).size.width; diff --git a/lib/foundation/local.dart b/lib/foundation/local.dart index 1d21c61..ce4b2b0 100644 --- a/lib/foundation/local.dart +++ b/lib/foundation/local.dart @@ -154,6 +154,8 @@ class LocalComic with HistoryMixin implements Comic { author: subtitle, tags: tags, ), + enableIOSGesture: false, + iosFullScreenGesture: false, ); } diff --git a/lib/network/images.dart b/lib/network/images.dart index 8953c61..8f311d7 100644 --- a/lib/network/images.dart +++ b/lib/network/images.dart @@ -52,7 +52,11 @@ abstract class ImageDownloader { responseType: ResponseType.stream, )); - var req = await dio.request(configs['url'] ?? url, + String requestUrl = configs['url'] ?? url; + if (requestUrl.startsWith('//')) { + requestUrl = 'https:$requestUrl'; + } + var req = await dio.request(requestUrl, data: configs['data']); var stream = req.data?.stream ?? (throw "Error: Empty response body."); int? expectedBytes = req.data!.contentLength; diff --git a/lib/pages/aggregated_search_page.dart b/lib/pages/aggregated_search_page.dart index 75f66e2..a5b2db2 100644 --- a/lib/pages/aggregated_search_page.dart +++ b/lib/pages/aggregated_search_page.dart @@ -170,6 +170,7 @@ class _SliverSearchResultState extends State<_SliverSearchResult> text: widget.keyword, sourceKey: widget.source.key, ), + iosFullScreenGesture: false, ); }, child: Column( diff --git a/lib/pages/comic_details_page/actions.dart b/lib/pages/comic_details_page/actions.dart index 4360e08..ad59ebb 100644 --- a/lib/pages/comic_details_page/actions.dart +++ b/lib/pages/comic_details_page/actions.dart @@ -116,6 +116,8 @@ abstract mixin class _ComicPageActions { author: comic.findAuthor() ?? '', tags: comic.plainTags, ), + enableIOSGesture: false, + iosFullScreenGesture: false, ) .then((_) { onReadEnd(); diff --git a/lib/pages/comic_details_page/comic_page.dart b/lib/pages/comic_details_page/comic_page.dart index 67708ba..2a453eb 100644 --- a/lib/pages/comic_details_page/comic_page.dart +++ b/lib/pages/comic_details_page/comic_page.dart @@ -236,7 +236,7 @@ class _ComicPageState extends LoadingState author: localComic.subTitle ?? '', tags: localComic.tags, ); - }); + }, enableIOSGesture: false, iosFullScreenGesture: false); App.mainNavigatorKey!.currentContext!.pop(); }); isFirst = false; diff --git a/lib/pages/favorites/local_favorites_page.dart b/lib/pages/favorites/local_favorites_page.dart index 65ab729..b73f7f7 100644 --- a/lib/pages/favorites/local_favorites_page.dart +++ b/lib/pages/favorites/local_favorites_page.dart @@ -563,7 +563,10 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { App.rootContext.to(() => ReaderWithLoading( id: c.id, sourceKey: c.sourceKey, - )); + ), + enableIOSGesture: false, + iosFullScreenGesture: false, + ); }, ), if (selectedComics.length == 1) @@ -575,7 +578,10 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { App.mainNavigatorKey?.currentContext?.to(() => ComicPage( id: c.id, sourceKey: c.sourceKey, - )); + ), + enableIOSGesture: false, + iosFullScreenGesture: false, + ); }, ), ]), @@ -676,6 +682,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { id: c.id, sourceKey: c.sourceKey, ), + enableIOSGesture: false, + iosFullScreenGesture: false, ); }, ), @@ -701,6 +709,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { id: c.id, sourceKey: c.sourceKey, ), + enableIOSGesture: false, + iosFullScreenGesture: false, ); } }, diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index c28c1bb..4780e60 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -874,7 +874,10 @@ class _ImageFavoritesState extends State { child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () { - context.to(() => const ImageFavoritesPage()); + context.to( + () => const ImageFavoritesPage(), + iosFullScreenGesture: false, + ); }, child: Column( mainAxisSize: MainAxisSize.min, @@ -993,7 +996,10 @@ class _ImageFavoritesState extends State { maxCount: maxCount, enableTranslation: displayType != 2, onTap: (text) { - context.to(() => ImageFavoritesPage(initialKeyword: text)); + context.to( + () => ImageFavoritesPage(initialKeyword: text), + iosFullScreenGesture: false, + ); }, ); }).toList(), diff --git a/lib/pages/image_favorites_page/image_favorites_item.dart b/lib/pages/image_favorites_page/image_favorites_item.dart index 120332b..a33e474 100644 --- a/lib/pages/image_favorites_page/image_favorites_item.dart +++ b/lib/pages/image_favorites_page/image_favorites_item.dart @@ -37,6 +37,8 @@ class _ImageFavoritesItemState extends State<_ImageFavoritesItem> { initialEp: ep, initialPage: page, ), + enableIOSGesture: false, + iosFullScreenGesture: false, ); } diff --git a/lib/pages/image_favorites_page/image_favorites_photo_view.dart b/lib/pages/image_favorites_page/image_favorites_photo_view.dart index 51768e6..646857e 100644 --- a/lib/pages/image_favorites_page/image_favorites_photo_view.dart +++ b/lib/pages/image_favorites_page/image_favorites_photo_view.dart @@ -244,6 +244,8 @@ class _ImageFavoritesPhotoViewState extends State { initialEp: ep, initialPage: page, ), + enableIOSGesture: false, + iosFullScreenGesture: false, ); }, ), diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart index 934446c..0a38336 100644 --- a/lib/pages/search_page.dart +++ b/lib/pages/search_page.dart @@ -49,7 +49,10 @@ class _SearchPageState extends State { void search([String? text]) { if (aggregatedSearch) { context - .to(() => AggregatedSearchPage(keyword: text ?? controller.text)) + .to( + () => AggregatedSearchPage(keyword: text ?? controller.text), + iosFullScreenGesture: false, + ) .then((_) => update()); } else { context @@ -59,6 +62,7 @@ class _SearchPageState extends State { sourceKey: searchTarget, options: options, ), + iosFullScreenGesture: false, ) .then((_) => update()); } diff --git a/lib/pages/settings/settings_page.dart b/lib/pages/settings/settings_page.dart index 6adbfca..75884f0 100644 --- a/lib/pages/settings/settings_page.dart +++ b/lib/pages/settings/settings_page.dart @@ -252,9 +252,10 @@ class _SettingsPageState extends State implements PopEntry { if (!App.isIOS) { return; } - if (event.position.dx < 20) { - gestureRecognizer.addPointer(event); + if (currentPage == -1) { + return; } + gestureRecognizer.addPointer(event); } Widget buildLeft() {