feat: accept edge swipes across the whole margin beside the text column

This commit is contained in:
randogoth 2026-10-11 11:53:27 +03:00
parent 56f3d959fa
commit 6f9c0e587a
3 changed files with 70 additions and 9 deletions

View file

@ -21,6 +21,7 @@ class SwipeNavigator extends StatefulWidget {
this.forwardView,
this.backTitle,
this.forwardTitle,
this.contentWidth,
super.key,
});
@ -35,6 +36,11 @@ class SwipeNavigator extends StatefulWidget {
final String? backTitle;
final String? forwardTitle;
/// Width of the content column; swipes are accepted anywhere in the empty
/// margin beside it, plus a base zone at each screen edge. Null keeps only
/// the base edge zone.
final double? contentWidth;
final Color background;
final VoidCallback onBack;
final VoidCallback onForward;
@ -73,10 +79,16 @@ class _SwipeNavigatorState extends State<SwipeNavigator>
bool get _settledAnimating => _settling != null && _settle.isAnimating;
/// How close to a screen edge a drag must begin to navigate; horizontal
/// drags elsewhere belong to text selection.
/// Base zone at each screen edge that always swipes; horizontal drags
/// elsewhere belong to text selection.
static const _edgeZone = 40.0;
/// The swipe zone: the whole margin beside the content column, with the
/// base zone as the floor.
double _stripWidth(double width) => widget.contentWidth == null
? _edgeZone
: math.max(_edgeZone, (width - widget.contentWidth!) / 2);
void _onDragStart(DragStartDetails details) {
if (_settledAnimating) return;
setState(() => _dragging = true);
@ -172,23 +184,23 @@ class _SwipeNavigatorState extends State<SwipeNavigator>
offset: Offset(dx, 0),
child: widget.child,
),
// Only horizontal drags that begin at a screen edge navigate;
// the strips are translucent, so taps, vertical scrolling and
// text selection still pass through everywhere.
_edgeStrip(fromLeft: true),
_edgeStrip(fromLeft: false),
// Horizontal drags in the strips navigate; elsewhere they select
// text. The strips are translucent, so taps, vertical scrolling
// and mid-page selection still pass through everywhere.
_edgeStrip(fromLeft: true, width: _stripWidth(_width)),
_edgeStrip(fromLeft: false, width: _stripWidth(_width)),
],
);
},
);
}
Widget _edgeStrip({required bool fromLeft}) => Positioned(
Widget _edgeStrip({required bool fromLeft, required double width}) => Positioned(
left: fromLeft ? 0 : null,
right: fromLeft ? null : 0,
top: 0,
bottom: 0,
width: _edgeZone,
width: width,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onHorizontalDragStart: _onDragStart,