From 47778222b7bbb18bbc8301900fd25282baadd455 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 24 Mar 2026 13:42:44 -0700 Subject: [PATCH] Add project documentation: design spec, implementation plan, and progress tracking Initial project setup with README, CLAUDE.md (AI dev context), DESIGN.md (full technical design covering architecture, data model, coordinate system, rendering strategy), PROJECT_PLAN.md (phased implementation steps), and PROGRESS.md (completion tracking). Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 82 ++++++++++++ DESIGN.md | 334 ++++++++++++++++++++++++++++++++++++++++++++++++ PROGRESS.md | 30 +++++ PROJECT_PLAN.md | 120 +++++++++++++++++ README.md | 27 ++++ 5 files changed, 593 insertions(+) create mode 100644 CLAUDE.md create mode 100644 DESIGN.md create mode 100644 PROGRESS.md create mode 100644 PROJECT_PLAN.md create mode 100644 README.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..773a56d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,82 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +eng-pad is an Android note-taking app built around notebooks with an EMR pen as the primary input method. Target devices are the Supernote Manta (Android 11) and Daylight DC-1 (Android 13). Minimum API level is Android 11 (API 30). + +## Build Commands + +```bash +./gradlew build # Compile everything +./gradlew test # Run unit tests +./gradlew lint # Run Android Lint +./gradlew installDebug # Install debug APK to connected device + +# Run a single test class: +./gradlew test --tests "net.metacircular.engpad.data.StrokeBlobTest" +``` + +## Architecture + +- **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 +- **Room** (SQLite) for persistence — notebooks, pages, strokes +- **Single Activity** with Compose Navigation (three screens: notebook list → page list → editor) +- **Coordinate system**: 300 DPI canonical points. Regular page = 2550×3300pt, large = 3300×5100pt. Scaled to 72 DPI (×0.24) for PDF export. +- **Input dispatch**: `TOOL_TYPE_STYLUS` → draw/erase/select, `TOOL_TYPE_FINGER` → zoom/pan + +## Project Documents + +- **DESIGN.md** — Full technical design (architecture, data model, rendering, source tree) +- **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. + +**Keep PROJECT_PLAN.md and PROGRESS.md in sync.** When a step is completed, check it off in PROJECT_PLAN.md and log it in PROGRESS.md. When files are added, update the source tree in both DESIGN.md and this file. + +## Source Tree + +``` +eng-pad/ +├── app/ +│ ├── build.gradle.kts -- Module build config +│ └── src/ +│ ├── main/ +│ │ ├── AndroidManifest.xml +│ │ ├── kotlin/net/metacircular/engpad/ +│ │ │ ├── EngPadApp.kt -- Application class +│ │ │ ├── MainActivity.kt -- Single activity, Compose NavHost +│ │ │ ├── data/ +│ │ │ │ ├── db/ -- Room database, DAOs, converters +│ │ │ │ ├── model/ -- Entities: Notebook, Page, Stroke, PageSize +│ │ │ │ └── repository/ -- NotebookRepository, PageRepository +│ │ │ ├── ui/ +│ │ │ │ ├── navigation/NavGraph.kt -- Route definitions +│ │ │ │ ├── notebooks/ -- List screen + ViewModel +│ │ │ │ ├── pages/ -- Page grid screen + ViewModel +│ │ │ │ ├── editor/ -- PadCanvasView, EditorScreen, ViewModel +│ │ │ │ ├── export/PdfExporter.kt -- PDF generation + sharing +│ │ │ │ └── theme/Theme.kt -- Material3 theme +│ │ │ └── undo/ -- UndoManager, UndoableAction +│ │ └── res/ +│ └── test/ -- Unit tests +├── build.gradle.kts -- Root build config +├── settings.gradle.kts +├── gradle.properties +├── gradle/libs.versions.toml -- Version catalog +├── CLAUDE.md -- This file +├── README.md +├── DESIGN.md -- Technical design +├── PROJECT_PLAN.md -- Implementation steps +└── PROGRESS.md -- Completion tracking +``` + +## Key Conventions + +- 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`. +- Grid spacing: 60pt (~5 squares/inch). Grid is drawn on screen, excluded from exports. +- Pen widths: 0.38mm = 4.49pt, 0.5mm = 5.91pt (at 300 DPI). +- Two pen sizes only, no pressure sensitivity. +- Hardware palm rejection only (EMR digitizer). diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..c70ba66 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,334 @@ +# DESIGN.md — eng-pad Technical Design + +## Overview + +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 +PDF export. Target devices are the Supernote Manta (Android 11) and the +Daylight DC-1 (Android 13). + +## Architecture + +**Kotlin + Jetpack Compose + Custom Canvas View.** + +The app uses a single-Activity architecture with Jetpack Compose for UI +chrome (notebook list, page list, toolbar, dialogs) and Compose Navigation +for screen transitions. The drawing surface is a custom `View` subclass +(`PadCanvasView`) hosted inside Compose via `AndroidView` — Compose's +`Canvas` composable does not provide direct access to `MotionEvent` dispatch +or hardware-accelerated `Path` rendering, both of which are critical for +low-latency stylus input. + +### Screen Flow + +``` +NotebookListScreen → PageListScreen → EditorScreen + | | | + Create/delete Page grid, PadCanvasView + + notebooks add pages Toolbar (Compose) +``` + +Three Compose navigation destinations: + +| Route | Screen | Purpose | +|------------------------|--------------------|--------------------------------| +| `notebooks` | NotebookListScreen | List, create, delete notebooks | +| `pages/{notebookId}` | PageListScreen | Page thumbnails, add pages | +| `editor/{pageId}` | EditorScreen | Drawing canvas + toolbar | + +### Dependency Stack + +| Layer | Technology | Purpose | +|--------------|-----------------------------|---------------------------------| +| UI | Jetpack Compose + Material3 | Screens, toolbar, dialogs | +| Drawing | Custom View + Canvas API | Stroke rendering, input | +| State | ViewModel + StateFlow | Reactive UI state | +| Persistence | Room (SQLite) | Notebooks, pages, strokes | +| Async | Kotlin Coroutines | Background DB operations | +| Navigation | Compose Navigation | Screen routing | +| Export | Android PdfDocument API | PDF generation | +| Share | FileProvider + Intents | PDF sharing (Dropbox, etc.) | + +No external drawing libraries. The Android `Canvas` + `Path` + `Paint` API +is sufficient and avoids dependencies that may behave unpredictably on e-ink +displays. + +## Coordinate System + +All stroke data is stored in **canonical coordinates at 300 points per inch** +for high precision with fine pen strokes. Coordinates are scaled to 72 DPI +during PDF export (multiply by 72/300 = 0.24). + +| Page Size | Inches | Canonical Points | +|-----------|----------|-------------------| +| Regular | 8.5 × 11 | 2550 × 3300 | +| Large | 11 × 17 | 3300 × 5100 | + +### 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 +export and any other output. + +### Pen Sizes + +Two fixed stroke widths, corresponding to Muji gel ink ballpoints: + +| Pen | Millimeters | Canonical Points | +|-------|-------------|------------------| +| Fine | 0.38 mm | 4.49 pt | +| Medium| 0.50 mm | 5.91 pt | + +No pressure sensitivity — stroke width is uniform for a given pen size. + +### Screen Transform + +A `Matrix` maps canonical coordinates to screen pixels. The matrix encodes +the current zoom level and pan offset. All stylus input coordinates are +transformed through the inverse matrix before being stored, ensuring strokes +are always in canonical space regardless of zoom. + +``` +canonical → [viewMatrix] → screen pixels +screen pixels → [inverseMatrix] → canonical +``` + +Zoom range: 0.5× to 4×. Pan is clamped so the page cannot scroll entirely +off-screen. + +## Data Model + +### SQLite Schema (Room) + +```sql +CREATE TABLE notebooks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + page_size TEXT NOT NULL CHECK(page_size IN ('regular', 'large')), + created_at INTEGER NOT NULL, -- epoch millis + updated_at INTEGER NOT NULL -- epoch millis +); + +CREATE TABLE pages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + notebook_id INTEGER NOT NULL REFERENCES notebooks(id) ON DELETE CASCADE, + page_number INTEGER NOT NULL, + created_at INTEGER NOT NULL, + UNIQUE(notebook_id, page_number) +); + +CREATE TABLE strokes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE, + pen_size REAL NOT NULL, -- width in canonical points + color INTEGER NOT NULL, -- ARGB packed int + point_data BLOB NOT NULL, -- packed floats: [x0,y0,x1,y1,...] + stroke_order INTEGER NOT NULL, -- z-order within the page + created_at INTEGER NOT NULL +); + +CREATE INDEX idx_strokes_page ON strokes(page_id); +CREATE INDEX idx_pages_notebook ON pages(notebook_id); +``` + +### Stroke Point Encoding + +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 +bytes. A typical stroke of 200 points = 1600 bytes. This is ~10× more +compact than JSON and eliminates parsing overhead. + +```kotlin +// Encode +fun FloatArray.toBlob(): ByteArray { + val buf = ByteBuffer.allocate(size * 4).order(ByteOrder.LITTLE_ENDIAN) + for (f in this) buf.putFloat(f) + return buf.array() +} + +// Decode +fun ByteArray.toFloatArray(): FloatArray { + val buf = ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN) + return FloatArray(size / 4) { buf.getFloat() } +} +``` + +## Input Handling + +### Tool Type Dispatch + +The EMR digitizer reports stylus events as `TOOL_TYPE_STYLUS` and finger +touches as `TOOL_TYPE_FINGER`. This provides a clean input split without +software palm rejection: + +| Tool Type | Action | +|------------------|-------------------------------------| +| `TOOL_TYPE_STYLUS` | Draw, erase, or select (per mode) | +| `TOOL_TYPE_FINGER` | Pinch-to-zoom, pan | + +### Historical Points + +The Wacom EMR digitizer batches events. To capture all intermediate points +for smooth strokes, `PadCanvasView` processes `MotionEvent.getHistoricalX/Y` +on every `ACTION_MOVE`, not just the current event coordinates. + +### Modes + +The editor toolbar controls the active mode: + +| Mode | Stylus Behavior | +|----------|------------------------------------------------------| +| 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 | + +## Rendering Strategy + +### On-Screen + +1. **Completed strokes** are rendered to a backing `Bitmap`. This bitmap is + redrawn only when strokes change (add, delete, move). +2. **In-progress stroke** (pen is down) is drawn directly to the canvas on + each `onDraw` call, on top of the backing bitmap. +3. **Grid** is drawn as a separate pass — thin gray lines at 14.4pt intervals. +4. The view `Matrix` transforms everything from canonical to screen space. + +This approach means `onDraw` is fast for the common case (pen moving): +draw the cached bitmap + one active path + grid. + +### PDF Export + +Uses Android's `PdfDocument` API. `PdfDocument` pages use 1/72-inch units, +so stroke coordinates are scaled by 72/300 = 0.24 during export. A `Matrix` +pre-concatenated with the export canvas handles this. The grid drawing pass +is skipped. + +Export flow: +1. Create `PdfDocument`. +2. For each page: add a `PdfDocument.Page` with canonical dimensions, + draw all strokes using the same `Path`/`Paint` rendering code. +3. Write to a temp file in the app's cache directory. +4. Share via `Intent.ACTION_SEND` with a `FileProvider` URI. + +## Undo/Redo + +Command pattern with a depth limit of 50: + +| Action | Execute | Undo | +|----------------------|-------------------|--------------------| +| `AddStrokeAction` | Insert stroke | Delete stroke | +| `DeleteStrokeAction` | Delete stroke | Re-insert stroke | +| `MoveStrokesAction` | Offset points | Offset back | +| `DeleteMultipleAction`| Delete strokes | Re-insert strokes | + +The `UndoManager` maintains two stacks. Performing a new action clears the +redo stack. Each action also persists or deletes from Room as part of +execute/undo. + +## Selection + +Rectangle selection (initial implementation; lasso selection as a future +upgrade): + +1. In select mode, stylus drag draws a selection rectangle. +2. Strokes whose bounding boxes intersect the rectangle are selected. +3. Selected strokes get a visual highlight (translucent overlay + bounding box). +4. Available operations: delete, drag-to-move, copy/paste. +5. All selection operations are undoable. + +## Eraser + +Stroke-level eraser (not pixel-level): + +1. In erase mode, stylus touch/drag hit-tests against all strokes on the page. +2. Hit test: check stroke bounding box first (fast reject), then check + point-by-point distance (threshold: ~42 canonical points / ~3.5mm). +3. Hit strokes are deleted immediately (with undo support). +4. Backing bitmap is rebuilt after deletion. + +## E-ink Considerations + +Both target devices have e-ink or e-ink-like displays with high refresh latency: + +- Minimize animations and transitions. +- Use high-contrast colors (black on white). +- Minimize invalidation regions (invalidate only the bounding box of new + stroke segments, not the full canvas). +- Consider a manual "refresh" button to force a full-screen redraw to clear + e-ink ghosting. + +## Source Tree + +``` +eng-pad/ +├── app/ +│ ├── build.gradle.kts -- Module build config +│ └── src/ +│ ├── main/ +│ │ ├── AndroidManifest.xml +│ │ ├── kotlin/net/metacircular/engpad/ +│ │ │ ├── EngPadApp.kt -- Application class +│ │ │ ├── MainActivity.kt -- Single activity, Compose NavHost +│ │ │ ├── data/ +│ │ │ │ ├── db/ +│ │ │ │ │ ├── EngPadDatabase.kt -- Room database definition +│ │ │ │ │ ├── NotebookDao.kt -- Notebook CRUD +│ │ │ │ │ ├── PageDao.kt -- Page CRUD +│ │ │ │ │ ├── StrokeDao.kt -- Stroke CRUD +│ │ │ │ │ └── Converters.kt -- Room type converters +│ │ │ │ ├── model/ +│ │ │ │ │ ├── Notebook.kt -- Room entity +│ │ │ │ │ ├── Page.kt -- Room entity +│ │ │ │ │ ├── Stroke.kt -- Room entity +│ │ │ │ │ └── PageSize.kt -- Enum: REGULAR, LARGE +│ │ │ │ └── repository/ +│ │ │ │ ├── NotebookRepository.kt -- Notebook operations +│ │ │ │ └── PageRepository.kt -- Page + stroke operations +│ │ │ ├── ui/ +│ │ │ │ ├── navigation/ +│ │ │ │ │ └── NavGraph.kt -- Route definitions +│ │ │ │ ├── notebooks/ +│ │ │ │ │ ├── NotebookListScreen.kt -- Notebook list UI +│ │ │ │ │ └── NotebookListViewModel.kt -- Notebook list state +│ │ │ │ ├── pages/ +│ │ │ │ │ ├── PageListScreen.kt -- Page grid UI +│ │ │ │ │ └── PageListViewModel.kt -- Page list state +│ │ │ │ ├── editor/ +│ │ │ │ │ ├── PadCanvasView.kt -- Custom View: rendering + input +│ │ │ │ │ ├── EditorScreen.kt -- Compose wrapper +│ │ │ │ │ ├── EditorViewModel.kt -- Editor state + persistence +│ │ │ │ │ ├── CanvasState.kt -- Zoom, pan, tool, selection +│ │ │ │ │ └── Toolbar.kt -- Editor toolbar +│ │ │ │ ├── export/ +│ │ │ │ │ └── PdfExporter.kt -- PDF generation + sharing +│ │ │ │ └── theme/ +│ │ │ │ └── Theme.kt -- Material3 theme +│ │ │ └── undo/ +│ │ │ ├── UndoManager.kt -- Undo/redo stack +│ │ │ └── UndoableAction.kt -- Action interface + impls +│ │ └── res/ +│ │ ├── values/ +│ │ │ ├── strings.xml +│ │ │ └── themes.xml +│ │ ├── drawable/ -- Toolbar icons +│ │ └── xml/ +│ │ └── file_provider_paths.xml -- FileProvider config +│ └── test/ +│ └── kotlin/net/metacircular/engpad/ +│ ├── data/ +│ │ ├── StrokeBlobTest.kt -- Float array ↔ blob roundtrip +│ │ └── RepositoryTest.kt -- CRUD + cascade delete +│ └── undo/ +│ └── UndoManagerTest.kt -- Undo/redo logic +├── build.gradle.kts -- Root build config +├── settings.gradle.kts -- Project settings +├── gradle.properties -- Gradle properties +├── gradle/ +│ └── libs.versions.toml -- Version catalog +├── .gitignore +├── CLAUDE.md +├── README.md +├── DESIGN.md -- This file +├── PROJECT_PLAN.md -- Implementation steps +└── PROGRESS.md -- Completion tracking +``` diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..6e40d24 --- /dev/null +++ b/PROGRESS.md @@ -0,0 +1,30 @@ +# PROGRESS.md — eng-pad Implementation Progress + +This file tracks completed work and decisions. Updated after every step. +See PROJECT_PLAN.md for the full step list. + +## Completed + +### Phase 0: Project Documents (2026-03-24) + +- [x] 0.1: Created DESIGN.md — full technical design covering architecture, + data model, coordinate system, rendering, input handling, and source tree. +- [x] 0.2: Created PROJECT_PLAN.md — discrete steps grouped by phase with + checkboxes and file references. +- [x] 0.3: Created PROGRESS.md — this file. +- [x] 0.4: Updated CLAUDE.md — build commands, source tree, project doc + pointers. + +## In Progress + +Phase 1: Project Skeleton + Data Layer + +## Decisions & Deviations + +- **Language**: Kotlin (chosen over Java for better Compose/coroutine support). +- **Storage**: Room/SQLite with packed float BLOBs for stroke points. +- **Palm rejection**: Hardware only (EMR digitizer handles it). +- **Pressure sensitivity**: None — two fixed pen sizes (0.38mm, 0.5mm). +- **Coordinate system**: 300 DPI canonical points (scaled to 72 DPI for PDF export). +- **UI framework**: Compose for chrome, custom View for canvas (Compose + Canvas lacks MotionEvent access needed for stylus input). diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md new file mode 100644 index 0000000..bb0b5f1 --- /dev/null +++ b/PROJECT_PLAN.md @@ -0,0 +1,120 @@ +# PROJECT_PLAN.md — eng-pad Implementation Steps + +This file tracks all implementation steps. Check off steps as they are +completed and log them in PROGRESS.md. + +## Phase 0: Project Documents + +- [x] 0.1: Create DESIGN.md +- [x] 0.2: Create PROJECT_PLAN.md (this file) +- [x] 0.3: Create PROGRESS.md +- [x] 0.4: Update CLAUDE.md with build commands and source tree + +## Phase 1: Project Skeleton + Data Layer + +- [ ] 1.1: Generate Android project with Gradle + - `build.gradle.kts` (root), `app/build.gradle.kts`, `settings.gradle.kts` + - Kotlin, Compose, Room KSP, minSdk 30, targetSdk 34 +- [ ] 1.2: Configure `gradle/libs.versions.toml` version catalog + - Compose BOM, Room, Navigation, Lifecycle, Coroutines +- [ ] 1.3: Configure linting (`app/build.gradle.kts` Android Lint config) +- [ ] 1.4: Define Room entities + - `data/model/Notebook.kt`, `Page.kt`, `Stroke.kt`, `PageSize.kt` +- [ ] 1.5: Implement type converters + - `data/db/Converters.kt` — `FloatArray` ↔ `ByteArray`, `PageSize` ↔ `String` +- [ ] 1.6: Define DAOs + - `data/db/NotebookDao.kt`, `PageDao.kt`, `StrokeDao.kt` +- [ ] 1.7: Define Room database + - `data/db/EngPadDatabase.kt` +- [ ] 1.8: Implement repositories + - `data/repository/NotebookRepository.kt`, `PageRepository.kt` +- [ ] 1.9: Unit tests + - `test/.../data/StrokeBlobTest.kt` — blob roundtrip + - `test/.../data/RepositoryTest.kt` — CRUD, cascade delete +- **Verify:** `./gradlew build && ./gradlew test && ./gradlew lint` + +## Phase 2: Notebook List Screen + +- [ ] 2.1: Set up `MainActivity` with Compose and `NavHost` + - `MainActivity.kt`, `ui/navigation/NavGraph.kt`, `ui/theme/Theme.kt` +- [ ] 2.2: Implement `NotebookListViewModel` + - `ui/notebooks/NotebookListViewModel.kt` +- [ ] 2.3: Implement `NotebookListScreen` + - `ui/notebooks/NotebookListScreen.kt` — list, create dialog, delete +- [ ] 2.4: Auto-create page 1 on notebook creation + - In `NotebookRepository` or ViewModel +- [ ] 2.5: Navigation: tap notebook → page list (stub screen) +- **Verify:** `./gradlew build && ./gradlew test` + +## Phase 3: Canvas — Basic Drawing + +- [ ] 3.1: Implement `PadCanvasView` — stylus event handling + - `ui/editor/PadCanvasView.kt` — `onTouchEvent`, `getHistoricalX/Y` +- [ ] 3.2: Stroke rendering — `Path`/`Paint`, backing bitmap +- [ ] 3.3: Grid drawing — 14.4pt spacing, toggleable +- [ ] 3.4: Coordinate transform — canonical ↔ screen via `Matrix` +- [ ] 3.5: Implement `CanvasState` + - `ui/editor/CanvasState.kt` — zoom, pan, active tool +- [ ] 3.6: Implement `EditorViewModel` + - `ui/editor/EditorViewModel.kt` — load/save strokes from Room +- [ ] 3.7: Implement `EditorScreen` + toolbar + - `ui/editor/EditorScreen.kt`, `ui/editor/Toolbar.kt` +- [ ] 3.8: Wire navigation from notebook/page list to editor +- **Verify:** `./gradlew build && ./gradlew test` + manual on-device test + +## Phase 4: Zoom and Pan + +- [ ] 4.1: `ScaleGestureDetector` for pinch-to-zoom (0.5×–4×) +- [ ] 4.2: Finger drag for pan with bounds clamping +- [ ] 4.3: Input routing — stylus → draw, finger → zoom/pan +- **Verify:** manual on-device test + +## Phase 5: Eraser + +- [ ] 5.1: Eraser mode in `CanvasState` +- [ ] 5.2: Hit testing — bounding box pre-filter + point distance +- [ ] 5.3: Stroke deletion + backing bitmap rebuild +- **Verify:** `./gradlew test` + manual test + +## Phase 6: Undo/Redo + +- [ ] 6.1: `UndoableAction` interface + concrete actions + - `undo/UndoableAction.kt` +- [ ] 6.2: `UndoManager` — undo/redo stacks, depth 50 + - `undo/UndoManager.kt` +- [ ] 6.3: Integrate with `EditorViewModel` + toolbar +- [ ] 6.4: Unit tests — `test/.../undo/UndoManagerTest.kt` +- **Verify:** `./gradlew test` + manual test + +## Phase 7: Selection — Move, Copy, Delete + +- [ ] 7.1: Selection mode — rectangle selection +- [ ] 7.2: Visual feedback — highlight, bounding box +- [ ] 7.3: Operations — delete, drag-to-move, copy/paste +- [ ] 7.4: Undo integration for all selection operations +- **Verify:** `./gradlew test` + manual test + +## Phase 8: Multi-Page Navigation + +- [ ] 8.1: `PageListScreen` with thumbnails + - `ui/pages/PageListScreen.kt`, `PageListViewModel.kt` +- [ ] 8.2: Add new page button +- [ ] 8.3: Prev/next page navigation in editor + auto-save +- **Verify:** manual test + +## Phase 9: PDF Export + +- [ ] 9.1: `PdfExporter` — Android `PdfDocument` API + - `ui/export/PdfExporter.kt` +- [ ] 9.2: Share via `Intent.ACTION_SEND` + `FileProvider` + - `res/xml/file_provider_paths.xml`, `AndroidManifest.xml` updates +- [ ] 9.3: Export button in toolbar (per-page + whole notebook) +- [ ] 9.4: Unit test — verify PDF generation +- **Verify:** `./gradlew test` + open exported PDF, confirm no grid + +## Phase 10: Polish + +- [ ] 10.1: E-ink optimizations — minimize animations, high contrast +- [ ] 10.2: Auto-save zoom/pan state per page +- [ ] 10.3: Empty states, error handling, delete confirmations +- [ ] 10.4: Performance profiling on target devices diff --git a/README.md b/README.md new file mode 100644 index 0000000..e06ff1f --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +eng-pad +======= + +This is an Android app, targeting Android 11, and specifically the +following two devices: + +1. The Supernote Manta (Android 11) +2. The Daylight DC-1 (Android 13) + +There are a few features it needs to have: + +1. It is organized around notebooks. +2. A *regular* notebook's page size is 8.5 x 11 inches. A *large* + notebook's size is 11 x 17 inches. +3. The app features a grid of roughly 5 squares per inch. The grid is + not visible on exported documents. +4. The app should allow pinch to zoom in and out. +5. There should be two pen sizes, corresponding roughly to a Muji gel + ink ballpoint 0.38 or 0.5 pen. +6. It should have the ability to erase, or to select-and-erase. +7. It should have the ability to select and cut/move or copy/paste. +8. The primary method of input is an EMR pen. +9. The app needs to be able to export PDFs though the normal Android + sharing mechanism (e.g. so docs can be exported to Dropbox). + +The tech stack can be flexible. The safe default is Java, but if Kotlin +or even Clojure or some other langauge makes more sense, it can be used.