Lint reported 113 warnings; this fixes them and turns on warningsAsErrors so new ones fail the build. The pre-commit hook now runs `ktlintCheck lint` and the pre-push hook runs `test lint`. Fixes: - Replace "..." with the typographic ellipsis in every locale's strings.xml - Remove unused resources: psbt_amount_sats and enable_applications_backup strings, white/amber/gray colors, ic_launcher_background_white drawable - Remove launcher bitmap fallbacks (background/foreground/monochrome/round and density ic_launcher.webp): minSdk 26 always resolves the adaptive icon - Move ic_qrcode.png to drawable-nodpi - Shrink ic_tor from a declared 512dp to 24dp (only ever drawn at 20dp) - Make modifier the first optional parameter of RemoteAppIcon - Use the lambda Modifier.offset overload for the animated toggle indicator - Replace deprecated Icons.Default.Article with the AutoMirrored version - Drop a redundant when branch, opt in to resetReplayCache's experimental API Suppressed with a stated reason: ScopedStorage (legacy backup import), InsecureBaseConfiguration (local/onion ws:// relays), PluralsCandidate, VectorPath/VectorRaster (artwork), and ObsoleteSdkInt for mipmap-anydpi-v26 (AAPT cannot resolve the launcher icon from plain mipmap-anydpi). Version freshness checks are disabled so hooks stay deterministic over time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VZCeWRzzk4dxptDiEZyXjM
7.1 KiB
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.shuse Temurin 21. compileSdk = 37,minSdk = 26, R8 full mode enabled, Gradle parallel + configuration-cache.- Room schemas are exported to
app/schemasvia 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;warningsAsErrorsis on, so any new warning fails the build (pre-commit and pre-push hooks run this). Version-freshness checks (GradleDependencyetc.) are disabled inapp/build.gradle.kts; per-path exemptions live inapp/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 envSIGN_RELEASEis set andkeystore.propertiesexists. CI otherwisetouches an empty one../build.sh <version> <appName>— builds free + offline release APKs/AABs into~/release/and callsgenerate_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:
- Bump
versionCode(+1) andversionName(newX.Y.Z) inapp/build.gradle.kts(defaultConfig). These are the only places the version lives. - Prepend a new
## Amber X.Y.Zblock at the top ofCHANGELOG.md, summarizing the non-merge commits since the previousvX.Y.Ztag (git log vX.Y.Z..HEAD --no-merges), grouped as user-facing bullets. End the block with the standard "Download it with …" line (update thereleases/tag/vX.Y.ZURL) and the "If you like my work …" donation line. - Always include the
## Verifying the releaseblock immediately after the donation line (before the next## Amberheading). Copy it verbatim from the previous release entry and only change themanifest-vX.Y.Z.txt/manifest-vX.Y.Z.txt.sigfilenames to the new version. Do not change thegpg --recv-keyskey id, thegpg: 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. - 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 alsoBuildConfig.IS_FDROID_BUILD(false by default; the F-Droid release workflow flips it true and stripsREQUEST_INSTALL_PACKAGESviased) — use it to disable self-update / Zapstore. - Offline permissions gate:
check-offline-permissions.ymlruns./gradlew processOfflineDebugManifestand greps the merged manifest for the three network permissions. Any new dependency that leaksINTERNETetc. 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:
nostrsigner:///nostrconnect://Intent →SignerActivity→IntentUtils→ approval bottom sheet.- ContentProvider IPC →
SignerProvider(synchronous,runBlocking). - 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 ConcurrentHashMaps 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_studiocode style (.editorconfig); star imports effectively disabled; trailing commas allowed;@Composablefunctions exempt from the function-naming rule. RunktlintFormatrather than hand-formatting. - Translations live in
app/src/main/res/values-<locale>/strings.xml;MissingTranslationlint is intentionally disabled and the shipped locales are pinned byandroidResources.localeFiltersinapp/build.gradle.kts. - Git hooks (
git-hooks/pre-commit,pre-push) are auto-installed by the rootbuild.gradle.ktsinstallGitHooktask wired into:apppreBuild. 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 forfreedebug 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!).