127 lines
3.4 KiB
Dart
127 lines
3.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../../../core/models/config.dart';
|
|
import 'pdf_page_display.dart';
|
|
|
|
/// Callback for page turn events.
|
|
typedef PageTurnCallback = void Function(int delta);
|
|
|
|
/// Gesture layer for touch-based navigation over PDF pages.
|
|
///
|
|
/// Touch zones:
|
|
/// - Top 2cm: Toggle fullscreen (or exit if in fullscreen + top-right corner)
|
|
/// - Left side: Turn page backward (-1 or -2 in two-page mode)
|
|
/// - Right side: Turn page forward (+1 or +2 in two-page mode)
|
|
class TouchNavigationLayer extends StatelessWidget {
|
|
final PdfPageDisplay pageDisplay;
|
|
final Config config;
|
|
final VoidCallback onToggleFullscreen;
|
|
final VoidCallback onExit;
|
|
final PageTurnCallback onPageTurn;
|
|
|
|
const TouchNavigationLayer({
|
|
super.key,
|
|
required this.pageDisplay,
|
|
required this.config,
|
|
required this.onToggleFullscreen,
|
|
required this.onExit,
|
|
required this.onPageTurn,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTapUp: (details) => _handleTap(context, details),
|
|
child: pageDisplay,
|
|
);
|
|
}
|
|
|
|
void _handleTap(BuildContext context, TapUpDetails details) {
|
|
final mediaQuery = MediaQuery.of(context);
|
|
final position = details.localPosition;
|
|
|
|
// Calculate physical measurements for consistent touch zones
|
|
final pixelsPerCm = _calculatePixelsPerCm(mediaQuery.devicePixelRatio);
|
|
final touchZoneHeight = 2 * pixelsPerCm;
|
|
final touchZoneWidth = 2 * pixelsPerCm;
|
|
|
|
final screenWidth = mediaQuery.size.width;
|
|
final screenCenter = screenWidth / 2;
|
|
|
|
// Get page sizes for accurate touch zone calculation
|
|
final (leftPageSize, rightPageSize) = pageDisplay.calculateScaledPageSizes(
|
|
mediaQuery.size,
|
|
);
|
|
|
|
// Check top zone first
|
|
if (position.dy < touchZoneHeight) {
|
|
_handleTopZoneTap(position, screenWidth, touchZoneWidth);
|
|
return;
|
|
}
|
|
|
|
// Handle page turning based on tap position
|
|
_handlePageTurnTap(position, screenCenter, leftPageSize, rightPageSize);
|
|
}
|
|
|
|
void _handleTopZoneTap(
|
|
Offset position,
|
|
double screenWidth,
|
|
double touchZoneWidth,
|
|
) {
|
|
// Top-right corner in fullscreen mode = exit
|
|
if (config.fullscreen && position.dx >= screenWidth - touchZoneWidth) {
|
|
onExit();
|
|
} else {
|
|
onToggleFullscreen();
|
|
}
|
|
}
|
|
|
|
void _handlePageTurnTap(
|
|
Offset position,
|
|
double screenCenter,
|
|
Size leftPageSize,
|
|
Size? rightPageSize,
|
|
) {
|
|
final isLeftSide = position.dx < screenCenter;
|
|
|
|
if (config.twoPageMode) {
|
|
_handleTwoPageModeTap(
|
|
position,
|
|
screenCenter,
|
|
leftPageSize,
|
|
rightPageSize,
|
|
);
|
|
} else {
|
|
// Single page mode: simple left/right
|
|
onPageTurn(isLeftSide ? -1 : 1);
|
|
}
|
|
}
|
|
|
|
void _handleTwoPageModeTap(
|
|
Offset position,
|
|
double screenCenter,
|
|
Size leftPageSize,
|
|
Size? rightPageSize,
|
|
) {
|
|
final leftEdge = screenCenter - leftPageSize.width / 2;
|
|
final rightEdge = screenCenter + (rightPageSize?.width ?? 0) / 2;
|
|
|
|
if (position.dx < leftEdge) {
|
|
onPageTurn(-2);
|
|
} else if (position.dx < screenCenter) {
|
|
onPageTurn(-1);
|
|
} else if (position.dx > rightEdge) {
|
|
onPageTurn(2);
|
|
} else {
|
|
onPageTurn(1);
|
|
}
|
|
}
|
|
|
|
double _calculatePixelsPerCm(double devicePixelRatio) {
|
|
const baseDpi = 160.0; // Android baseline DPI
|
|
const cmPerInch = 2.54;
|
|
return (devicePixelRatio * baseDpi) / cmPerInch;
|
|
}
|
|
}
|