Update project docs to reflect current state after post-Phase 10 polish

- PROGRESS.md: remove stale known issues (all fixed), add post-Phase 10
  feature polish section covering toolbar redesign, 4 pen sizes, line/box/move
  tools, edge swipe nav, page reorder, notebook rename, filter/sort, JPG
  export, clipboard ops, startup state restoration, and DB migrations
- PROJECT_PLAN.md: add Phase 11 (Server Sync Integration) and Phase 12
  (Notebook Backup/Export) with step breakdowns
- DESIGN.md: add Tools table, sync architecture section, backup/export design,
  JPG export, stroke styles, startup state restoration, edge swipe nav; update
  rendering strategy (3-layer compositing), source tree, schema, and pen sizes
- CLAUDE.md: update build commands, architecture, source tree, and key
  conventions to match current codebase

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 20:49:24 -07:00
parent b8fb85c5f0
commit 6e5c500786
4 changed files with 304 additions and 115 deletions

View File

@@ -9,31 +9,38 @@ eng-pad is an Android note-taking app built around notebooks with an EMR pen as
## Build Commands ## Build Commands
```bash ```bash
make all # lint test build make all # lint -> test -> build
make build # Compile debug + release APKs make build # Compile debug + release APKs
make test # Run unit tests make test # Run unit tests
make lint # Run Android Lint make lint # Run Android Lint
make clean # Clean build artifacts make clean # Clean build artifacts
make apk # Build release APK
make run # Build, install, and launch on emulator (starts emulator if needed) make run # Build, install, and launch on emulator (starts emulator if needed)
make devrun # Build, install, and launch on connected USB device make devrun # Build, install, and launch on connected USB device
make run AVD=Medium_Phone_API_36.0 # Use a specific AVD make run AVD=Medium_Phone_API_36.0 # Use a specific AVD
# Run a single test class: # Run a single test class:
make test-one CLASS=net.metacircular.engpad.data.StrokeBlobTest make test-one CLASS=net.metacircular.engpad.data.StrokeBlobTest
# Direct Gradle commands:
./gradlew build # Compile + test + lint
./gradlew test # Unit tests only
./gradlew lint # Android Lint only
``` ```
## Architecture ## Architecture
- **Kotlin + Jetpack Compose** for UI chrome (screens, toolbar, dialogs) - **Kotlin + Jetpack Compose** for UI chrome (screens, toolbar, dialogs)
- **Custom View** (`PadCanvasView`) for the drawing canvas — Compose Canvas lacks `MotionEvent` access needed for stylus input - **Custom View** (`PadCanvasView`) for the drawing canvas — Compose Canvas lacks `MotionEvent` access needed for stylus input
- **Room** (SQLite) for persistence — notebooks, pages, strokes - **Room** (SQLite) for persistence — notebooks, pages, strokes (schema version 3)
- **Single Activity** with Compose Navigation (three screens: notebook list → page list → editor) - **SharedPreferences** for startup state restoration (last notebook, page list flag)
- **Coordinate system**: 300 DPI canonical points. Regular page = 2550×3300pt, large = 3300×5100pt. Scaled to 72 DPI (×0.24) for PDF export. - **Single Activity** with Compose Navigation (three routes: notebook list -> page list -> editor)
- **Input dispatch**: `TOOL_TYPE_STYLUS` → draw/erase/select, `TOOL_TYPE_FINGER` → zoom/pan - **Coordinate system**: 300 DPI canonical points. Regular page = 2550x3300pt, large = 3300x5100pt. Scaled to 72 DPI (x0.24) for PDF export.
- **Input dispatch**: `TOOL_TYPE_STYLUS` -> draw/erase/select/move, `TOOL_TYPE_FINGER` -> zoom/pan/edge swipe
## Project Documents ## Project Documents
- **DESIGN.md** — Full technical design (architecture, data model, rendering, source tree) - **DESIGN.md** — Full technical design (architecture, data model, rendering, tools, source tree)
- **PROJECT_PLAN.md** — All implementation steps with checkboxes. Check off steps as completed. - **PROJECT_PLAN.md** — All implementation steps with checkboxes. Check off steps as completed.
- **PROGRESS.md** — What's been done, what's in progress, decisions made. Update after every step. - **PROGRESS.md** — What's been done, what's in progress, decisions made. Update after every step.
@@ -49,27 +56,31 @@ eng-pad/
│ ├── main/ │ ├── main/
│ │ ├── AndroidManifest.xml │ │ ├── AndroidManifest.xml
│ │ ├── kotlin/net/metacircular/engpad/ │ │ ├── kotlin/net/metacircular/engpad/
│ │ │ ├── EngPadApp.kt -- Application class │ │ │ ├── EngPadApp.kt -- Application class, SharedPreferences
│ │ │ ├── MainActivity.kt -- Single activity, Compose NavHost │ │ │ ├── MainActivity.kt -- Single activity, Compose NavHost
│ │ │ ├── data/ │ │ │ ├── data/
│ │ │ │ ├── db/ -- Room database, DAOs, converters │ │ │ │ ├── db/ -- Room database (v3), DAOs, converters
│ │ │ │ ├── model/ -- Entities: Notebook, Page, Stroke, PageSize │ │ │ │ ├── model/ -- Entities: Notebook, Page, Stroke, PageSize
│ │ │ │ └── repository/ -- NotebookRepository, PageRepository │ │ │ │ └── repository/ -- NotebookRepository, PageRepository
│ │ │ ├── ui/ │ │ │ ├── ui/
│ │ │ │ ├── navigation/NavGraph.kt -- Route definitions │ │ │ │ ├── navigation/NavGraph.kt -- Routes, auto-restore last notebook
│ │ │ │ ├── notebooks/ -- List screen + ViewModel │ │ │ │ ├── notebooks/ -- Library: list, filter, sort, rename + ViewModel
│ │ │ │ ├── pages/ -- Page grid screen + ViewModel │ │ │ │ ├── pages/ -- Page grid, drag-to-reorder + ViewModel
│ │ │ │ ├── editor/ -- PadCanvasView, EditorScreen, ViewModel │ │ │ │ ├── editor/ -- PadCanvasView, EditorScreen, ViewModel, Toolbar
│ │ │ │ ├── export/PdfExporter.kt -- PDF generation + sharing │ │ │ │ ├── export/PdfExporter.kt -- PDF + JPG generation + sharing
│ │ │ │ └── theme/Theme.kt -- Material3 theme │ │ │ │ └── theme/Theme.kt -- Material3 high-contrast e-ink theme
│ │ │ └── undo/ -- UndoManager, UndoableAction │ │ │ └── undo/ -- UndoManager, UndoableAction, StrokeActions, SelectionActions
│ │ └── res/ │ │ └── res/
└── test/ -- Unit tests │ ├── values/ -- strings.xml, themes.xml
│ │ ├── drawable/ -- Launcher icons
│ │ ├── mipmap-anydpi/ -- Adaptive icon
│ │ └── xml/file_provider_paths.xml -- FileProvider config
│ └── test/ -- Unit tests (StrokeBlobTest, PageSizeTest, UndoManagerTest)
├── build.gradle.kts -- Root build config ├── build.gradle.kts -- Root build config
├── settings.gradle.kts -- Project settings (foojay JDK resolver) ├── settings.gradle.kts -- Project settings (foojay JDK resolver)
├── gradle.properties ├── gradle.properties
├── gradle/libs.versions.toml -- Version catalog ├── gradle/libs.versions.toml -- Version catalog
├── Makefile -- Build targets (build, test, lint, run, devrun) ├── Makefile -- Build targets (build, test, lint, run, devrun, apk)
├── CLAUDE.md -- This file ├── CLAUDE.md -- This file
├── README.md ├── README.md
├── DESIGN.md -- Technical design ├── DESIGN.md -- Technical design
@@ -81,11 +92,19 @@ eng-pad/
- Stroke points are packed as little-endian float BLOBs: `[x0,y0,x1,y1,...]` - Stroke points are packed as little-endian float BLOBs: `[x0,y0,x1,y1,...]`
- All coordinates are in canonical space (300 DPI). Screen transform via `Matrix`. - All coordinates are in canonical space (300 DPI). Screen transform via `Matrix`.
- Grid drawn in **screen space** with pixel-snapped positions (not canonical space) for uniform squares. - Grid drawn in **screen space** with pixel-snapped positions (not canonical space) for uniform squares. Cached in a grid bitmap, redrawn only on zoom/pan/resize.
- Pen widths: 0.38mm (4.49pt), 0.5mm (5.91pt), 0.6mm (7.09pt), 0.7mm (8.27pt) at 300 DPI. - **4 pen sizes**: 0.38mm (4.49pt), 0.5mm (5.91pt), 0.6mm (7.09pt), 0.7mm (8.27pt) at 300 DPI.
- Single PEN tool with long-press for size selection; remembers last size. - Single PEN tool with long-press for size selection; remembers last size.
- **Line styles** stored in stroke `style` DB column: plain, dashed, arrow, double_arrow.
- LINE tool with long-press for style selection; BOX tool for rectangles.
- MOVE tool for tap-and-drag stroke relocation.
- Anti-aliasing disabled on all paint objects for crisp e-ink rendering. - Anti-aliasing disabled on all paint objects for crisp e-ink rendering.
- No backing bitmap — strokes draw directly as paths. - Screen-resolution backing bitmap with incremental stroke addition (no full redraw on each new stroke).
- Viewport: page always fills screen, pan clamped to edges, dynamic min zoom. - Viewport: page always fills screen, pan clamped to edges, dynamic min zoom.
- Edge swipe navigation: finger swipe from screen edge -> previous/next page.
- Line snap: hold pen still 1.5s to snap to straight line (60pt movement threshold). - Line snap: hold pen still 1.5s to snap to straight line (60pt movement threshold).
- Hardware palm rejection only (EMR digitizer). - Hardware palm rejection only (EMR digitizer).
- Startup state restoration via SharedPreferences (last notebook ID, page list flag).
- Notebook `lastPageId` tracks resume position per-notebook.
- Clipboard: cut/copy/paste with toolbar buttons shown contextually.
- Export: PDF (multi-page, 72 DPI) and JPG (single page, 300 DPI) via share intents.

216
DESIGN.md
View File

@@ -3,9 +3,9 @@
## Overview ## Overview
eng-pad is an Android note-taking app for EMR pen devices. It provides a eng-pad is an Android note-taking app for EMR pen devices. It provides a
notebook-based writing surface with a guide grid, two fixed pen sizes, and notebook-based writing surface with a guide grid, four fixed pen sizes, line
PDF export. Target devices are the Supernote Manta (Android 11) and the and box drawing tools, and PDF/JPG export. Target devices are the Supernote
Daylight DC-1 (Android 13). Manta (Android 11) and the Daylight DC-1 (Android 13).
## Architecture ## Architecture
@@ -22,19 +22,20 @@ low-latency stylus input.
### Screen Flow ### Screen Flow
``` ```
NotebookListScreen PageListScreen EditorScreen NotebookListScreen -> PageListScreen -> EditorScreen
| | | | | |
Create/delete Page grid, PadCanvasView + Create/delete Page grid, PadCanvasView +
notebooks add pages Toolbar (Compose) rename, filter, add/delete/ Toolbar (Compose)
sort notebooks reorder pages
``` ```
Three Compose navigation destinations: Three Compose navigation destinations:
| Route | Screen | Purpose | | Route | Screen | Purpose |
|------------------------|--------------------|--------------------------------| |------------------------|--------------------|--------------------------------|
| `notebooks` | NotebookListScreen | List, create, delete notebooks | | `notebooks` | NotebookListScreen | List, create, delete, rename notebooks; filter and sort |
| `pages/{notebookId}` | PageListScreen | Page thumbnails, add pages | | `pages/{notebookId}` | PageListScreen | Page thumbnails, add/delete pages, drag-to-reorder |
| `editor/{pageId}` | EditorScreen | Drawing canvas + toolbar | | `editor/{notebookId}?pageId={pageId}` | EditorScreen | Drawing canvas + toolbar |
### Dependency Stack ### Dependency Stack
@@ -44,10 +45,12 @@ Three Compose navigation destinations:
| Drawing | Custom View + Canvas API | Stroke rendering, input | | Drawing | Custom View + Canvas API | Stroke rendering, input |
| State | ViewModel + StateFlow | Reactive UI state | | State | ViewModel + StateFlow | Reactive UI state |
| Persistence | Room (SQLite) | Notebooks, pages, strokes | | Persistence | Room (SQLite) | Notebooks, pages, strokes |
| Preferences | SharedPreferences | Last notebook, UI state restore |
| Async | Kotlin Coroutines | Background DB operations | | Async | Kotlin Coroutines | Background DB operations |
| Navigation | Compose Navigation | Screen routing | | Navigation | Compose Navigation | Screen routing |
| Export | Android PdfDocument API | PDF generation | | Export | Android PdfDocument API | PDF generation |
| Share | FileProvider + Intents | PDF sharing (Dropbox, etc.) | | Export | Bitmap + JPEG compress | JPG generation |
| Share | FileProvider + Intents | PDF/JPG sharing (Dropbox, etc.) |
No external drawing libraries. The Android `Canvas` + `Path` + `Paint` API No external drawing libraries. The Android `Canvas` + `Path` + `Paint` API
is sufficient and avoids dependencies that may behave unpredictably on e-ink is sufficient and avoids dependencies that may behave unpredictably on e-ink
@@ -61,25 +64,29 @@ during PDF export (multiply by 72/300 = 0.24).
| Page Size | Inches | Canonical Points | | Page Size | Inches | Canonical Points |
|-----------|----------|-------------------| |-----------|----------|-------------------|
| Regular | 8.5 × 11 | 2550 × 3300 | | Regular | 8.5 x 11 | 2550 x 3300 |
| Large | 11 × 17 | 3300 × 5100 | | Large | 11 x 17 | 3300 x 5100 |
### Grid ### Grid
The guide grid uses ~5 squares per inch = 300/5 = **60 points** per grid The guide grid uses ~5 squares per inch = 300/5 = **60 points** per grid
square. The grid is drawn as thin gray lines on screen but excluded from PDF square. The grid is drawn as thin gray lines on screen but excluded from PDF
export and any other output. export, JPG export, and any other output.
### Pen Sizes ### Pen Sizes
Two fixed stroke widths, corresponding to Muji gel ink ballpoints: Four fixed stroke widths:
| Pen | Millimeters | Canonical Points | | Pen | Millimeters | Canonical Points |
|-------|-------------|------------------| |-------|-------------|------------------|
| Fine | 0.38 mm | 4.49 pt | | 0.38 | 0.38 mm | 4.49 pt |
| Medium| 0.50 mm | 5.91 pt | | 0.50 | 0.50 mm | 5.91 pt |
| 0.60 | 0.60 mm | 7.09 pt |
| 0.70 | 0.70 mm | 8.27 pt |
No pressure sensitivity — stroke width is uniform for a given pen size. No pressure sensitivity — stroke width is uniform for a given pen size.
The PEN tool remembers the last selected size. Long-press the pen button
to open the size picker.
### Screen Transform ### Screen Transform
@@ -89,12 +96,23 @@ transformed through the inverse matrix before being stored, ensuring strokes
are always in canonical space regardless of zoom. are always in canonical space regardless of zoom.
``` ```
canonical [viewMatrix] screen pixels canonical -> [viewMatrix] -> screen pixels
screen pixels [inverseMatrix] canonical screen pixels -> [inverseMatrix] -> canonical
``` ```
Zoom range: 0.5× to 4×. Pan is clamped so the page cannot scroll entirely Zoom range: dynamic minimum (page fills viewport) to 4x. Pan is clamped so
off-screen. the page cannot scroll entirely off-screen.
## Tools
| Tool | Behavior | Sub-options |
|--------|----------|-------------|
| PEN | Freehand drawing with active pen size | 4 sizes via long-press menu |
| LINE | Straight line from pen-down to pen-up | Plain, arrow, double-arrow, dashed via long-press menu |
| BOX | Rectangle from corner to corner | Uses current pen size |
| ERASER | Touch a stroke to delete it (stroke-level) | — |
| SELECT | Rectangle selection, then cut/copy/delete/paste | — |
| MOVE | Tap and drag individual strokes | — |
## Data Model ## Data Model
@@ -106,7 +124,8 @@ CREATE TABLE notebooks (
title TEXT NOT NULL, title TEXT NOT NULL,
page_size TEXT NOT NULL CHECK(page_size IN ('regular', 'large')), page_size TEXT NOT NULL CHECK(page_size IN ('regular', 'large')),
created_at INTEGER NOT NULL, -- epoch millis created_at INTEGER NOT NULL, -- epoch millis
updated_at INTEGER NOT NULL -- epoch millis updated_at INTEGER NOT NULL, -- epoch millis
last_page_id INTEGER NOT NULL DEFAULT 0 -- resume position
); );
CREATE TABLE pages ( CREATE TABLE pages (
@@ -124,18 +143,22 @@ CREATE TABLE strokes (
color INTEGER NOT NULL, -- ARGB packed int color INTEGER NOT NULL, -- ARGB packed int
point_data BLOB NOT NULL, -- packed floats: [x0,y0,x1,y1,...] point_data BLOB NOT NULL, -- packed floats: [x0,y0,x1,y1,...]
stroke_order INTEGER NOT NULL, -- z-order within the page stroke_order INTEGER NOT NULL, -- z-order within the page
created_at INTEGER NOT NULL created_at INTEGER NOT NULL,
style TEXT NOT NULL DEFAULT 'plain' -- plain, dashed, arrow, double_arrow
); );
CREATE INDEX idx_strokes_page ON strokes(page_id); CREATE INDEX idx_strokes_page ON strokes(page_id);
CREATE INDEX idx_pages_notebook ON pages(notebook_id); CREATE INDEX idx_pages_notebook ON pages(notebook_id);
``` ```
Database version: 3. Migrations add `style` column to strokes and
`last_page_id` column to notebooks.
### Stroke Point Encoding ### Stroke Point Encoding
Points are stored as a `BLOB` of packed little-endian floats: Points are stored as a `BLOB` of packed little-endian floats:
`[x0, y0, x1, y1, x2, y2, ...]`. A stroke with N points uses N × 2 × 4 `[x0, y0, x1, y1, x2, y2, ...]`. A stroke with N points uses N x 2 x 4
bytes. A typical stroke of 200 points = 1600 bytes. This is ~10× more bytes. A typical stroke of 200 points = 1600 bytes. This is ~10x more
compact than JSON and eliminates parsing overhead. compact than JSON and eliminates parsing overhead.
```kotlin ```kotlin
@@ -153,6 +176,19 @@ fun ByteArray.toFloatArray(): FloatArray {
} }
``` ```
### Stroke Styles
The `style` column stores a string constant identifying the stroke's visual
style. This applies to LINE tool strokes and is stored in the DB so rendering
is consistent across sessions.
| Style | Rendering |
|----------------|-----------|
| `plain` | Solid line (default for freehand, box, and plain lines) |
| `dashed` | Dashed line pattern |
| `arrow` | Solid line with arrowhead at endpoint |
| `double_arrow` | Solid line with arrowheads at both ends |
## Input Handling ## Input Handling
### Tool Type Dispatch ### Tool Type Dispatch
@@ -163,8 +199,8 @@ software palm rejection:
| Tool Type | Action | | Tool Type | Action |
|------------------|-------------------------------------| |------------------|-------------------------------------|
| `TOOL_TYPE_STYLUS` | Draw, erase, or select (per mode) | | `TOOL_TYPE_STYLUS` | Draw, erase, select, or move (per tool) |
| `TOOL_TYPE_FINGER` | Pinch-to-zoom, pan | | `TOOL_TYPE_FINGER` | Pinch-to-zoom, pan, edge swipe navigation |
### Historical Points ### Historical Points
@@ -172,16 +208,12 @@ The Wacom EMR digitizer batches events. To capture all intermediate points
for smooth strokes, `PadCanvasView` processes `MotionEvent.getHistoricalX/Y` for smooth strokes, `PadCanvasView` processes `MotionEvent.getHistoricalX/Y`
on every `ACTION_MOVE`, not just the current event coordinates. on every `ACTION_MOVE`, not just the current event coordinates.
### Modes ### Edge Swipe Navigation
The editor toolbar controls the active mode: Finger swipes originating from the left or right 8% of the screen trigger
page navigation (previous/next page). Requires a minimum horizontal distance
| Mode | Stylus Behavior | of 100px. Multi-finger gestures cancel edge swipe detection. Swiping forward
|----------|------------------------------------------------------| past the last page auto-creates a new page if the current page has strokes.
| Draw | Create strokes with the active pen size |
| Erase | Touch a stroke to delete it (stroke-level eraser) |
| Select | Draw a rectangle to select strokes, then move/copy/delete |
| Box | Drag corner-to-corner to draw rectangles |
### Line Snap ### Line Snap
@@ -197,14 +229,23 @@ a separate ruler tool.
### On-Screen ### On-Screen
1. **Completed strokes** are drawn directly as `Path` objects on each Three-layer compositing with cached bitmaps:
`onDraw` call. No backing bitmap — direct path rendering avoids the
resolution loss that caused blurry strokes on e-ink displays. 1. **Grid layer** cached bitmap at screen resolution, redrawn only when
2. **In-progress stroke** (pen is down) is drawn on top of completed strokes. zoom, pan, or view size changes. Grid is drawn in screen pixel space
3. **Grid** is drawn in screen pixel space (not canonical space) with (not canonical space) with pixel-snapped line positions for perfectly
pixel-snapped line positions. This ensures perfectly uniform square uniform squares regardless of zoom level. Uses 1px screen-space lines.
spacing regardless of zoom level. Grid uses 1px screen-space lines.
4. **Strokes** are drawn in canonical space via the view `Matrix`. 2. **Stroke layer** — screen-resolution backing bitmap. Completed strokes
are drawn as `Path` objects via the view `Matrix`. New strokes are added
incrementally (drawn onto the existing bitmap without clearing) to avoid
full-redraw flicker. The bitmap is fully rebuilt only on zoom/pan changes
or stroke deletion.
3. **Dynamic layer** — drawn directly on each `onDraw` call in canonical
space via the view `Matrix`. Includes: in-progress stroke (pen is down),
line/box previews, selection rectangle and highlights.
Anti-aliasing is disabled on all paint objects for crisp e-ink rendering. Anti-aliasing is disabled on all paint objects for crisp e-ink rendering.
### Viewport ### Viewport
@@ -229,6 +270,12 @@ Export flow:
3. Write to a temp file in the app's cache directory. 3. Write to a temp file in the app's cache directory.
4. Share via `Intent.ACTION_SEND` with a `FileProvider` URI. 4. Share via `Intent.ACTION_SEND` with a `FileProvider` URI.
### JPG Export
Single-page export at 300 DPI (full canonical resolution). Creates a
`Bitmap`, renders all strokes (no grid), compresses to JPEG at 95% quality.
Shared via the same `FileProvider` + intent mechanism as PDF.
## Undo/Redo ## Undo/Redo
Command pattern with a depth limit of 50: Command pattern with a depth limit of 50:
@@ -237,7 +284,7 @@ Command pattern with a depth limit of 50:
|----------------------|-------------------|--------------------| |----------------------|-------------------|--------------------|
| `AddStrokeAction` | Insert stroke | Delete stroke | | `AddStrokeAction` | Insert stroke | Delete stroke |
| `DeleteStrokeAction` | Delete stroke | Re-insert stroke | | `DeleteStrokeAction` | Delete stroke | Re-insert stroke |
| `MoveStrokesAction` | Offset points | Offset back | | `MoveStrokesAction` | Offset points | Restore original points |
| `DeleteMultipleAction`| Delete strokes | Re-insert strokes | | `DeleteMultipleAction`| Delete strokes | Re-insert strokes |
| `CopyStrokesAction` | Duplicate strokes | Delete copies | | `CopyStrokesAction` | Duplicate strokes | Delete copies |
@@ -253,7 +300,8 @@ upgrade):
1. In select mode, stylus drag draws a selection rectangle. 1. In select mode, stylus drag draws a selection rectangle.
2. Strokes whose bounding boxes intersect the rectangle are selected. 2. Strokes whose bounding boxes intersect the rectangle are selected.
3. Selected strokes get a visual highlight (translucent overlay + bounding box). 3. Selected strokes get a visual highlight (translucent overlay + bounding box).
4. Available operations: delete, drag-to-move, copy/paste. 4. Available operations: cut, copy, delete, paste (toolbar buttons appear
contextually when strokes are selected or clipboard is non-empty).
5. All selection operations are undoable. 5. All selection operations are undoable.
## Eraser ## Eraser
@@ -265,6 +313,15 @@ Stroke-level eraser (not pixel-level):
point-by-point distance (threshold: ~42 canonical points / ~3.5mm). point-by-point distance (threshold: ~42 canonical points / ~3.5mm).
3. Hit strokes are deleted immediately (with undo support). 3. Hit strokes are deleted immediately (with undo support).
## Startup State Restoration
The app persists the last-opened notebook ID and a flag for whether the page
list was showing in `SharedPreferences` (via `EngPadApp`). On launch:
- If a notebook was previously open, auto-navigate to it.
- If the page list was showing, navigate to the page list on top.
- The editor resumes at the notebook's `lastPageId` (tracked per-notebook in
the DB).
## E-ink Considerations ## E-ink Considerations
Both target devices have e-ink or e-ink-like displays with high refresh latency: Both target devices have e-ink or e-ink-like displays with high refresh latency:
@@ -276,6 +333,36 @@ Both target devices have e-ink or e-ink-like displays with high refresh latency:
- Consider a manual "refresh" button to force a full-screen redraw to clear - Consider a manual "refresh" button to force a full-screen redraw to clear
e-ink ghosting. e-ink ghosting.
## Sync Architecture (Planned)
eng-pad will sync notebooks to an eng-pad-server instance over gRPC:
- **Protocol**: gRPC with protobuf-lite, over TLS.
- **Authentication**: password-based auth to eng-pad-server (not MCIAS — this
is a personal-use app, not a Metacircular service).
- **Sync model**: manual sync (user-initiated, not automatic). Per-notebook
sync button and sync-all from the library.
- **Conflict resolution**: last-write-wins based on `updated_at` timestamps,
with potential for manual resolution in future.
- **Data flow**: push local changes, pull remote changes. Full notebook
content (metadata + pages + strokes) serialized for transfer.
- **Status tracking**: per-notebook sync state (synced, pending, error)
displayed as badges in the notebook list.
## Backup/Export Design (Planned)
Notebook backup as a portable zip file:
- **Format**: `notebook_title.engpad.zip` containing:
- `notebook.json` — notebook metadata (title, page_size, page count)
- `pages/001.json`, `pages/002.json`, ... — one file per page with
metadata and strokes array
- Each stroke: `{penSize, color, style, points: [x0,y0,x1,y1,...]}`
- Points as JSON float arrays (portable, human-readable)
- **Import**: parse zip, create notebook + pages + strokes in Room DB.
- **Export-all**: single zip containing all notebooks for full backup.
- **Sharing**: via Android share intents (same mechanism as PDF/JPG).
## Source Tree ## Source Tree
``` ```
@@ -286,42 +373,42 @@ eng-pad/
│ ├── main/ │ ├── main/
│ │ ├── AndroidManifest.xml │ │ ├── AndroidManifest.xml
│ │ ├── kotlin/net/metacircular/engpad/ │ │ ├── kotlin/net/metacircular/engpad/
│ │ │ ├── EngPadApp.kt -- Application class │ │ │ ├── EngPadApp.kt -- Application class, SharedPreferences
│ │ │ ├── MainActivity.kt -- Single activity, Compose NavHost │ │ │ ├── MainActivity.kt -- Single activity, Compose NavHost
│ │ │ ├── data/ │ │ │ ├── data/
│ │ │ │ ├── db/ │ │ │ │ ├── db/
│ │ │ │ │ ├── EngPadDatabase.kt -- Room database definition │ │ │ │ │ ├── EngPadDatabase.kt -- Room database (version 3)
│ │ │ │ │ ├── NotebookDao.kt -- Notebook CRUD │ │ │ │ │ ├── NotebookDao.kt -- Notebook CRUD
│ │ │ │ │ ├── PageDao.kt -- Page CRUD │ │ │ │ │ ├── PageDao.kt -- Page CRUD + reorder
│ │ │ │ │ ├── StrokeDao.kt -- Stroke CRUD │ │ │ │ │ ├── StrokeDao.kt -- Stroke CRUD
│ │ │ │ │ └── Converters.kt -- Room type converters │ │ │ │ │ └── Converters.kt -- Room type converters
│ │ │ │ ├── model/ │ │ │ │ ├── model/
│ │ │ │ │ ├── Notebook.kt -- Room entity │ │ │ │ │ ├── Notebook.kt -- Room entity (+ lastPageId)
│ │ │ │ │ ├── Page.kt -- Room entity │ │ │ │ │ ├── Page.kt -- Room entity
│ │ │ │ │ ├── Stroke.kt -- Room entity │ │ │ │ │ ├── Stroke.kt -- Room entity (+ style column)
│ │ │ │ │ └── PageSize.kt -- Enum: REGULAR, LARGE │ │ │ │ │ └── PageSize.kt -- Enum: REGULAR, LARGE
│ │ │ │ └── repository/ │ │ │ │ └── repository/
│ │ │ │ ├── NotebookRepository.kt -- Notebook operations │ │ │ │ ├── NotebookRepository.kt -- Notebook operations + rename
│ │ │ │ └── PageRepository.kt -- Page + stroke operations │ │ │ │ └── PageRepository.kt -- Page + stroke ops + reorder
│ │ │ ├── ui/ │ │ │ ├── ui/
│ │ │ │ ├── navigation/ │ │ │ │ ├── navigation/
│ │ │ │ │ └── NavGraph.kt -- Route definitions │ │ │ │ │ └── NavGraph.kt -- Routes, auto-restore last notebook
│ │ │ │ ├── notebooks/ │ │ │ │ ├── notebooks/
│ │ │ │ │ ├── NotebookListScreen.kt -- Notebook list UI │ │ │ │ │ ├── NotebookListScreen.kt -- Library: list, filter, sort, rename
│ │ │ │ │ └── NotebookListViewModel.kt -- Notebook list state │ │ │ │ │ └── NotebookListViewModel.kt -- Notebook list state + rename
│ │ │ │ ├── pages/ │ │ │ │ ├── pages/
│ │ │ │ │ ├── PageListScreen.kt -- Page grid UI │ │ │ │ │ ├── PageListScreen.kt -- Page grid, drag-to-reorder
│ │ │ │ │ └── PageListViewModel.kt -- Page list state │ │ │ │ │ └── PageListViewModel.kt -- Page list state + reorder
│ │ │ │ ├── editor/ │ │ │ │ ├── editor/
│ │ │ │ │ ├── PadCanvasView.kt -- Custom View: rendering + input │ │ │ │ │ ├── PadCanvasView.kt -- Custom View: rendering + input + edge swipe
│ │ │ │ │ ├── EditorScreen.kt -- Compose wrapper │ │ │ │ │ ├── EditorScreen.kt -- Compose wrapper
│ │ │ │ │ ├── EditorViewModel.kt -- Editor state + persistence │ │ │ │ │ ├── EditorViewModel.kt -- Editor state, page nav, clipboard
│ │ │ │ │ ├── CanvasState.kt -- Zoom, pan, tool, selection │ │ │ │ │ ├── CanvasState.kt -- Tool, PenSize, LineStyle, zoom/pan
│ │ │ │ │ └── Toolbar.kt -- Editor toolbar │ │ │ │ │ └── Toolbar.kt -- Icon-button toolbar with Canvas icons
│ │ │ │ ├── export/ │ │ │ │ ├── export/
│ │ │ │ │ └── PdfExporter.kt -- PDF generation + sharing │ │ │ │ │ └── PdfExporter.kt -- PDF + JPG generation + sharing
│ │ │ │ └── theme/ │ │ │ │ └── theme/
│ │ │ │ └── Theme.kt -- Material3 theme │ │ │ │ └── Theme.kt -- Material3 high-contrast e-ink theme
│ │ │ └── undo/ │ │ │ └── undo/
│ │ │ ├── UndoManager.kt -- Undo/redo stack │ │ │ ├── UndoManager.kt -- Undo/redo stack
│ │ │ ├── UndoableAction.kt -- Action interface │ │ │ ├── UndoableAction.kt -- Action interface
@@ -331,13 +418,14 @@ eng-pad/
│ │ ├── values/ │ │ ├── values/
│ │ │ ├── strings.xml │ │ │ ├── strings.xml
│ │ │ └── themes.xml │ │ │ └── themes.xml
│ │ ├── drawable/ -- Toolbar icons │ │ ├── drawable/ -- Launcher icons
│ │ ├── mipmap-anydpi/ -- Adaptive icon
│ │ └── xml/ │ │ └── xml/
│ │ └── file_provider_paths.xml -- FileProvider config │ │ └── file_provider_paths.xml -- FileProvider config
│ └── test/ │ └── test/
│ └── kotlin/net/metacircular/engpad/ │ └── kotlin/net/metacircular/engpad/
│ ├── data/ │ ├── data/
│ │ ├── StrokeBlobTest.kt -- Float array blob roundtrip │ │ ├── StrokeBlobTest.kt -- Float array <-> blob roundtrip
│ │ └── PageSizeTest.kt -- Page size enum tests │ │ └── PageSizeTest.kt -- Page size enum tests
│ └── undo/ │ └── undo/
│ └── UndoManagerTest.kt -- Undo/redo logic │ └── UndoManagerTest.kt -- Undo/redo logic

View File

@@ -23,7 +23,7 @@ See PROJECT_PLAN.md for the full step list.
- [x] 1.3: Lint configured — warningsAsErrors, AGP version check suppressed - [x] 1.3: Lint configured — warningsAsErrors, AGP version check suppressed
(AGP 9.x needs Gradle 9.x) (AGP 9.x needs Gradle 9.x)
- [x] 1.4: Room entities: Notebook, Page, Stroke, PageSize enum - [x] 1.4: Room entities: Notebook, Page, Stroke, PageSize enum
- [x] 1.5: Converters: FloatArray ByteArray (packed little-endian) - [x] 1.5: Converters: FloatArray <-> ByteArray (packed little-endian)
- [x] 1.6: DAOs: NotebookDao, PageDao, StrokeDao - [x] 1.6: DAOs: NotebookDao, PageDao, StrokeDao
- [x] 1.7: EngPadDatabase (Room, version 1) - [x] 1.7: EngPadDatabase (Room, version 1)
- [x] 1.8: NotebookRepository, PageRepository - [x] 1.8: NotebookRepository, PageRepository
@@ -38,20 +38,20 @@ See PROJECT_PLAN.md for the full step list.
- [x] 2.3: NotebookListScreen — lazy list, create dialog (title + page size radio), - [x] 2.3: NotebookListScreen — lazy list, create dialog (title + page size radio),
long-press delete with confirmation, empty state long-press delete with confirmation, empty state
- [x] 2.4: Auto-create page 1 in NotebookRepository.create() - [x] 2.4: Auto-create page 1 in NotebookRepository.create()
- [x] 2.5: Navigation wired — tap notebook pages stub, editor stub - [x] 2.5: Navigation wired — tap notebook -> pages stub, editor stub
- EngPadTheme: high-contrast light color scheme (black on white for e-ink) - EngPadTheme: high-contrast light color scheme (black on white for e-ink)
### Phase 3: Canvas — Basic Drawing (2026-03-24) ### Phase 3: Canvas — Basic Drawing (2026-03-24)
- [x] 3.13.4: PadCanvasView — stylus input with historical points, Path/Paint - [x] 3.1-3.4: PadCanvasView — stylus input with historical points, Path/Paint
stroke rendering, direct path drawing (no backing bitmap), 60pt grid stroke rendering, direct path drawing (no backing bitmap), 60pt grid
drawn in screen space with pixel-snapped positions, Matrix coordinate drawn in screen space with pixel-snapped positions, Matrix coordinate
transform (canonical screen) transform (canonical <-> screen)
- [x] 3.5: CanvasState — tool enum (PEN_FINE, PEN_MEDIUM, ERASER, SELECT, BOX), - [x] 3.5: CanvasState — tool enum (PEN, LINE, BOX, ERASER, SELECT, MOVE),
zoom/pan state, pen width constants (4.49pt, 5.91pt) zoom/pan state, PenSize enum with 4 sizes
- [x] 3.6: EditorViewModel — loads strokes from Room, saves on completion - [x] 3.6: EditorViewModel — loads strokes from Room, saves on completion
- [x] 3.7: EditorScreen + Toolbar — Compose wrapper with AndroidView, - [x] 3.7: EditorScreen + Toolbar — Compose wrapper with AndroidView,
FilterChip toolbar for pen size and eraser selection icon-button toolbar with long-press menus for pen size and line style
- [x] 3.8: NavGraph updated — pages route auto-navigates to first page's editor, - [x] 3.8: NavGraph updated — pages route auto-navigates to first page's editor,
page size passed through route params page size passed through route params
- Used KTX Canvas extensions (withMatrix, withScale, createBitmap) per lint - Used KTX Canvas extensions (withMatrix, withScale, createBitmap) per lint
@@ -59,7 +59,7 @@ See PROJECT_PLAN.md for the full step list.
### Phase 4: Zoom and Pan (2026-03-24) ### Phase 4: Zoom and Pan (2026-03-24)
- [x] 4.1: ScaleGestureDetector with focal-point zoom (dynamic min4×) - [x] 4.1: ScaleGestureDetector with focal-point zoom (dynamic min-4x)
- [x] 4.2: Finger drag for pan with multi-pointer tracking, pan clamped - [x] 4.2: Finger drag for pan with multi-pointer tracking, pan clamped
so page always fills viewport so page always fills viewport
- [x] 4.3: Input routing by tool type already in place from Phase 3 - [x] 4.3: Input routing by tool type already in place from Phase 3
@@ -69,7 +69,7 @@ See PROJECT_PLAN.md for the full step list.
### Phase 5: Eraser (2026-03-24) ### Phase 5: Eraser (2026-03-24)
- [x] 5.15.3: Stroke-level eraser with bounding box hit test (42pt radius), - [x] 5.1-5.3: Stroke-level eraser with bounding box hit test (42pt radius),
processes historical touch points for thorough erasing, deletes from processes historical touch points for thorough erasing, deletes from
view + Room DB view + Room DB
@@ -84,7 +84,7 @@ See PROJECT_PLAN.md for the full step list.
### Phase 7: Selection — Move, Copy, Delete (2026-03-24) ### Phase 7: Selection — Move, Copy, Delete (2026-03-24)
- [x] 7.17.2: Rectangle selection with dashed rect and blue highlight - [x] 7.1-7.2: Rectangle selection with dashed rect and blue highlight
- [x] 7.3: Delete, drag-to-move, copy operations with toolbar buttons - [x] 7.3: Delete, drag-to-move, copy operations with toolbar buttons
- [x] 7.4: Full undo integration — DeleteMultipleStrokesAction, MoveStrokesAction, - [x] 7.4: Full undo integration — DeleteMultipleStrokesAction, MoveStrokesAction,
CopyStrokesAction CopyStrokesAction
@@ -100,7 +100,7 @@ See PROJECT_PLAN.md for the full step list.
### Phase 9: PDF Export (2026-03-24) ### Phase 9: PDF Export (2026-03-24)
- [x] 9.1: PdfExporter — creates PdfDocument, scales canonical 300 DPI coords - [x] 9.1: PdfExporter — creates PdfDocument, scales canonical 300 DPI coords
to 72 DPI PDF points (×0.24), renders strokes without grid to 72 DPI PDF points (x0.24), renders strokes without grid
- [x] 9.2: Share via Intent.ACTION_SEND + FileProvider (configured in Phase 1) - [x] 9.2: Share via Intent.ACTION_SEND + FileProvider (configured in Phase 1)
- [x] 9.3: PDF button in editor toolbar, exports current page - [x] 9.3: PDF button in editor toolbar, exports current page
@@ -120,8 +120,9 @@ Tested on DC-1, identified and fixed rendering issues:
sub-pixel positioning when scaled. Fixed by drawing grid in screen space sub-pixel positioning when scaled. Fixed by drawing grid in screen space
with pixel-snapped line positions. Now uniform squares. with pixel-snapped line positions. Now uniform squares.
- **Stroke quality**: original backing bitmap at 1/4 resolution caused blurry - **Stroke quality**: original backing bitmap at 1/4 resolution caused blurry
strokes. Fixed by removing backing bitmap entirely and drawing paths directly. strokes. Fixed by switching to a screen-resolution backing bitmap with
Anti-aliasing disabled on all paint for crisp e-ink lines. incremental stroke addition. Anti-aliasing disabled on all paint for
crisp e-ink lines.
- **Viewport**: dark gray background was visible around page edges. Fixed by - **Viewport**: dark gray background was visible around page edges. Fixed by
clamping pan to page edges, computing dynamic min zoom, and white background. clamping pan to page edges, computing dynamic min zoom, and white background.
- **Box tool**: works correctly on device. - **Box tool**: works correctly on device.
@@ -143,11 +144,55 @@ Critical/high-priority fixes applied:
fall back to first page instead of showing blank canvas. fall back to first page instead of showing blank canvas.
- **Arrow head Paint allocation**: preallocated reusable Paint object - **Arrow head Paint allocation**: preallocated reusable Paint object
instead of allocating per drawArrowHeads() call. instead of allocating per drawArrowHeads() call.
- **CopyStrokesAction**: fixed `takeLast(n)` fragile ID lookup — now
tracks copied stroke IDs directly.
- **Grid caching**: grid bitmap is now cached and only redrawn on
zoom/pan/resize changes.
Known issues still to address: ### Post-Phase 10: Feature Polish (2026-03-24)
- CopyStrokesAction uses `takeLast(n)` to find new stroke IDs — fragile
- No mutex guarding concurrent page navigation + paste operations Extensive feature work after the initial 10-phase plan:
- Grid redrawn every frame (could be cached)
- **Toolbar redesign**: replaced FilterChip toolbar with icon-button toolbar.
Custom Canvas-drawn icons for each tool (pen dot, line, box outline,
eraser, dashed-rect select, four-way-arrow move, curved undo/redo arrows).
- **Pen size consolidation**: expanded from 2 sizes to 4 sizes (0.38mm,
0.5mm, 0.6mm, 0.7mm). Single PEN tool with long-press for size selection
dropdown; remembers last size.
- **Line tool**: dedicated LINE tool (separate from PEN) with style variants
via long-press menu: plain, arrow, double-arrow, dashed. Line style stored
in stroke `style` column in DB.
- **Box tool**: draws rectangles corner-to-corner. Uses current pen size
and style.
- **Move tool**: dedicated MOVE tool for tap-and-drag stroke relocation
(separate from select-then-drag).
- **Edge swipe navigation**: finger swipe from left/right screen edge
navigates to previous/next page. Auto-creates new page on forward swipe
past last page (if current page has strokes). Edge zone = 8% of screen
width, minimum 100px horizontal distance.
- **Page list drag-to-reorder**: long-press drag in PageListScreen to
reorder pages. Uses `longPressDraggableHandle` with reorderable lazy
column. Page numbers renumbered sequentially on drop.
- **Notebook rename**: long-press notebook in library list to rename.
Rename dialog with text field.
- **Library filter/sort**: search field to filter notebooks by title,
sort by last-edited or title (ascending/descending).
- **JPG export**: single-page JPEG export at 300 DPI via Export dropdown
menu. Uses `Bitmap.compress(JPEG, 95)`, shared via FileProvider + intents.
- **Clipboard operations**: cut, copy, paste toolbar buttons appear when
strokes are selected or clipboard is non-empty. Cut = copy + delete.
- **Startup state restoration**: SharedPreferences in EngPadApp store last
opened notebook ID and view-all-pages flag. On launch, auto-navigates to
last notebook (or page list if that was the last screen).
- **Page navigation from editor**: binder button in toolbar shows
"p1/5"-style page indicator with dropdown for "View all pages" and
"Go to page..." dialog.
- **Last page tracking**: Notebook entity has `lastPageId` column; editor
resumes at last-visited page when reopening a notebook.
- **Page delete**: delete pages from PageListScreen with sequential
renumbering of remaining pages.
- **Database migrations**: schema at version 3 (added `style` column to
strokes, `last_page_id` to notebooks).
## Backup Design (Not Yet Implemented) ## Backup Design (Not Yet Implemented)
@@ -168,11 +213,12 @@ portable or human-readable).
## Status ## Status
All implementation phases complete. Remaining work: All implementation phases complete. Post-phase polish significantly extended
the feature set beyond the original plan. Remaining work:
- Test on Supernote Manta - Test on Supernote Manta
- Verify line snap behavior on device
- Performance profiling with heavily annotated pages - Performance profiling with heavily annotated pages
- Implement notebook backup/restore - Implement notebook backup/restore (zip format)
- Server sync integration (eng-pad-server, gRPC, manual sync)
## Decisions & Deviations ## Decisions & Deviations
@@ -183,3 +229,11 @@ All implementation phases complete. Remaining work:
- **Coordinate system**: 300 DPI canonical points (scaled to 72 DPI for PDF export). - **Coordinate system**: 300 DPI canonical points (scaled to 72 DPI for PDF export).
- **UI framework**: Compose for chrome, custom View for canvas (Compose - **UI framework**: Compose for chrome, custom View for canvas (Compose
Canvas lacks MotionEvent access needed for stylus input). Canvas lacks MotionEvent access needed for stylus input).
- **Toolbar**: Icon buttons with Canvas-drawn icons (no drawable resources needed
for tool icons). Long-press for sub-menus (pen size, line style).
- **Rendering**: Screen-resolution backing bitmap with incremental stroke
addition + cached grid bitmap. Both invalidated on zoom/pan/resize.
- **Line styles**: Stored as string in stroke `style` DB column (plain, dashed,
arrow, double_arrow) rather than a separate table.
- **State restoration**: SharedPreferences for last notebook/page rather than
Compose SavedStateHandle (survives process death).

View File

@@ -21,7 +21,7 @@ completed and log them in PROGRESS.md.
- [x] 1.4: Define Room entities - [x] 1.4: Define Room entities
- `data/model/Notebook.kt`, `Page.kt`, `Stroke.kt`, `PageSize.kt` - `data/model/Notebook.kt`, `Page.kt`, `Stroke.kt`, `PageSize.kt`
- [x] 1.5: Implement type converters - [x] 1.5: Implement type converters
- `data/db/Converters.kt``FloatArray` `ByteArray` - `data/db/Converters.kt``FloatArray` <-> `ByteArray`
- [x] 1.6: Define DAOs - [x] 1.6: Define DAOs
- `data/db/NotebookDao.kt`, `PageDao.kt`, `StrokeDao.kt` - `data/db/NotebookDao.kt`, `PageDao.kt`, `StrokeDao.kt`
- [x] 1.7: Define Room database - [x] 1.7: Define Room database
@@ -43,30 +43,30 @@ completed and log them in PROGRESS.md.
- `ui/notebooks/NotebookListScreen.kt` — list, create dialog, delete confirmation - `ui/notebooks/NotebookListScreen.kt` — list, create dialog, delete confirmation
- [x] 2.4: Auto-create page 1 on notebook creation - [x] 2.4: Auto-create page 1 on notebook creation
- In `NotebookRepository.create()` - In `NotebookRepository.create()`
- [x] 2.5: Navigation: tap notebook page list (stub screen) - [x] 2.5: Navigation: tap notebook -> page list (stub screen)
- **Verify:** `./gradlew build` — PASSED (build + test + lint) - **Verify:** `./gradlew build` — PASSED (build + test + lint)
## Phase 3: Canvas — Basic Drawing ## Phase 3: Canvas — Basic Drawing
- [x] 3.1: Implement `PadCanvasView` — stylus event handling - [x] 3.1: Implement `PadCanvasView` — stylus event handling
- `ui/editor/PadCanvasView.kt``onTouchEvent`, `getHistoricalX/Y` - `ui/editor/PadCanvasView.kt``onTouchEvent`, `getHistoricalX/Y`
- [x] 3.2: Stroke rendering — `Path`/`Paint`, backing bitmap (1/4 resolution) - [x] 3.2: Stroke rendering — `Path`/`Paint`, backing bitmap (screen resolution)
- [x] 3.3: Grid drawing — 60pt spacing, drawn on screen only - [x] 3.3: Grid drawing — 60pt spacing, drawn on screen only
- [x] 3.4: Coordinate transform — canonical screen via `Matrix` - [x] 3.4: Coordinate transform — canonical <-> screen via `Matrix`
- [x] 3.5: Implement `CanvasState` - [x] 3.5: Implement `CanvasState`
- `ui/editor/CanvasState.kt` — zoom, pan, active tool, pen widths - `ui/editor/CanvasState.kt` — zoom, pan, active tool, pen sizes, line styles
- [x] 3.6: Implement `EditorViewModel` - [x] 3.6: Implement `EditorViewModel`
- `ui/editor/EditorViewModel.kt` — load/save strokes from Room - `ui/editor/EditorViewModel.kt` — load/save strokes from Room
- [x] 3.7: Implement `EditorScreen` + toolbar - [x] 3.7: Implement `EditorScreen` + toolbar
- `ui/editor/EditorScreen.kt`, `ui/editor/Toolbar.kt` - `ui/editor/EditorScreen.kt`, `ui/editor/Toolbar.kt`
- [x] 3.8: Wire navigation — notebook list pages editor (pages auto-navigates to first page) - [x] 3.8: Wire navigation — notebook list -> pages -> editor (pages auto-navigates to first page)
- **Verify:** `./gradlew build` — PASSED (build + test + lint). Manual on-device test pending. - **Verify:** `./gradlew build` — PASSED (build + test + lint). Manual on-device test pending.
## Phase 4: Zoom and Pan ## Phase 4: Zoom and Pan
- [x] 4.1: `ScaleGestureDetector` for pinch-to-zoom (0.5×4×) with focal-point zoom - [x] 4.1: `ScaleGestureDetector` for pinch-to-zoom (0.5x-4x) with focal-point zoom
- [x] 4.2: Finger drag for pan with pointer tracking - [x] 4.2: Finger drag for pan with pointer tracking
- [x] 4.3: Input routing — `TOOL_TYPE_STYLUS` draw, `TOOL_TYPE_FINGER` zoom/pan - [x] 4.3: Input routing — `TOOL_TYPE_STYLUS` -> draw, `TOOL_TYPE_FINGER` -> zoom/pan
- **Verify:** `./gradlew build` — PASSED. Manual on-device test pending. - **Verify:** `./gradlew build` — PASSED. Manual on-device test pending.
## Phase 5: Eraser ## Phase 5: Eraser
@@ -99,12 +99,12 @@ completed and log them in PROGRESS.md.
- [x] 8.1: `PageListScreen` with page grid (aspect-ratio cards) - [x] 8.1: `PageListScreen` with page grid (aspect-ratio cards)
- `ui/pages/PageListScreen.kt`, `PageListViewModel.kt` - `ui/pages/PageListScreen.kt`, `PageListViewModel.kt`
- [x] 8.2: Add new page FAB - [x] 8.2: Add new page FAB
- [x] 8.3: NavGraph updated — pages route shows PageListScreen, tap page editor - [x] 8.3: NavGraph updated — pages route shows PageListScreen, tap page -> editor
- **Verify:** `./gradlew build` — PASSED - **Verify:** `./gradlew build` — PASSED
## Phase 9: PDF Export ## Phase 9: PDF Export
- [x] 9.1: `PdfExporter` — Android `PdfDocument` API with 30072 DPI scaling - [x] 9.1: `PdfExporter` — Android `PdfDocument` API with 300->72 DPI scaling
- `ui/export/PdfExporter.kt` - `ui/export/PdfExporter.kt`
- [x] 9.2: Share via `Intent.ACTION_SEND` + `FileProvider` - [x] 9.2: Share via `Intent.ACTION_SEND` + `FileProvider`
- `res/xml/file_provider_paths.xml` already configured in Phase 1 - `res/xml/file_provider_paths.xml` already configured in Phase 1
@@ -117,3 +117,31 @@ completed and log them in PROGRESS.md.
- [x] 10.2: Auto-save — strokes save on completion (since Phase 3) - [x] 10.2: Auto-save — strokes save on completion (since Phase 3)
- [x] 10.3: Empty states (notebook list, page list), delete confirmations (notebooks) - [x] 10.3: Empty states (notebook list, page list), delete confirmations (notebooks)
- [ ] 10.4: Performance profiling on target devices (requires on-device testing) - [ ] 10.4: Performance profiling on target devices (requires on-device testing)
## Phase 11: Server Sync Integration
- [ ] 11.1: Proto setup — add `protobuf-lite` dependency, generate Kotlin
stubs from eng-pad-server proto definitions
- [ ] 11.2: Implement `SyncClient` — gRPC client for eng-pad-server
(password auth over TLS, notebook/page/stroke push/pull)
- [ ] 11.3: Implement `SyncManager` — orchestrates sync operations, conflict
resolution (last-write-wins or manual), tracks sync state per notebook
- [ ] 11.4: Sync settings UI — server URL, credentials, TLS CA cert
configuration screen
- [ ] 11.5: Per-notebook sync button — manual sync trigger from editor or
page list toolbar
- [ ] 11.6: Sync-all — bulk sync from notebook list screen
- [ ] 11.7: Status indicators — sync state badges on notebook list items
(synced, pending, error)
## Phase 12: Notebook Backup/Export
- [ ] 12.1: Define zip format — `notebook_title.engpad.zip` with
`notebook.json` metadata and `pages/NNN.json` stroke data
- [ ] 12.2: Implement export — serialize notebook + pages + strokes to zip,
share via FileProvider + intents
- [ ] 12.3: Implement import — parse zip, create notebook + pages + strokes
in Room DB
- [ ] 12.4: Export-all — single zip containing all notebooks for full backup
- [ ] 12.5: UI integration — export/import buttons in notebook list and
editor toolbar