4.4 KiB · text History fbe85ec

Fieri Android

Companion app for Fieri, Eternal: your areas, ranking restaurants, and lunch votes from your phone. Adding restaurants stays on the web, where the map is.

Open in Android Studio

  1. File → Open → this android/ directory, trust the project, let Gradle sync.
  2. Run against an emulator or device. Min SDK 26 (Android 8.0).

From a shell: ./gradlew :app:assembleDebug with ANDROID_HOME (or a local.properties sdk.dir) pointing at an SDK with platform 34.

Signing in

Two ways, both ending in the same place: an API token for this phone, stored with the server address in EncryptedSharedPreferences and kept out of backups.

Username and password. The Sign in tab takes the server (default https://fe.neiam.org), username and password, and trades them for a token at POST /api/auth/login. The password isn’t stored. The token is named after the phone (Google Pixel 8) on the web’s Devices page.

QR. On the web, avatar menu → Devices → Make a pairing QR. The page shows a QR of fieri://pair?base=<server>&token=<token>; the Scan QR tab scans it and connects. The same link opened in a phone browser lands on that tab, filled in. The token is shown only on that page, once.

Either way, the token shows up under Devices, where revoking it signs that phone out (the app notices the next 401 and returns to sign-in). Sign out in the app revokes its own token on the server too.

If the server sits behind a proxy, set PUBLIC_URL on it so the QR carries the public origin; otherwise it uses the host the browser asked for.

What it does

  • Areas you’re a member of, with your role.
  • An area’s restaurants: your rank, cuisine, last visit, and whether it’s open at noon today; lunch votes in progress at the top.
  • A restaurant: details, opening hours, map and website, your rank (all six, including Obsessed and To try, and the “Been there. How was it?” nudge), I went today, everyone’s ranks, visits.
  • Where to?: pick who’s going (you, friends and people you’ve eaten with first, everyone else folded away) and start a vote.
  • A lunch vote: join, vote for every option you’d go to, re-roll the rest, call it off, or settle it with Go here. Refreshes every few seconds while on screen.
  • Themes: the web’s palettes (Her, After Dark, Forest, Sky, Clays, Stones, Light, Dark, or follow the system), B612 type.

Everything goes through /api (src/handlers/api.rs), which returns the same data, through the same permission checks, as the web pages.

Release builds

Same as the other neiam apps: CI builds and signs the release APK in a container (android/Dockerfile) and publishes it to the forge’s F-Droid repository. Reproduce locally from the repository root:

docker buildx build --target android-export \
  --secret id=keystore,src=./release.jks \
  --secret id=keystore_password,src=./keystore.pass \
  --progress=plain \
  --output type=local,dest=./apk --file android/Dockerfile android

.forgejo/workflows/android.yml runs that on every push to master and uploads the APK with the job’s own token. versionCode is the CI run number; local builds stay at 1 and are never published.

Install from F-Droid by adding the owner’s repository once (it covers every app they publish):

<forge>/pkg/fdroid/<owner>/_all?fingerprint=<fingerprint>
curl -fsSL <forge>/pkg/_keys/fdroid/<owner>    # prints the fingerprint

The release keystore

Generate it once. Android checks this certificate on every update, so losing it means uninstalling and reinstalling on every phone — there is no recovery.

keytool -genkeypair -v \
  -keystore release.jks -storetype PKCS12 \
  -alias fieri -keyalg RSA -keysize 4096 -validity 10000 \
  -dname "CN=Fieri, O=neiam, C=US"

Back it up before it exists anywhere else:

export BAO_TOKEN=<root token>
E() { kubectl -n openbao exec -i statefulset/openbao -- env BAO_TOKEN="$BAO_TOKEN" "$@"; }
E bao kv put kv/fieri/android keystore="$(base64 -w0 release.jks)" password='<the password>'
# recover: E bao kv get -field=keystore kv/fieri/android | base64 -d > release.jks

Then on the repository, Settings → Actions → Secrets:

Secret Value
ANDROID_KEYSTORE base64 -w0 release.jks
ANDROID_KEYSTORE_PASS the password

Until they’re set the android job skips cleanly. It never falls back to a debug-signed APK: that key is shared by everyone with an SDK.