on master help sign in
fold/android/README.md
137 lines 6.6 kB history raw
1<p align="center"><img src="../docs/icon.png" width="96" height="96" alt="fold"></p>
3# fold for Android
5The same outline on your phone: fold for Android opens the vault the desktop app
6uses — `root.md` and its block files, synced by Syncthing — and reads and writes
7it with the same code. Every file it writes comes out of `fold-core`, the crate
8the TUI and the CLI use, so a vault edited on the phone is byte for byte what
9the desktop would have written.
11<p>
12<img src="docs/outline.png" width="220" alt="The outline">
13<img src="docs/zoomed.png" width="220" alt="Zoomed into a node">
14<img src="docs/editor.png" width="220" alt="Editing a subtree">
15<img src="docs/reading.png" width="220" alt="Reading a node">
16</p>
18## What it does
20- **The outline**: every node a row, folds, `☐`/`☑` to check a task off,
21 open/total task counts, due dates, a `▤` for nodes with their own file, the
22 first line of each node's text under its title. Tap a row to zoom into it;
23 the breadcrumb and Back zoom out. Folds, zoom and "hide done" are remembered
24 per vault on the phone, never in the vault.
25- **Long-press a row** (or *More…*) for everything you can do to a node — the
26 TUI's node menu: edit, zoom, properties, new sibling or child, done/reopen,
27 task on/off, heading ↔ bullet, give it its own file, move up/down,
28 indent/outdent, move to…, archive, copy, paste, delete, resolve conflict.
29- **Capture** drops a note (or a task) under today's day in the Inbox, as
30 `fold capture` does. Share text from any app to fold, or long-press the
31 launcher icon for *Capture*.
32- **Edit** a node: its subtree's Markdown in a plain text box, every nested
33 block inlined, as the TUI's editor shows it. Each line still belongs to its
34 block, so each save writes each block to its own file; it saves when you
35 pause and when you leave. A bar above the keyboard indents, outdents, and
36 toggles bullets, checkboxes and headings.
37- **Read** a node as a document, with light Markdown styling, checkboxes you
38 can tick and links you can open.
39- **Find** titles and text; **Move to…** picks a destination from a list.
40- **Properties** in a form — a due date with a date picker; the first one
41 gives the node its own file, as on the desktop.
42- **Undo and redo** every change, from the top bar or the message after it.
43 Undo refuses, writing nothing, when the file changed since (a sync).
44- **Sync**: fold watches the vault folder, takes in what Syncthing brings, and
45 says what changed (*↻ changed outside fold: Inbox (+1 item)*). Sync-conflict
46 copies are merged node by node, and pairs it cannot merge are shown side by
47 side to keep ours, theirs or both.
48- **Check the vault** (`fold check`), *Canonicalize* (`fold check --fix`), and
49 the trash, with restore.
51## Install
53Build it (below), then install `app/build/outputs/apk/debug/app-debug.apk` on
54the phone — `adb install`, or copy the file over and open it.
56On first start, choose the folder Syncthing syncs your vault to (or keep a
57vault on the phone only). fold reads and writes the vault's files in place, as
58Syncthing itself does, so for a folder in shared storage it asks for
59**all files access** (Android 11+; on Android 8–10, storage permission). It
60writes nothing outside the vault but its own trash, which lives in the app's
61private storage. That permission is why this is a build-it-yourself app and not
62a Play Store one.
64An empty folder becomes a new vault with an Inbox. As on the desktop, give
65Syncthing a `.stignore` with `*.fold-tmp` and `*.tmp`.
67## Build
69You need:
71- the Android SDK with platform 37 and NDK 28.2.13676358 (Android Studio
72 installs both; or `sdkmanager "platforms;android-37.0" "ndk;28.2.13676358"`),
73- a recent stable Rust with the Android targets and
74 [cargo-ndk](https://github.com/bbqsrc/cargo-ndk):
76 ```sh
77 rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
78 cargo install cargo-ndk
79 ```
81- JDK 17 or newer.
83Then, from `android/`:
85```sh
86./gradlew assembleDebug # app/build/outputs/apk/debug/app-debug.apk
87./gradlew installDebug # onto a connected phone
88./gradlew assembleRelease # shrunk; sign it with your own key
89```
91Gradle builds the Rust core itself: `cargoNdk` compiles `crates/fold-ffi` for
92the ABIs in `gradle.properties` (`fold.abis`; drop `armeabi-v7a` or `x86_64`
93to build faster), and `uniffiBindgen` builds it for this machine and generates
94its Kotlin bindings. Opening `android/` in Android Studio works the same way.
96## How it fits together
98```
99crates/fold-core the vault: parse, render, splice, every verb, merge
100crates/fold-ffi a Session over fold-core for other languages (UniFFI):
101 outline rows, reading lines, verbs with an op log,
102 the editor's tagged lines, sync and conflicts
103android/app Jetpack Compose UI; calls the Session on one worker
104 thread through the generated Kotlin bindings
105```
107`fold-ffi` holds what the TUI holds between keys — the undo log, the register,
108the open editor — and follows the TUI's rules for them: verbs refuse to write
109over a file changed on disk, an editor save is one undo step, a block cut in
110the editor and pasted elsewhere moves with its file. Nodes are named by keys
111([SPEC.md](../SPEC.md) §3.4), so a tap never acts on a node that moved under it.
113The editor is the one place the phone differs in kind: a text field reports
114its whole text after each change, so `fold-ffi` finds the span that changed and
115replays it through the same line rules as the TUI's editor, using the caret to
116tell, say, Enter at the end of one line from Enter at the start of the next.
118## Tests
120```sh
121cargo test -p fold-ffi # the session, the editor bridge (Rust)
122./gradlew testDebugUnitTest # the Kotlin bindings over the host build of
123 # the core, and the UI under Robolectric
124./gradlew recordRoborazziDebug # the same, saving a screenshot of each screen
125 # to app/build/outputs/roborazzi
126```
128Robolectric downloads an Android framework jar on first use. Offline, or behind
129a rate-limited mirror, put
130`android-all-instrumented-15-robolectric-13954326-i7.jar` in a folder and pass
131`-Probolectric.dependency.dir=/that/folder`.
133## Not there yet
135- No drag and drop: rows move with Move up/down, Indent/Outdent and Move to….
136- No syntax highlighting in fenced code; it is shown in a monospace block.
137- The editor has no Vim or Helix keys; it is the phone's keyboard.