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) <noreply@anthropic.com>
This commit is contained in:
82
CLAUDE.md
Normal file
82
CLAUDE.md
Normal file
@@ -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).
|
||||
334
DESIGN.md
Normal file
334
DESIGN.md
Normal file
@@ -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
|
||||
```
|
||||
30
PROGRESS.md
Normal file
30
PROGRESS.md
Normal file
@@ -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).
|
||||
120
PROJECT_PLAN.md
Normal file
120
PROJECT_PLAN.md
Normal file
@@ -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
|
||||
27
README.md
Normal file
27
README.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user