- Kotlin 99.7%
- Shell 0.3%
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STsrVABBMYp3a9cXfrqJ41 |
||
|---|---|---|
| .woodpecker | ||
| app | ||
| gradle | ||
| .gitignore | ||
| build.gradle.kts | ||
| build.sh | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| PLAN.md | ||
| README.md | ||
| settings.gradle.kts | ||
| SMOKE.md | ||
pantryapp
Android companion app for pantryapi — the kitchen tool:
scan a barcode, book packages in, quick-consume, check stock levels, see
what's short. Booking and consuming both move whole packages of a
barcode — a 330 ml can goes out as 1 can, never 150 ml — with the package
size coming from the product, so the scan sheet and the quick-consume
dialog just count packages (typed quantities remain only as the fallback
for classes with no sized product). The Manage tab covers the everyday additions — new locations,
product groups and product classes — and when a scanned product isn't
assigned to a class yet, you can create the class and set the package size
right in the scan sheet. Class QR labels print directly on a Niimbot B1
(Bluetooth, 50×30 mm labels) — select the printer once in Settings, then
"Print label" on any class detail page; the QR encodes the class UUID, the
same payload the web UI's printable labels use, so either kind scans back
to the class. Heavier surgery stays on the pantryapi web UI (pantryweb),
reachable from any browser on the same server: renames and deletes, class
splits and user administration. Think of the app as the thing you keep in
the kitchen for the five-second interactions, and the web UI as the thing
you open at a desk once in a while to reorganize.
Native Kotlin + Jetpack Compose, minSdk 31 (Android 12+). See PLAN.md for the full plan, screen list and work packages.
Building
Everything runs in Docker; no local Android toolchain needed.
./build.sh # unit tests + debug APK (default)
./build.sh test # just the unit tests
./build.sh assembleDebug # just the APK -> app/build/outputs/apk/debug/
./build.sh assembleRelease # R8-minified release APK -> app/build/outputs/apk/release/
Unit tests are plain JVM: the typed API client runs against a MockWebServer fake pantryapi with the same canned fixtures as the web UI's fake.
assembleRelease works locally without any signing secrets — it just
produces an unsigned app-release-unsigned.apk, useful for checking that
R8 shrinking still builds cleanly. Signed release APKs are built by CI on
tags; see "Release/signing" below.
Installing
Signed APKs are attached to Forgejo releases (see the repo's Releases tab) on every version tag. Two ways to install:
- Direct: download
pantryapp-<tag>.apkfrom the latest release and open it on the device (enable "install unknown apps" for your browser or file manager once, when prompted). - Obtainium: add this repo as an app source (it auto-detects Forgejo/Gitea releases) and Obtainium will track new tags and offer updates, the same way it would for an F-Droid or GitHub-hosted app. There is no Play Store listing.
First-run setup
On first launch the app asks for:
- Server URL — your pantryapi instance, e.g.
http://pantry.local:8084orhttps://pantry.example.com. The app probes/healthzbefore continuing, so a wrong URL or unreachable server fails fast with a clear message rather than a silent hang. - Username / password — the same credentials as the web UI (the
bootstrap
adminuser works out of the box on a fresh pantryapi).
The resulting JWT is stored encrypted on-device (DataStore + Android Keystore) and is valid for 30 days, so re-login is rare; a 401 from the API (expired/revoked token) drops you back to this screen automatically.
HTTPS is recommended if the server is reachable from outside your LAN,
but plain cleartext HTTP works fine for a household server on the local
network — the app explicitly allows cleartext traffic
(usesCleartextTraffic) since that's the common case for a self-hosted
pantryapi. Put the server behind a TLS-terminating proxy if you ever expose
it beyond the LAN.
Release/signing (for maintainers)
Release builds are signed with a dedicated app-signing keystore (never the
debug key) and R8-minified. The signing config in app/build.gradle.kts
reads the keystore path and passwords from environment variables and is
only applied when they're set — this is what lets local
./build.sh assembleRelease succeed unsigned, and lets CI sign only on tag
builds where the secrets are injected.
One-time: generate the keystore
keytool -genkeypair -v \
-keystore pantryapp-release.jks \
-alias pantryapp \
-keyalg RSA -keysize 4096 -validity 10000
Keep the resulting .jks and its passwords somewhere durable and private
(a password manager, not this repo) — losing it means future releases can
no longer update installs signed with it, and Android will refuse to
install an update signed with a different key over an existing install.
Woodpecker secrets
.woodpecker/build.yaml builds and signs the release APK only on tag
events, using these repo secrets:
| Secret | Value |
|---|---|
pantryapp_keystore |
base64 -w0 pantryapp-release.jks output |
pantryapp_keystore_password |
keystore password |
pantryapp_key_alias |
key alias (pantryapp above) |
pantryapp_key_password |
key password |
gitea_token |
Forgejo API token with write access to releases (shared with pantryapi's release step) |
Set them in the Woodpecker UI under the repo's Secrets settings. On a tag
push, CI decodes the keystore to a temp file, runs assembleRelease with
PANTRYAPP_KEYSTORE/PANTRYAPP_KEYSTORE_PASSWORD/PANTRYAPP_KEY_ALIAS/
PANTRYAPP_KEY_PASSWORD set, renames the output to
pantryapp-${CI_COMMIT_TAG}.apk, and attaches it to the matching Forgejo
release.
Manual on-device verification
There's no automated UI/instrumentation test suite (see PLAN.md — unit tests cover the API client and business logic against a MockWebServer fake). After a release build, run through SMOKE.md against a live pantryapi stack before trusting a tag.