generated from dellevin/template
原生epub阅读
This commit is contained in:
169
lib/pages/epub_reader/page_turn/android_page_turn_session.dart
Normal file
169
lib/pages/epub_reader/page_turn/android_page_turn_session.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../reader_webview.dart';
|
||||
|
||||
class AndroidPageTurnSession {
|
||||
late final AnimationController _animController;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
ui.Image? _screenshotData;
|
||||
bool _isAnimating = false;
|
||||
bool _isForwardAnimation = true;
|
||||
int _pageTurnToken = 0;
|
||||
|
||||
AndroidPageTurnSession({
|
||||
required TickerProvider vsync,
|
||||
required Duration duration,
|
||||
}) {
|
||||
_animController = AnimationController(vsync: vsync, duration: duration);
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: Offset.zero,
|
||||
).animate(_animController);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_screenshotData?.dispose();
|
||||
_animController.dispose();
|
||||
}
|
||||
|
||||
void _setupTween(bool isNext, bool isVertical) {
|
||||
Tween<Offset> tween;
|
||||
if (isNext) {
|
||||
tween = Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: Offset(isVertical ? 1.0 : -1.0, 0.0),
|
||||
);
|
||||
} else {
|
||||
tween = Tween<Offset>(
|
||||
begin: Offset(isVertical ? 1.0 : -1.0, 0.0),
|
||||
end: Offset.zero,
|
||||
);
|
||||
}
|
||||
_slideAnimation = CurvedAnimation(
|
||||
parent: _animController,
|
||||
curve: Curves.easeOut,
|
||||
).drive(tween);
|
||||
}
|
||||
|
||||
Future<ui.Image?> _takeScreenshot(
|
||||
ReaderWebViewController webViewController,
|
||||
) async {
|
||||
ui.Image? screenshot;
|
||||
try {
|
||||
screenshot = await webViewController.takeScreenshot();
|
||||
} catch (e) {
|
||||
debugPrint('Error taking screenshot: $e');
|
||||
screenshot = null;
|
||||
}
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
Future<void> perform({
|
||||
required ReaderWebViewController webViewController,
|
||||
required bool needAnimation,
|
||||
required bool isNext,
|
||||
required bool isVertical,
|
||||
required Future<void> Function(bool) onPerformPageTurn,
|
||||
required void Function(VoidCallback) setState,
|
||||
required bool Function() isMounted,
|
||||
}) async {
|
||||
if (!needAnimation) {
|
||||
await onPerformPageTurn(isNext);
|
||||
return;
|
||||
}
|
||||
|
||||
final int turnToken = ++_pageTurnToken;
|
||||
|
||||
final screenshot = await _takeScreenshot(webViewController);
|
||||
|
||||
// If screenshot fails, just perform the page turn without animation
|
||||
if (screenshot == null) {
|
||||
_screenshotData?.dispose();
|
||||
_screenshotData = null;
|
||||
_animController.reset();
|
||||
await onPerformPageTurn(isNext);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the widget has been unmounted or a new page turn has started, dispose the screenshot and exit
|
||||
if (!isMounted() || turnToken != _pageTurnToken) {
|
||||
screenshot.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isForwardAnimation = isNext;
|
||||
_screenshotData?.dispose();
|
||||
_screenshotData = screenshot;
|
||||
_isAnimating = true;
|
||||
|
||||
_setupTween(isNext, isVertical);
|
||||
_animController.reset();
|
||||
});
|
||||
|
||||
// Perform the page turn in parallel with the animation
|
||||
await onPerformPageTurn(isNext);
|
||||
|
||||
// Start the animation
|
||||
if (!isMounted() || turnToken != _pageTurnToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _animController.forward();
|
||||
} finally {
|
||||
if (turnToken == _pageTurnToken) {
|
||||
final finishedScreenshot = _screenshotData;
|
||||
if (isMounted()) {
|
||||
setState(() {
|
||||
_screenshotData = null;
|
||||
_isAnimating = false;
|
||||
});
|
||||
} else {
|
||||
_screenshotData = null;
|
||||
_isAnimating = false;
|
||||
}
|
||||
finishedScreenshot?.dispose();
|
||||
|
||||
_animController.reset();
|
||||
_isAnimating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildAnimatedContainer(
|
||||
BuildContext context,
|
||||
Widget child,
|
||||
Widget Function(ui.Image?) buildScreenshotContainer,
|
||||
) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Backward animation: show the current page as the background
|
||||
if (_isAnimating && !_isForwardAnimation)
|
||||
Positioned.fill(child: buildScreenshotContainer(_screenshotData)),
|
||||
// Webview page: slide it in from the left for backward animation, or keep it static for forward animation
|
||||
Positioned.fill(
|
||||
child: SlideTransition(
|
||||
position: _isAnimating && !_isForwardAnimation
|
||||
? _slideAnimation
|
||||
: const AlwaysStoppedAnimation(Offset.zero),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
// Forward animation: slide the current page out to the right
|
||||
if (_isAnimating && _isForwardAnimation)
|
||||
Positioned.fill(
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: buildScreenshotContainer(_screenshotData),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool get isAnimating => _isAnimating;
|
||||
}
|
||||
67
lib/pages/epub_reader/page_turn/ios_page_turn_session.dart
Normal file
67
lib/pages/epub_reader/page_turn/ios_page_turn_session.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class IOSPageTurnSession {
|
||||
static const MethodChannel _nativePageTurnChannel = MethodChannel(
|
||||
'mooknote/reader_page_turn',
|
||||
);
|
||||
|
||||
int _currentToken = 0;
|
||||
bool _isAnimating = false;
|
||||
|
||||
Future<void> _prepareIOSPageTurn() async {
|
||||
if (!Platform.isIOS) return;
|
||||
try {
|
||||
await _nativePageTurnChannel.invokeMethod<void>('preparePageTurn');
|
||||
} on MissingPluginException {
|
||||
// no-op for configurations without iOS native channel
|
||||
} catch (e) {
|
||||
debugPrint('preparePageTurn failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _animateIOSPageTurn(bool isNext, bool isVertical) async {
|
||||
if (!Platform.isIOS) return;
|
||||
try {
|
||||
final token = ++_currentToken;
|
||||
_isAnimating = true;
|
||||
await _nativePageTurnChannel.invokeMethod<void>('animatePageTurn', {
|
||||
'isNext': isNext,
|
||||
'isVertical': isVertical,
|
||||
});
|
||||
if (token == _currentToken) {
|
||||
_isAnimating = false;
|
||||
return;
|
||||
}
|
||||
} on MissingPluginException {
|
||||
// no-op for configurations without iOS native channel
|
||||
} catch (e) {
|
||||
debugPrint('animatePageTurn failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> perform({
|
||||
required bool needAnimation,
|
||||
required bool isNext,
|
||||
required bool isVertical,
|
||||
required Future<void> Function(bool) onPerformPageTurn,
|
||||
}) async {
|
||||
if (!needAnimation) {
|
||||
await onPerformPageTurn(isNext);
|
||||
return;
|
||||
}
|
||||
|
||||
await _prepareIOSPageTurn();
|
||||
await onPerformPageTurn(isNext);
|
||||
unawaited(_animateIOSPageTurn(isNext, isVertical));
|
||||
}
|
||||
|
||||
Widget buildAnimatedContainer(BuildContext context, Widget child) {
|
||||
return child;
|
||||
}
|
||||
|
||||
bool get isAnimating => _isAnimating;
|
||||
}
|
||||
2
lib/pages/epub_reader/page_turn/page_turn.dart
Normal file
2
lib/pages/epub_reader/page_turn/page_turn.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export 'android_page_turn_session.dart';
|
||||
export 'ios_page_turn_session.dart';
|
||||
Reference in New Issue
Block a user