Only save annotations on changes

This commit is contained in:
2026-02-06 16:09:52 +01:00
parent 9a11e42571
commit 58157a2e6e
2 changed files with 53 additions and 29 deletions

View File

@@ -51,6 +51,9 @@ class DrawingController extends ChangeNotifier {
/// Maximum number of history steps to keep /// Maximum number of history steps to keep
final int maxHistorySteps; final int maxHistorySteps;
/// Whether there are unsaved changes since last load/clear
bool _hasUnsavedChanges = false;
DrawingController({this.maxHistorySteps = 50}); DrawingController({this.maxHistorySteps = 50});
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -75,6 +78,9 @@ class DrawingController extends ChangeNotifier {
/// Whether redo is available /// Whether redo is available
bool get canRedo => _redoStack.isNotEmpty; bool get canRedo => _redoStack.isNotEmpty;
/// Whether there are unsaved changes since last load/clear/markSaved
bool get hasUnsavedChanges => _hasUnsavedChanges;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Drawing Operations // Drawing Operations
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -120,6 +126,7 @@ class DrawingController extends ChangeNotifier {
_redoStack.clear(); _redoStack.clear();
_trimHistory(); _trimHistory();
_currentErasedLines.clear(); _currentErasedLines.clear();
_hasUnsavedChanges = true;
notifyListeners(); // Update UI to enable undo button notifyListeners(); // Update UI to enable undo button
} }
return; return;
@@ -133,6 +140,7 @@ class DrawingController extends ChangeNotifier {
_undoStack.add(AddLineAction(_currentLine!)); _undoStack.add(AddLineAction(_currentLine!));
_redoStack.clear(); _redoStack.clear();
_trimHistory(); _trimHistory();
_hasUnsavedChanges = true;
} }
_currentLine = null; _currentLine = null;
@@ -257,6 +265,7 @@ class DrawingController extends ChangeNotifier {
_redoStack.add(action); _redoStack.add(action);
} }
_hasUnsavedChanges = true;
notifyListeners(); notifyListeners();
} }
@@ -279,6 +288,7 @@ class DrawingController extends ChangeNotifier {
_undoStack.add(action); _undoStack.add(action);
} }
_hasUnsavedChanges = true;
notifyListeners(); notifyListeners();
} }
@@ -289,6 +299,7 @@ class DrawingController extends ChangeNotifier {
_redoStack.clear(); _redoStack.clear();
_currentLine = null; _currentLine = null;
_currentErasedLines.clear(); _currentErasedLines.clear();
_hasUnsavedChanges = false;
notifyListeners(); notifyListeners();
} }
@@ -324,6 +335,7 @@ class DrawingController extends ChangeNotifier {
_redoStack.clear(); _redoStack.clear();
_currentLine = null; _currentLine = null;
_currentErasedLines.clear(); _currentErasedLines.clear();
_hasUnsavedChanges = false;
for (final json in jsonList) { for (final json in jsonList) {
_lines.add(DrawingLine.fromJson(json)); _lines.add(DrawingLine.fromJson(json));
@@ -354,6 +366,11 @@ class DrawingController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
/// Marks the current state as saved (resets unsaved changes flag).
void markSaved() {
_hasUnsavedChanges = false;
}
@override @override
void dispose() { void dispose() {
_lines.clear(); _lines.clear();

View File

@@ -103,7 +103,7 @@ class _SheetViewerPageState extends State<SheetViewerPage>
}); });
// Sync annotations from server (downloads newer versions) // Sync annotations from server (downloads newer versions)
await _syncService.syncAnnotationsFromServer(widget.sheet.uuid); await _syncService.syncFromServer(widget.sheet.uuid);
// Load annotations for current page(s) // Load annotations for current page(s)
await _loadAnnotationsForCurrentPages(); await _loadAnnotationsForCurrentPages();
@@ -143,32 +143,40 @@ class _SheetViewerPageState extends State<SheetViewerPage>
} }
/// Saves the current page(s) annotations to storage and uploads to server. /// Saves the current page(s) annotations to storage and uploads to server.
///
/// Only saves if there are actual changes to avoid unnecessary writes/uploads.
Future<void> _saveCurrentAnnotations() async { Future<void> _saveCurrentAnnotations() async {
final now = DateTime.now(); final now = DateTime.now();
// Save left page // Save left page only if changed
final leftJson = _leftDrawingController.toJsonString(); if (_leftDrawingController.hasUnsavedChanges) {
final leftHasContent = leftJson.isNotEmpty && leftJson != '[]'; final leftJson = _leftDrawingController.toJsonString();
final leftHasContent = leftJson.isNotEmpty && leftJson != '[]';
await _storageService.writeAnnotationsWithMetadata( await _storageService.writeAnnotationsWithMetadata(
widget.sheet.uuid, widget.sheet.uuid,
_currentPage, _currentPage,
leftHasContent ? leftJson : null, leftHasContent ? leftJson : null,
now, now,
);
// Upload left page to server
if (leftHasContent) {
_syncService.uploadAnnotation(
sheetUuid: widget.sheet.uuid,
page: _currentPage,
annotationsJson: leftJson,
lastModified: now,
); );
// Upload left page to server
if (leftHasContent) {
_syncService.uploadAnnotation(
sheetUuid: widget.sheet.uuid,
page: _currentPage,
annotationsJson: leftJson,
lastModified: now,
);
}
_leftDrawingController.markSaved();
} }
// Save right page (two-page mode) // Save right page (two-page mode) only if changed
if (widget.config.twoPageMode && _currentPage < _totalPages) { if (widget.config.twoPageMode &&
_currentPage < _totalPages &&
_rightDrawingController.hasUnsavedChanges) {
final rightJson = _rightDrawingController.toJsonString(); final rightJson = _rightDrawingController.toJsonString();
final rightHasContent = rightJson.isNotEmpty && rightJson != '[]'; final rightHasContent = rightJson.isNotEmpty && rightJson != '[]';
@@ -188,6 +196,8 @@ class _SheetViewerPageState extends State<SheetViewerPage>
lastModified: now, lastModified: now,
); );
} }
_rightDrawingController.markSaved();
} }
} }
@@ -290,9 +300,8 @@ class _SheetViewerPageState extends State<SheetViewerPage>
icon: Icon( icon: Icon(
widget.config.fullscreen ? Icons.fullscreen_exit : Icons.fullscreen, widget.config.fullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
), ),
tooltip: widget.config.fullscreen tooltip:
? 'Exit Fullscreen' widget.config.fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen',
: 'Enter Fullscreen',
onPressed: _toggleFullscreen, onPressed: _toggleFullscreen,
), ),
IconButton( IconButton(
@@ -304,9 +313,8 @@ class _SheetViewerPageState extends State<SheetViewerPage>
icon: Icon( icon: Icon(
widget.config.twoPageMode ? Icons.filter_1 : Icons.filter_2, widget.config.twoPageMode ? Icons.filter_1 : Icons.filter_2,
), ),
tooltip: widget.config.twoPageMode tooltip:
? 'Single Page Mode' widget.config.twoPageMode ? 'Single Page Mode' : 'Two Page Mode',
: 'Two Page Mode',
onPressed: _toggleTwoPageMode, onPressed: _toggleTwoPageMode,
), ),
], ],
@@ -338,9 +346,8 @@ class _SheetViewerPageState extends State<SheetViewerPage>
currentPageNumber: _currentPage, currentPageNumber: _currentPage,
config: widget.config, config: widget.config,
leftDrawingController: _leftDrawingController, leftDrawingController: _leftDrawingController,
rightDrawingController: widget.config.twoPageMode rightDrawingController:
? _rightDrawingController widget.config.twoPageMode ? _rightDrawingController : null,
: null,
drawingEnabled: _isPaintMode, drawingEnabled: _isPaintMode,
); );