Files
BadNote/lib/screens/split_view_screen.dart

501 lines
16 KiB
Dart
Raw Normal View History

Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import 'package:uuid/uuid.dart';
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
import '../editor/canvas/pen_canvas.dart';
import '../editor/canvas/pen_stroke.dart';
import '../editor/notebook/ink_stroke_adapter.dart';
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
import '../models/ink_stroke.dart';
import '../models/pen_tool.dart';
import '../models/pressure_curve.dart';
import '../services/database_service.dart';
import '../services/undo_manager.dart';
import '../utils/stroke_stabilizer.dart';
import '../widgets/annotation_toolbar.dart';
/// Split-view derivation mode: left pane = reference PDF, right pane = infinite
/// scratchpad for formula derivation. Scratchpad strokes are persisted per
/// document via [DatabaseService.saveScratchpad] / [DatabaseService.loadScratchpad].
class SplitViewScreen extends StatefulWidget {
final String filePath;
final String documentId;
const SplitViewScreen({
super.key,
required this.filePath,
required this.documentId,
});
@override
State<SplitViewScreen> createState() => _SplitViewState();
}
class _SplitViewState extends State<SplitViewScreen> {
// -- PDF (left pane) --
final PdfViewerController _pdfController = PdfViewerController();
int _currentPage = 0;
int _pageCount = 0;
String _fileName = '';
// -- Split divider --
double _leftPaneFraction = 0.5;
bool _isDraggingDivider = false;
// -- Scratchpad (right pane) --
// The scratchpad is an infinite WORLD: strokes are stored in absolute world
// pixels ([InkStroke], unchanged persistence format), and rendered through the
// performant PenCanvas by normalizing against the CURRENT world size. When the
// world auto-expands, the stored world coords don't move — only the
// normalization divisor grows — so ink stays put with zero drift.
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
final UndoManager _undoManager = UndoManager();
List<InkStroke> _strokes = [];
double _canvasWidth = 4000;
double _canvasHeight = 4000;
static const _uuid = Uuid();
/// Pan/zoom transform for the scratchpad world (PenCanvas drives this).
final TransformationController _scratchTransform = TransformationController();
Size get _worldSize => Size(_canvasWidth, _canvasHeight);
/// Maps the scratchpad toolbar's [PenTool] to the pen-canvas tool. Shapes and
/// text fall back to pen (the pen-first scratchpad is freehand).
CanvasTool get _canvasTool => switch (_currentTool) {
PenTool.eraser => CanvasTool.eraser,
PenTool.highlighter => CanvasTool.highlighter,
_ => CanvasTool.pen,
};
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
// -- Tool state --
PenTool _currentTool = PenTool.pen;
Color _currentColor = Colors.black;
double _currentStrokeWidth = 2.0;
bool _filled = false;
PressureCurveType _pressureCurveType = PressureCurveType.linear;
StabilizationLevel _stabilizationLevel = StabilizationLevel.none;
// -- Auto-save debounce --
Timer? _saveTimer;
bool _dirty = false;
// -- Page link markers (optional feature) --
final List<_PageLink> _pageLinks = [];
static const double _edgeThreshold = 200.0;
static const double _expandAmount = 1000.0;
@override
void initState() {
super.initState();
_loadScratchpad();
}
@override
void dispose() {
_saveTimer?.cancel();
_saveImmediate();
_pdfController.dispose();
_scratchTransform.dispose();
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
super.dispose();
}
// -- Persistence --
Future<void> _loadScratchpad() async {
final db = await DatabaseService.getInstance();
final strokes = await db.loadScratchpad(widget.documentId);
if (mounted) {
setState(() {
// Keep only freehand strokes so the canvas list stays 1:1 with the
// undo manager (shapes/text have no pen-canvas representation).
final freehand = strokes.where((s) => isFreehandTool(s.tool)).toList();
_strokes = freehand;
for (final s in freehand) {
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
_undoManager.addStroke(s);
}
});
}
}
void _scheduleSave() {
_dirty = true;
_saveTimer?.cancel();
_saveTimer = Timer(const Duration(seconds: 3), _saveImmediate);
}
Future<void> _saveImmediate() async {
if (!_dirty) return;
_dirty = false;
final db = await DatabaseService.getInstance();
final json = jsonEncode(_strokes.map((s) => s.toJson()).toList());
await db.saveScratchpad(widget.documentId, json);
}
// -- Scratchpad stroke callbacks --
/// PenCanvas committed a stroke (normalized to the current world). Convert it
/// to absolute world coords for storage.
void _onStrokeComplete(PenStroke pen) {
final stroke = inkStrokeFromPen(pen, _worldSize,
id: _uuid.v4(), createdAt: DateTime.now());
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
setState(() {
_strokes.add(stroke);
_undoManager.addStroke(stroke);
_checkCanvasExpansion(stroke);
});
_scheduleSave();
}
/// PenCanvas erased through stroke [index] (into [_strokes]); [replacements]
/// are the surviving sub-strokes (normalized) — convert back to world coords.
void _onErase(int index, List<PenStroke> replacements) {
if (index < 0 || index >= _strokes.length) return;
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
setState(() {
final original = _strokes[index];
final inkReplacements = [
for (final r in replacements)
inkStrokeFromPen(r, _worldSize,
id: _uuid.v4(), createdAt: DateTime.now()),
];
_undoManager.removeStroke(original, replacements: inkReplacements);
_strokes = List.from(_undoManager.currentStrokes);
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
});
_scheduleSave();
}
void _undo() {
setState(() {
_undoManager.undo();
_strokes = List.from(_undoManager.currentStrokes);
});
_scheduleSave();
}
void _redo() {
setState(() {
_undoManager.redo();
_strokes = List.from(_undoManager.currentStrokes);
});
_scheduleSave();
}
// -- Auto-expand canvas --
void _checkCanvasExpansion(InkStroke stroke) {
double maxRight = 0;
double maxBottom = 0;
for (final p in stroke.points) {
if (p.x > maxRight) maxRight = p.x;
if (p.y > maxBottom) maxBottom = p.y;
}
bool expanded = false;
if (maxRight > _canvasWidth - _edgeThreshold) {
_canvasWidth += _expandAmount;
expanded = true;
}
if (maxBottom > _canvasHeight - _edgeThreshold) {
_canvasHeight += _expandAmount;
expanded = true;
}
if (expanded) setState(() {});
}
// -- Divider drag --
void _onDividerDragStart(DragStartDetails details) {
setState(() => _isDraggingDivider = true);
}
void _onDividerDragUpdate(
DragUpdateDetails details,
BoxConstraints constraints,
) {
final renderWidth = constraints.maxWidth;
if (renderWidth <= 0) return;
final delta = details.delta.dx / renderWidth;
setState(() {
_leftPaneFraction = (_leftPaneFraction + delta).clamp(0.2, 0.8);
});
}
void _onDividerDragEnd(DragEndDetails details) {
setState(() => _isDraggingDivider = false);
}
// -- PDF page navigation --
void _prevPage() {
if (_currentPage > 0) {
_pdfController.previousPage();
}
}
void _nextPage() {
if (_currentPage < _pageCount - 1) {
_pdfController.nextPage();
}
}
// -- Page link creation (long-press on left pane) --
void _onPdfLongPress(int pageNumber) {
// Place a page link marker at the current scratchpad viewport center.
// We approximate the viewport center as (0, 0) since InteractiveViewer
// manages its own transform — the user can reposition by panning.
setState(() {
_pageLinks.add(
_PageLink(
pageNumber: pageNumber,
position: const Offset(100, 100), // default top-left area
),
);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Page link marker added for page $pageNumber')),
);
}
void _onPageLinkTap(_PageLink link) {
_pdfController.jumpToPage(link.pageNumber);
setState(() {
_currentPage = link.pageNumber - 1;
});
}
void _deletePageLink(_PageLink link) {
setState(() {
_pageLinks.remove(link);
});
}
// -- Build --
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
_fileName.isEmpty ? 'Split View' : _fileName,
style: const TextStyle(fontSize: 16),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
_saveImmediate();
Navigator.of(context).pop();
},
),
actions: [
// Left pane page navigation
IconButton(
icon: const Icon(Icons.navigate_before),
tooltip: 'Previous page (PDF)',
onPressed: _currentPage > 0 ? _prevPage : null,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Center(
child: Text(
'${_currentPage + 1} / $_pageCount',
style: const TextStyle(fontSize: 13),
),
),
),
IconButton(
icon: const Icon(Icons.navigate_next),
tooltip: 'Next page (PDF)',
onPressed: _currentPage < _pageCount - 1 ? _nextPage : null,
),
const SizedBox(width: 8),
// Canvas info
Tooltip(
message:
'Scratchpad size: ${_canvasWidth.round()} x ${_canvasHeight.round()}',
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Center(
child: Text(
'${_canvasWidth.round()}x${_canvasHeight.round()}',
style: const TextStyle(fontSize: 11, color: Colors.grey),
),
),
),
),
],
),
body: Column(
children: [
// Label clarifying that the toolbar controls the scratchpad pane.
Padding(
padding: const EdgeInsets.only(left: 12, top: 4),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Scratchpad tools',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
),
// Toolbar (applies to scratchpad only)
AnnotationToolbar(
currentTool: _currentTool,
currentColor: _currentColor,
currentStrokeWidth: _currentStrokeWidth,
filled: _filled,
pressureCurveType: _pressureCurveType,
stabilizationLevel: _stabilizationLevel,
canUndo: _undoManager.canUndo,
canRedo: _undoManager.canRedo,
onToolChanged: (tool) => setState(() => _currentTool = tool),
onColorChanged: (color) => setState(() => _currentColor = color),
onStrokeWidthChanged: (w) =>
setState(() => _currentStrokeWidth = w),
onFilledChanged: (f) => setState(() => _filled = f),
onPressureCurveChanged: (v) =>
setState(() => _pressureCurveType = v),
onStabilizationChanged: (v) =>
setState(() => _stabilizationLevel = v),
onUndo: _undo,
onRedo: _redo,
),
// Split view body
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
final totalWidth = constraints.maxWidth;
final leftWidth = totalWidth * _leftPaneFraction;
final rightWidth =
totalWidth - leftWidth - 12; // 12px divider hit area
return Row(
children: [
// Left pane: PDF reference (read-only)
SizedBox(width: leftWidth, child: _buildPdfPane()),
// Draggable divider: 12px hit area, 4px visual strip.
GestureDetector(
onHorizontalDragStart: _onDividerDragStart,
onHorizontalDragUpdate: (d) =>
_onDividerDragUpdate(d, constraints),
onHorizontalDragEnd: _onDividerDragEnd,
child: MouseRegion(
cursor: SystemMouseCursors.resizeColumn,
child: SizedBox(
width: 12,
child: Center(
child: Container(
width: 4,
color: _isDraggingDivider
? Theme.of(context).colorScheme.primary
: Theme.of(context).dividerColor,
),
),
),
),
),
// Right pane: Infinite scratchpad
SizedBox(width: rightWidth, child: _buildScratchpadPane()),
],
);
},
),
),
],
),
);
}
Widget _buildPdfPane() {
return Stack(
children: [
GestureDetector(
onLongPress: () {
// Long-press on PDF to create page link marker
_onPdfLongPress(_currentPage + 1);
},
child: SfPdfViewer.file(
File(widget.filePath),
controller: _pdfController,
canShowScrollHead: true,
canShowScrollStatus: true,
onPageChanged: (PdfPageChangedDetails details) {
setState(() {
_currentPage = details.newPageNumber - 1;
});
},
onDocumentLoaded: (PdfDocumentLoadedDetails details) {
setState(() {
_pageCount = details.document.pages.count;
_fileName = widget.filePath.split(Platform.pathSeparator).last;
});
},
),
),
// Page link markers overlay (on PDF pane, showing linked pages)
if (_pageLinks.isNotEmpty)
Positioned(bottom: 8, left: 8, child: _buildPageLinkChips()),
],
);
}
Widget _buildPageLinkChips() {
return Wrap(
spacing: 4,
runSpacing: 4,
children: _pageLinks.map((link) {
return GestureDetector(
onTap: () => _onPageLinkTap(link),
onLongPress: () => _deletePageLink(link),
child: Chip(
avatar: const Icon(Icons.link, size: 14, color: Colors.white),
label: Text(
'p${link.pageNumber}',
style: const TextStyle(fontSize: 11, color: Colors.white),
),
backgroundColor: Colors.blue.shade600,
padding: EdgeInsets.zero,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
);
}).toList(),
);
}
Widget _buildScratchpadPane() {
// Render the world through the performant PenCanvas: strokes normalized
// against the current world size; toolbar width is in world pixels, so the
// pen-canvas fraction is width / worldWidth.
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: PenCanvas(
pageSize: _worldSize,
strokes: penStrokesFromInk(_strokes, _worldSize),
transformationController: _scratchTransform,
tool: _canvasTool,
color: _currentColor,
strokeWidth: _currentStrokeWidth / _canvasWidth,
// The world is huge, so allow zooming further out to survey it.
minScale: 0.1,
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
maxScale: 8.0,
onStrokeComplete: _onStrokeComplete,
onEraseStroke: _onErase,
pageWidget: const ColoredBox(color: Colors.white),
Fix bugs across app + server, optimize UI/UX, add Gitea CI Bug fixes (Flutter): - Wrap multi-statement DB writes (insert/update/delete note, deleteDocument, deletePageData, OCR FTS merge, migrations) in transactions to prevent data loss on interruption and a read-modify-write FTS race. - Fix PdfDocument leaks on exception (try/finally dispose) and preserve image aspect ratio when stamping images onto PDF pages. - Guard file-picker against empty selection (was .single -> crash). - Fix eraser ConcurrentModificationError and unmodifiable-list crash on PDF pages; capture page synchronously on save to stop wrong-page data loss. - Fix Riverpod DB-not-ready races, broken pull-to-refresh, settings load race, and search N+1; transform stored annotations on PDF page rotation. - Normalize pen pressure for devices without a pressure range. - PPT: single source of truth for slide strokes so ink displays and exports. UI/UX: - Material 3 typography, theme-aware colors (dark-mode fixes), hover cursors and right-click/visible actions on desktop, keyboard shortcuts (undo/redo/ save/find), toolbar overflow handling, friendlier empty states, semantic OCR status badges, relative timestamps, 1-based page indicators, large-deck PPT navigation, and a scratchpad-scope label in split view. Server (optional backend): - Persist JWT secret (was per-process random), block path traversal in storage, fix CORS '*'+credentials, add OCR job ownership checks, last-writer-wins sync guard, constant-time login, and split out heavy OCR deps so the API/tests run without them. CI: Gitea workflows for format+analyze+test (Linux, system sqlite) and a Windows release build; pristine `flutter analyze`, all Flutter and server tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:18:00 +08:00
),
);
}
}
/// A marker linking a scratchpad position to a specific PDF page.
class _PageLink {
final int pageNumber;
final Offset position;
const _PageLink({required this.pageNumber, required this.position});
}