tally/android/README.md

70 lines
3.3 KiB
Markdown

# Tally for Android
Two screens, reached from a hamburger drawer: the standard calculator (design 9.2), an
NCalc-style pad with a live answer as you type, and the converter (design 9.6), which
reopens where it was left and shows a value in every unit at once. Settings has the
theme: follow the device, light or dark. The engine behind it is the finished one, so
`2^100 + 1`, `x = 5` then `x * 2` and `cagr(10000, 25000, 5) * 100` all work. The
programmer and financial screens (design 9) are not built.
## Building
Nothing is installed system-wide; the toolchain comes from mise. **Setup, emulator and
deployment, including wireless adb, are in [SETUP.md](SETUP.md)** - this file is about what
the app is.
```
mise install && mise run android-sdk # toolchain, then the SDK packages
zig build android # the native library, one .so per ABI
cd android && gradle assembleDebug
```
`zig build android` writes `zig-out/android/{arm64-v8a,x86_64,armeabi-v7a}/libtally.so`,
which is the `jniLibs` layout Gradle expects, so Gradle only packages it. Gradle never
invokes Zig, and there is no CMake or `ndk-build` in this project: the library is built
without the NDK entirely (design 6.2), and the JNI glue is hand-written Zig
(`engine/src/jni.zig`) rather than generated from `jni.h`.
Because Gradle never invokes Zig, `zig build android` has to be re-run by hand after any
engine or JNI change, or the APK gets a stale library.
The Gradle wrapper is not committed; `gradle wrapper` writes one, or Android Studio offers
to on first open.
## The first thing to run, and it needs no device
```
zig build jvm-test
```
`engine/src/jni.zig` reaches the JNI function table by index, because there is no `jni.h`
here to name the entries. Four of those indices have to be right. JNI's table is fixed by
the specification rather than by the platform, so a desktop JVM checks them just as well as
a phone: this step builds the library for the host, loads it from Java (which runs
`JNI_OnLoad`, which round-trips a string through two of the four), and drives every entry
point. Moving one index by a slot makes it fail.
Then, with a device or emulator attached:
```
cd android && gradle connectedAndroidTest
```
`TallyEngineTest` covers what only the real app can: that a session remembers `x` and
`Ans`, that `2^100 + 1` arrives with its last digit intact, that `12 in to ft` is exactly
`1`, that an error carries the engine's own wording rather than a second copy written in
Kotlin, and that a closed session refuses to evaluate instead of crashing. It passes on an
Android 15 x86_64 image with 16KB pages. Note that it uninstalls the app when it finishes.
Nothing has been verified on a physical device yet, and arm64-v8a is the interesting one:
it is the ABI phones actually use, and the only one where the page-size bug in design 6.3
showed up.
## What is deliberately not here
- **No arithmetic.** Kotlin parses no expressions, formats no numbers and words no errors.
All of it is the shared engine, so a wrong answer is wrong in the CLI and TUI too, which
is the point of having one engine.
- **No error table.** Messages come from `engine.phrase` through the JSON.
- **No unit list.** `TallySession.unitCatalog()` returns the engine's own tables, aliases
included, for whenever the convert screen is built.