mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 10:58:23 +00:00
Toggling the setting rotates the AMBER_AES_KEY Keystore key and re-encrypts every stored secret, which can take a while. While the rotation runs the screen now shows a small progress spinner with "Re-encrypting all stored keys…" and "Do not close the app or lock the screen until this finishes", and the toggle/row are disabled to prevent overlapping rotations. The work still runs on the application IOScope so it completes even if the user leaves; on completion (or failure, which now logs and reverts the switch to the persisted value) the indicator is dismissed. The two new strings are translated into all 13 shipped locales, and AGENTS.md now instructs agents to always translate new/changed strings into every locale file in the same change.
75 lines
7.6 KiB
Markdown
75 lines
7.6 KiB
Markdown
# Repository instructions for Codex / OpenCode
|
|
|
|
Amber is a single-module Android app (`:app`, package `com.greenart7c3.nostrsigner`) — a Nostr event signer (NIP-46 / NIP-55). Despite top-level `lib/`, `commonMain/`, `androidMain/` dirs, `settings.gradle.kts` includes only `:app`; those are not separate Gradle modules.
|
|
|
|
## Toolchain
|
|
|
|
- JDK 21 required (source/target compat 21). CI and `.codex/setup.sh` use Temurin 21.
|
|
- `compileSdk = 37`, `minSdk = 26`, R8 full mode enabled, Gradle parallel + configuration-cache.
|
|
- Room schemas are exported to `app/schemas` via KSP (`room.schemaLocation`).
|
|
|
|
## Build and validation commands
|
|
|
|
- `./gradlew ktlintCheck` — Kotlin style check (pre-commit hook runs this).
|
|
- `./gradlew ktlintFormat` — auto-fix formatting issues.
|
|
- `./gradlew lint` — Android Lint on the default variant; `warningsAsErrors` is on, so any new warning fails the build (pre-commit and pre-push hooks run this). Version-freshness checks (`GradleDependency` etc.) are disabled in `app/build.gradle.kts`; per-path exemptions live in `app/lint.xml`.
|
|
- `./gradlew test --no-daemon` — JVM unit tests for all variants (pre-push hook runs `./gradlew test lint`).
|
|
- Run one test: `./gradlew :app:testFreeDebugUnitTest --tests "com.greenart7c3.nostrsigner.SomeTest"` (or `--tests "*SomeTest.method"`).
|
|
- `./gradlew assembleDebug --no-daemon` — debug APK for both product flavors.
|
|
- `./gradlew assembleRelease --no-daemon` — release APK; signing only activates when env `SIGN_RELEASE` is set **and** `keystore.properties` exists. CI otherwise `touch`es an empty one.
|
|
- `./build.sh <version> <appName>` — builds free + offline release APKs/AABs into `~/release/` and calls `generate_manifest.sh`.
|
|
|
|
Required order when pushing: `ktlintCheck` → `lint` → `test` → build (mirrored by the hooks, but run them directly; do not rely on hooks).
|
|
|
|
## Releases / version bumps
|
|
|
|
When asked to "bump the version" / cut a release, do **all** of the following in a single change — never omit the verification block:
|
|
|
|
1. Bump `versionCode` (+1) and `versionName` (new `X.Y.Z`) in `app/build.gradle.kts` (`defaultConfig`). These are the only places the version lives.
|
|
2. Prepend a new `## Amber X.Y.Z` block at the top of `CHANGELOG.md`, summarizing the non-merge commits since the previous `vX.Y.Z` tag (`git log vX.Y.Z..HEAD --no-merges`), grouped as user-facing bullets. End the block with the standard "Download it with …" line (update the `releases/tag/vX.Y.Z` URL) and the "If you like my work …" donation line.
|
|
3. **Always include the `## Verifying the release` block immediately after the donation line** (before the next `## Amber` heading). Copy it verbatim from the previous release entry and only change the `manifest-vX.Y.Z.txt` / `manifest-vX.Y.Z.txt.sig` filenames to the new version. Do **not** change the `gpg --recv-keys` key id, the `gpg: Signature made Fri 13 Sep 2024 …` block, or the surrounding prose — those are key-specific, not release-specific. This block must be part of the version-bump commit, not a follow-up.
|
|
4. Do not commit unless explicitly asked (per the global rule). Do not edit `build.sh` — it takes the version as an argument.
|
|
|
|
## Product flavors (dimension `version`)
|
|
|
|
| Flavor | Notes |
|
|
|--------|-------|
|
|
| `free` (default) | Online: OkHttp, Coil, kmptor, relay connectivity. Owns `INTERNET`/network permissions. |
|
|
| `offline` | No network stack. `app/src/offline/AndroidManifest.xml` **removes** `INTERNET`/`CHANGE_NETWORK_STATE`/`ACCESS_NETWORK_STATE` with `tools:node="remove"`. |
|
|
| `benchmark` | Mirrors `free` network deps; `applicationIdSuffix=.benchmark`, `versionNameSuffix=-BENCHMARK`; CI builds a signed release per push for side-by-side install. |
|
|
|
|
- Guard any network-only code with `BuildFlavorChecker.isOfflineFlavor()`. There is also `BuildConfig.IS_FDROID_BUILD` (false by default; the F-Droid release workflow flips it true and strips `REQUEST_INSTALL_PACKAGES` via `sed`) — use it to disable self-update / Zapstore.
|
|
- **Offline permissions gate:** `check-offline-permissions.yml` runs `./gradlew processOfflineDebugManifest` and greps the merged manifest for the three network permissions. Any new dependency that leaks `INTERNET` etc. will fail this check — verify the offline merged manifest when adding network-capable deps.
|
|
|
|
## Architecture notes (not obvious from filenames)
|
|
|
|
Three request ingestion paths all converge on `Account.sign()` / encrypt-decrypt:
|
|
1. `nostrsigner://` / `nostrconnect://` Intent → `SignerActivity` → `IntentUtils` → approval bottom sheet.
|
|
2. ContentProvider IPC → `SignerProvider` (synchronous, `runBlocking`).
|
|
3. NIP-46 relay kind 24133 → `NotificationSubscription` → `EventNotificationConsumer` → `BunkerRequestUtils`.
|
|
|
|
`Amber.kt` (the `Application` class) is the DI container: owns `applicationIOScope`, the Quartz `NostrClient`, `notificationSubscription`, `isStartingAppState` (set during `runMigrations()` — wait on `isStartingAppState.first { !it }`), and `settings.killSwitch` (disconnects all relays when true).
|
|
|
|
Per-account isolation: every `npub` gets its own `SharedPreferences` (`prefs_${npub}`), `AppDatabase` (`amber_db_${npub}`), `LogDatabase`, and `HistoryDatabase` — all lazily cached in `ConcurrentHashMap`s in `Amber`. Decrypted keys loaded via `LocalPreferences.loadFromEncryptedStorage()` and cached in `LargeCache`.
|
|
|
|
Biometric/PIN lock (`useAuth`/`usePin`, `SecurityScreen`, `BiometricAuthScreen`) is a **UI-only app-launch gate**, not a signing gate. `SignerProvider` and the NIP-46 path never touch it; auto-accept permission rules sign silently on all three paths. Authorization for automatic signing is governed solely by the permission system (`ApplicationEntity` / `ApplicationPermissionsEntity`: `rememberType`, `acceptUntil`/`rejectUntil`, `kind`). Do not wire signing through the biometric prompt.
|
|
|
|
See `CLAUDE.md` for the key-files table (verified accurate against the current tree).
|
|
|
|
## Conventions
|
|
|
|
- ktlint `android_studio` code style (`.editorconfig`); star imports effectively disabled; trailing commas allowed; `@Composable` functions exempt from the function-naming rule. Run `ktlintFormat` rather than hand-formatting.
|
|
- Translations live in `app/src/main/res/values-<locale>/strings.xml`; `MissingTranslation` lint is intentionally disabled and the shipped locales are pinned by `androidResources.localeFilters` in `app/build.gradle.kts`.
|
|
- **Always translate string resources.** Whenever you add or change a string in `app/src/main/res/values/strings.xml`, add the translated entry to **every** existing `app/src/main/res/values-*/strings.xml` locale file in the same change (list them with `ls app/src/main/res | grep '^values-'`, ignoring `values-night`). Do not ship English placeholders in locale files — write the actual translation, matching the existing tone/registers of that file. Escape apostrophes as `\'` in languages that use them (e.g. French, Italian, Turkish), exactly like neighboring entries.
|
|
- Git hooks (`git-hooks/pre-commit`, `pre-push`) are auto-installed by the root `build.gradle.kts` `installGitHook` task wired into `:app` `preBuild`. Do not rely on them as a substitute for running checks directly.
|
|
|
|
## Codex Web / cloud setup
|
|
|
|
Use the committed scripts for cloud environments:
|
|
- Setup: `bash .codex/setup.sh` — installs/verifies Java 21, bootstraps Android cmdline SDK, installs API 36 / build-tools 36.0.0, accepts licenses, and prewarms Gradle for `free` debug unit-test sources.
|
|
- Maintenance: `bash .codex/maintenance.sh` — refreshes Gradle metadata in cached containers.
|
|
|
|
## Reproducibility
|
|
|
|
`Dockerfile` + `apkdiff.py` verify reproducible builds: `docker build -t amber-repro --build-arg VERSION=vX.Y.Z --build-arg APK_TYPE=free-arm64-v8a .` then `docker run --rm amber-repro` (expect `APKs match!`).
|