Custom drawing implementation
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_drawing_board/flutter_drawing_board.dart';
|
||||
import 'package:flutter_drawing_board/paint_contents.dart';
|
||||
import 'package:flutter_fullscreen/flutter_fullscreen.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
@@ -12,7 +10,7 @@ import 'package:sheetless/core/services/api_client.dart';
|
||||
import 'package:sheetless/core/services/storage_service.dart';
|
||||
|
||||
import '../../shared/input/pedal_shortcuts.dart';
|
||||
import 'widgets/paint_mode_layer.dart';
|
||||
import 'drawing/drawing.dart';
|
||||
import 'widgets/pdf_page_display.dart';
|
||||
import 'widgets/touch_navigation_layer.dart';
|
||||
|
||||
@@ -43,20 +41,21 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
int _currentPage = 1;
|
||||
int _totalPages = 1;
|
||||
bool _isPaintMode = false;
|
||||
late DrawingController _drawingController;
|
||||
|
||||
/// Drawing controller for the current left page
|
||||
late DrawingController _leftDrawingController;
|
||||
|
||||
/// Drawing controller for the right page (two-page mode)
|
||||
late DrawingController _rightDrawingController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize drawing controller with default configuration
|
||||
_drawingController = DrawingController(
|
||||
config: DrawConfig(
|
||||
contentType: SimpleLine,
|
||||
strokeWidth: 4.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
maxHistorySteps: 100, // Limit undo/redo history (default: 100)
|
||||
);
|
||||
|
||||
// Initialize drawing controllers
|
||||
_leftDrawingController = DrawingController(maxHistorySteps: 50);
|
||||
_rightDrawingController = DrawingController(maxHistorySteps: 50);
|
||||
|
||||
FullScreen.addListener(this);
|
||||
FullScreen.setFullScreen(widget.config.fullscreen);
|
||||
_documentLoaded = _loadPdf();
|
||||
@@ -64,12 +63,38 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_drawingController.dispose();
|
||||
// Save current annotations synchronously before disposing
|
||||
// Note: This is fire-and-forget, but Hive operations are fast enough
|
||||
_saveCurrentAnnotationsSync();
|
||||
|
||||
_leftDrawingController.dispose();
|
||||
_rightDrawingController.dispose();
|
||||
FullScreen.removeListener(this);
|
||||
_document?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Synchronous version that doesn't await - used in dispose
|
||||
void _saveCurrentAnnotationsSync() {
|
||||
// Save left page (always, since paint mode is single-page only)
|
||||
final leftJson = _leftDrawingController.toJsonString();
|
||||
_storageService.writeAnnotations(
|
||||
widget.sheet.uuid,
|
||||
_currentPage,
|
||||
leftJson.isEmpty || leftJson == '[]' ? null : leftJson,
|
||||
);
|
||||
|
||||
// Save right page if in two-page mode
|
||||
if (widget.config.twoPageMode && _currentPage < _totalPages) {
|
||||
final rightJson = _rightDrawingController.toJsonString();
|
||||
_storageService.writeAnnotations(
|
||||
widget.sheet.uuid,
|
||||
_currentPage + 1,
|
||||
rightJson.isEmpty || rightJson == '[]' ? null : rightJson,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PDF Loading
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -89,9 +114,64 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
_totalPages = _document!.pages.length;
|
||||
});
|
||||
|
||||
// Load annotations for current page(s)
|
||||
await _loadAnnotationsForCurrentPages();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Annotation Persistence
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Loads annotations for the current page(s) from storage.
|
||||
Future<void> _loadAnnotationsForCurrentPages() async {
|
||||
// Load left page annotations
|
||||
final leftJson = await _storageService.readAnnotations(
|
||||
widget.sheet.uuid,
|
||||
_currentPage,
|
||||
);
|
||||
if (leftJson != null && leftJson.isNotEmpty) {
|
||||
_leftDrawingController.fromJsonString(leftJson);
|
||||
} else {
|
||||
_leftDrawingController.clear();
|
||||
}
|
||||
|
||||
// Load right page annotations (two-page mode)
|
||||
if (widget.config.twoPageMode && _currentPage < _totalPages) {
|
||||
final rightJson = await _storageService.readAnnotations(
|
||||
widget.sheet.uuid,
|
||||
_currentPage + 1,
|
||||
);
|
||||
if (rightJson != null && rightJson.isNotEmpty) {
|
||||
_rightDrawingController.fromJsonString(rightJson);
|
||||
} else {
|
||||
_rightDrawingController.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves the current page(s) annotations to storage.
|
||||
Future<void> _saveCurrentAnnotations() async {
|
||||
// Save left page
|
||||
final leftJson = _leftDrawingController.toJsonString();
|
||||
await _storageService.writeAnnotations(
|
||||
widget.sheet.uuid,
|
||||
_currentPage,
|
||||
leftJson.isEmpty || leftJson == '[]' ? null : leftJson,
|
||||
);
|
||||
|
||||
// Save right page (two-page mode)
|
||||
if (widget.config.twoPageMode && _currentPage < _totalPages) {
|
||||
final rightJson = _rightDrawingController.toJsonString();
|
||||
await _storageService.writeAnnotations(
|
||||
widget.sheet.uuid,
|
||||
_currentPage + 1,
|
||||
rightJson.isEmpty || rightJson == '[]' ? null : rightJson,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fullscreen
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -105,10 +185,6 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
}
|
||||
|
||||
void _toggleFullscreen() {
|
||||
if (_isPaintMode) {
|
||||
_showSnackBar('Cannot enter fullscreen while in paint mode');
|
||||
return;
|
||||
}
|
||||
FullScreen.setFullScreen(!widget.config.fullscreen);
|
||||
}
|
||||
|
||||
@@ -116,10 +192,19 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
// Navigation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void _turnPage(int delta) {
|
||||
setState(() {
|
||||
_currentPage = (_currentPage + delta).clamp(1, _totalPages);
|
||||
});
|
||||
Future<void> _turnPage(int delta) async {
|
||||
// Save current annotations before turning
|
||||
await _saveCurrentAnnotations();
|
||||
|
||||
// Calculate new page
|
||||
final newPage = (_currentPage + delta).clamp(1, _totalPages);
|
||||
|
||||
// Load annotations for new page(s) BEFORE updating state
|
||||
_currentPage = newPage;
|
||||
await _loadAnnotationsForCurrentPages();
|
||||
|
||||
// Now update UI
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -132,6 +217,11 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isPaintMode) {
|
||||
// Exiting paint mode - save annotations
|
||||
_saveCurrentAnnotations();
|
||||
}
|
||||
|
||||
setState(() => _isPaintMode = !_isPaintMode);
|
||||
}
|
||||
|
||||
@@ -145,6 +235,9 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
widget.config.twoPageMode = !widget.config.twoPageMode;
|
||||
_storageService.writeConfig(widget.config);
|
||||
});
|
||||
|
||||
// Reload annotations for new mode
|
||||
_loadAnnotationsForCurrentPages();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -156,7 +249,12 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
return PedalShortcuts(
|
||||
onPageForward: () => _turnPage(1),
|
||||
onPageBackward: () => _turnPage(-1),
|
||||
child: Scaffold(appBar: _buildAppBar(), body: _buildBody()),
|
||||
child: Scaffold(
|
||||
appBar: _buildAppBar(),
|
||||
body: _buildBody(),
|
||||
floatingActionButton: _isPaintMode ? _buildDrawingToolbar() : null,
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,23 +271,21 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
icon: Icon(
|
||||
widget.config.fullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
),
|
||||
tooltip: widget.config.fullscreen
|
||||
? 'Exit Fullscreen'
|
||||
: 'Enter Fullscreen',
|
||||
tooltip:
|
||||
widget.config.fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen',
|
||||
onPressed: _toggleFullscreen,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(_isPaintMode ? Icons.brush : Icons.brush_outlined),
|
||||
tooltip: 'Toggle Paint Mode',
|
||||
tooltip: _isPaintMode ? 'Exit Paint Mode' : 'Enter Paint Mode',
|
||||
onPressed: _togglePaintMode,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
widget.config.twoPageMode ? Icons.filter_1 : Icons.filter_2,
|
||||
),
|
||||
tooltip: widget.config.twoPageMode
|
||||
? 'Single Page Mode'
|
||||
: 'Two Page Mode',
|
||||
tooltip:
|
||||
widget.config.twoPageMode ? 'Single Page Mode' : 'Two Page Mode',
|
||||
onPressed: _toggleTwoPageMode,
|
||||
),
|
||||
],
|
||||
@@ -220,30 +316,37 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
numPages: _totalPages,
|
||||
currentPageNumber: _currentPage,
|
||||
config: widget.config,
|
||||
leftDrawingController: _leftDrawingController,
|
||||
rightDrawingController:
|
||||
widget.config.twoPageMode ? _rightDrawingController : null,
|
||||
drawingEnabled: _isPaintMode,
|
||||
);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// Show touch navigation when not in paint mode
|
||||
Visibility(
|
||||
visible: !_isPaintMode,
|
||||
child: TouchNavigationLayer(
|
||||
pageDisplay: pageDisplay,
|
||||
config: widget.config,
|
||||
onToggleFullscreen: _toggleFullscreen,
|
||||
onExit: () => Navigator.pop(context),
|
||||
onPageTurn: _turnPage,
|
||||
),
|
||||
// When in paint mode, show the page display directly (DrawingBoard handles zoom/pan)
|
||||
if (_isPaintMode) {
|
||||
return pageDisplay;
|
||||
}
|
||||
|
||||
// When not in paint mode, wrap with touch navigation
|
||||
return TouchNavigationLayer(
|
||||
pageDisplay: pageDisplay,
|
||||
config: widget.config,
|
||||
onToggleFullscreen: _toggleFullscreen,
|
||||
onExit: () => Navigator.pop(context),
|
||||
onPageTurn: _turnPage,
|
||||
child: pageDisplay,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDrawingToolbar() {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: DrawingToolbar(
|
||||
controller: _leftDrawingController,
|
||||
onClose: _togglePaintMode,
|
||||
),
|
||||
// Show paint mode layer when active
|
||||
Visibility(
|
||||
visible: _isPaintMode,
|
||||
child: PaintModeLayer(
|
||||
pageDisplay: pageDisplay,
|
||||
drawingController: _drawingController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,9 +356,10 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
message,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(color: Colors.red),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: Colors.red),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
@@ -264,7 +368,7 @@ class _SheetViewerPageState extends State<SheetViewerPage>
|
||||
|
||||
void _showSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message), duration: Duration(seconds: 2)),
|
||||
SnackBar(content: Text(message), duration: const Duration(seconds: 2)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user