mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
chapter switching gesture
This commit is contained in:
@@ -12,14 +12,16 @@ class _ReaderGestureDetector extends StatefulWidget {
|
|||||||
class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
||||||
late TapGestureRecognizer _tapGestureRecognizer;
|
late TapGestureRecognizer _tapGestureRecognizer;
|
||||||
|
|
||||||
static const _kDoubleTapMinTime = Duration(milliseconds: 200);
|
static const _kDoubleTapMaxTime = Duration(milliseconds: 200);
|
||||||
|
|
||||||
static const _kLongPressMinTime = Duration(milliseconds: 200);
|
static const _kLongPressMinTime = Duration(milliseconds: 250);
|
||||||
|
|
||||||
static const _kDoubleTapMaxDistanceSquared = 20.0 * 20.0;
|
static const _kDoubleTapMaxDistanceSquared = 20.0 * 20.0;
|
||||||
|
|
||||||
static const _kTapToTurnPagePercent = 0.3;
|
static const _kTapToTurnPagePercent = 0.3;
|
||||||
|
|
||||||
|
_DragListener? dragListener;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_tapGestureRecognizer = TapGestureRecognizer()
|
_tapGestureRecognizer = TapGestureRecognizer()
|
||||||
@@ -28,6 +30,7 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
onSecondaryTapUp(details.globalPosition);
|
onSecondaryTapUp(details.globalPosition);
|
||||||
};
|
};
|
||||||
super.initState();
|
super.initState();
|
||||||
|
context.readerScaffold._gestureDetectorState = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -38,11 +41,20 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
_lastTapPointer = event.pointer;
|
_lastTapPointer = event.pointer;
|
||||||
_lastTapMoveDistance = Offset.zero;
|
_lastTapMoveDistance = Offset.zero;
|
||||||
_tapGestureRecognizer.addPointer(event);
|
_tapGestureRecognizer.addPointer(event);
|
||||||
|
if(_dragInProgress) {
|
||||||
|
dragListener?.onEnd?.call();
|
||||||
|
_dragInProgress = false;
|
||||||
|
}
|
||||||
Future.delayed(_kLongPressMinTime, () {
|
Future.delayed(_kLongPressMinTime, () {
|
||||||
if (_lastTapPointer == event.pointer &&
|
if (_lastTapPointer == event.pointer) {
|
||||||
_lastTapMoveDistance!.distanceSquared < 20.0 * 20.0) {
|
if(_lastTapMoveDistance!.distanceSquared < 20.0 * 20.0) {
|
||||||
onLongPressedDown(event.position);
|
onLongPressedDown(event.position);
|
||||||
_longPressInProgress = true;
|
_longPressInProgress = true;
|
||||||
|
} else {
|
||||||
|
_dragInProgress = true;
|
||||||
|
dragListener?.onStart?.call(event.position);
|
||||||
|
dragListener?.onMove?.call(_lastTapMoveDistance!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -50,11 +62,18 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
if (event.pointer == _lastTapPointer) {
|
if (event.pointer == _lastTapPointer) {
|
||||||
_lastTapMoveDistance = event.delta + _lastTapMoveDistance!;
|
_lastTapMoveDistance = event.delta + _lastTapMoveDistance!;
|
||||||
}
|
}
|
||||||
|
if(_dragInProgress) {
|
||||||
|
dragListener?.onMove?.call(event.delta);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onPointerUp: (event) {
|
onPointerUp: (event) {
|
||||||
if (_longPressInProgress) {
|
if (_longPressInProgress) {
|
||||||
onLongPressedUp(event.position);
|
onLongPressedUp(event.position);
|
||||||
}
|
}
|
||||||
|
if(_dragInProgress) {
|
||||||
|
dragListener?.onEnd?.call();
|
||||||
|
_dragInProgress = false;
|
||||||
|
}
|
||||||
_lastTapPointer = null;
|
_lastTapPointer = null;
|
||||||
_lastTapMoveDistance = null;
|
_lastTapMoveDistance = null;
|
||||||
},
|
},
|
||||||
@@ -89,6 +108,8 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
|
|
||||||
bool _longPressInProgress = false;
|
bool _longPressInProgress = false;
|
||||||
|
|
||||||
|
bool _dragInProgress = false;
|
||||||
|
|
||||||
void onTapUp(TapUpDetails event) {
|
void onTapUp(TapUpDetails event) {
|
||||||
if (_longPressInProgress) {
|
if (_longPressInProgress) {
|
||||||
_longPressInProgress = false;
|
_longPressInProgress = false;
|
||||||
@@ -107,7 +128,7 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_previousEvent = event;
|
_previousEvent = event;
|
||||||
Future.delayed(_kDoubleTapMinTime, () {
|
Future.delayed(_kDoubleTapMaxTime, () {
|
||||||
if (_previousEvent == event) {
|
if (_previousEvent == event) {
|
||||||
onTap(location);
|
onTap(location);
|
||||||
_previousEvent = null;
|
_previousEvent = null;
|
||||||
@@ -222,3 +243,11 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
context.reader._imageViewController?.handleLongPressDown(location);
|
context.reader._imageViewController?.handleLongPressDown(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DragListener {
|
||||||
|
void Function(Offset point)? onStart;
|
||||||
|
void Function(Offset offset)? onMove;
|
||||||
|
void Function()? onEnd;
|
||||||
|
|
||||||
|
_DragListener({this.onStart, this.onMove, this.onEnd});
|
||||||
|
}
|
@@ -2,6 +2,7 @@ library venera_reader;
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
@@ -22,22 +22,66 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
|
|
||||||
var lastValue = 0;
|
var lastValue = 0;
|
||||||
|
|
||||||
double fABValue = 0;
|
var fABValue = ValueNotifier<double>(0);
|
||||||
|
|
||||||
|
_ReaderGestureDetectorState? _gestureDetectorState;
|
||||||
|
|
||||||
void setFloatingButton(int value) {
|
void setFloatingButton(int value) {
|
||||||
lastValue = showFloatingButtonValue;
|
lastValue = showFloatingButtonValue;
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
if (showFloatingButtonValue != 0) {
|
if (showFloatingButtonValue != 0) {
|
||||||
showFloatingButtonValue = 0;
|
showFloatingButtonValue = 0;
|
||||||
fABValue = 0;
|
fABValue.value = 0;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
_gestureDetectorState!.dragListener = null;
|
||||||
}
|
}
|
||||||
|
var readerMode = context.reader.mode;
|
||||||
if (value == 1 && showFloatingButtonValue == 0) {
|
if (value == 1 && showFloatingButtonValue == 0) {
|
||||||
showFloatingButtonValue = 1;
|
showFloatingButtonValue = 1;
|
||||||
|
_gestureDetectorState!.dragListener = _DragListener(
|
||||||
|
onMove: (offset) {
|
||||||
|
if (readerMode == ReaderMode.continuousTopToBottom) {
|
||||||
|
fABValue.value -= offset.dy;
|
||||||
|
} else if (readerMode == ReaderMode.continuousLeftToRight) {
|
||||||
|
fABValue.value -= offset.dx;
|
||||||
|
} else if (readerMode == ReaderMode.continuousRightToLeft) {
|
||||||
|
fABValue.value += offset.dx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEnd: () {
|
||||||
|
if (fABValue.value.abs() > 58 * 3) {
|
||||||
|
setState(() {
|
||||||
|
showFloatingButtonValue = 0;
|
||||||
|
});
|
||||||
|
context.reader.toNextChapter();
|
||||||
|
}
|
||||||
|
fABValue.value = 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
update();
|
update();
|
||||||
} else if (value == -1 && showFloatingButtonValue == 0) {
|
} else if (value == -1 && showFloatingButtonValue == 0) {
|
||||||
showFloatingButtonValue = -1;
|
showFloatingButtonValue = -1;
|
||||||
|
_gestureDetectorState!.dragListener = _DragListener(
|
||||||
|
onMove: (offset) {
|
||||||
|
if (readerMode == ReaderMode.continuousTopToBottom) {
|
||||||
|
fABValue.value += offset.dy;
|
||||||
|
} else if (readerMode == ReaderMode.continuousLeftToRight) {
|
||||||
|
fABValue.value += offset.dx;
|
||||||
|
} else if (readerMode == ReaderMode.continuousRightToLeft) {
|
||||||
|
fABValue.value -= offset.dx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEnd: () {
|
||||||
|
if (fABValue.value.abs() > 58 * 3) {
|
||||||
|
setState(() {
|
||||||
|
showFloatingButtonValue = 0;
|
||||||
|
});
|
||||||
|
context.reader.toPrevChapter();
|
||||||
|
}
|
||||||
|
fABValue.value = 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +94,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
sliderFocus.nextFocus();
|
sliderFocus.nextFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(rotation != null) {
|
if (rotation != null) {
|
||||||
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
|
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -167,8 +211,14 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
IconButton.filledTonal(
|
IconButton.filledTonal(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if(!context.reader.toPrevChapter()) {
|
if (!context.reader.toPrevChapter()) {
|
||||||
context.reader.toPage(1);
|
context.reader.toPage(1);
|
||||||
|
} else {
|
||||||
|
if(showFloatingButtonValue != 0) {
|
||||||
|
setState(() {
|
||||||
|
showFloatingButtonValue = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.first_page),
|
icon: const Icon(Icons.first_page),
|
||||||
@@ -178,8 +228,14 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
),
|
),
|
||||||
IconButton.filledTonal(
|
IconButton.filledTonal(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if(!context.reader.toNextChapter()) {
|
if (!context.reader.toNextChapter()) {
|
||||||
context.reader.toPage(context.reader.maxPage);
|
context.reader.toPage(context.reader.maxPage);
|
||||||
|
} else {
|
||||||
|
if(showFloatingButtonValue != 0) {
|
||||||
|
setState(() {
|
||||||
|
showFloatingButtonValue = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.last_page)),
|
icon: const Icon(Icons.last_page)),
|
||||||
@@ -373,8 +429,8 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
if (imageKey.startsWith("file://")) {
|
if (imageKey.startsWith("file://")) {
|
||||||
return await File(imageKey.substring(7)).readAsBytes();
|
return await File(imageKey.substring(7)).readAsBytes();
|
||||||
} else {
|
} else {
|
||||||
return (await CacheManager()
|
return (await CacheManager().findCache(
|
||||||
.findCache("$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
"$imageKey@${context.reader.type.sourceKey}@${context.reader.cid}@${context.reader.eid}"))!
|
||||||
.readAsBytes();
|
.readAsBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,14 +472,6 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
Widget buildEpChangeButton() {
|
Widget buildEpChangeButton() {
|
||||||
if (context.reader.widget.chapters == null) return const SizedBox();
|
if (context.reader.widget.chapters == null) return const SizedBox();
|
||||||
switch (showFloatingButtonValue) {
|
switch (showFloatingButtonValue) {
|
||||||
case -1:
|
|
||||||
return FloatingActionButton(
|
|
||||||
onPressed: () {
|
|
||||||
setFloatingButton(0);
|
|
||||||
context.reader.toPrevChapter();
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.arrow_back_ios_outlined),
|
|
||||||
);
|
|
||||||
case 0:
|
case 0:
|
||||||
return Container(
|
return Container(
|
||||||
width: 58,
|
width: 58,
|
||||||
@@ -441,6 +489,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
case -1:
|
||||||
case 1:
|
case 1:
|
||||||
return Container(
|
return Container(
|
||||||
width: 58,
|
width: 58,
|
||||||
@@ -450,7 +499,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: fABValue,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Material(
|
child: Material(
|
||||||
@@ -458,15 +510,24 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setFloatingButton(0);
|
setFloatingButton(0);
|
||||||
|
if (showFloatingButtonValue == 1) {
|
||||||
context.reader.toNextChapter();
|
context.reader.toNextChapter();
|
||||||
|
} else {
|
||||||
|
context.reader.toPrevChapter();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.arrow_forward_ios,
|
showFloatingButtonValue == 1
|
||||||
|
? Icons.arrow_forward_ios
|
||||||
|
: Icons.arrow_back_ios_outlined,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context)
|
||||||
)),
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -474,7 +535,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
height: fABValue,
|
height: value.clamp(0, 58*3) / 3,
|
||||||
child: ColoredBox(
|
child: ColoredBox(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
@@ -482,8 +543,10 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
.withOpacity(0.2),
|
.withOpacity(0.2),
|
||||||
child: const SizedBox.expand(),
|
child: const SizedBox.expand(),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user