From ebf2cec57c02c24624cf4d3b16e76923ace3f32c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 01:48:40 +0000 Subject: [PATCH 001/127] docs(qr): plan a CameraX + zxing-cpp overhaul of the QR reader Studies the current reader (zxing-android-embedded 4.3.0 behind ScanContract) and records the verified causes of its poor scan rate: a Camera1 stack with a 2s autofocus timer rather than continuous AF, MIXED_SCAN burning half of all frames on an inverted pass, no zoom or discoverable torch, a decode cropped to the viewfinder rect, one un-retried decode attempt per frame, and an external activity whose failures collapse "cancelled" and "unroutable payload" into the same silent return. Proposes an in-app Compose scanner on CameraX (already a declared but unused dependency) behind a BarcodeDecoder interface, with zxing-cpp (Apache-2.0) as the default engine, staged in six phases: a measurement corpus first, then the camera rewrite, per-frame decode quality, explicit scan feedback, gallery and clipboard import, and the display-side fixes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX --- .../plans/2026-09-15-qr-reader-overhaul.md | 302 ++++++++++++++++++ amethyst/plans/README.md | 1 + 2 files changed, 303 insertions(+) create mode 100644 amethyst/plans/2026-09-15-qr-reader-overhaul.md diff --git a/amethyst/plans/2026-09-15-qr-reader-overhaul.md b/amethyst/plans/2026-09-15-qr-reader-overhaul.md new file mode 100644 index 0000000000..caff7d5e4b --- /dev/null +++ b/amethyst/plans/2026-09-15-qr-reader-overhaul.md @@ -0,0 +1,302 @@ +# QR Reader Overhaul + +**Status:** queued (design + task plan; nothing implemented yet) +**Goal:** make scanning a QR code in Amethyst fast and near-certain — codes that are small, +far, dim, glossy, tilted, inverted, on a screen, or already sitting in the gallery should all +resolve on the first try, and a code the app *can't* route should say so instead of silently +dropping the user back where they started. + +**Scope:** the reader (`amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt` and the four +screens that call it), plus a short second section on the *display* side, because half of +"scan this npub" is how legibly we draw the code for the other phone. + +--- + +## 1. What we have today + +`SimpleQrCodeScanner` is 30 lines that hand the whole job to +[zxing-android-embedded](https://github.com/journeyapps/zxing-android-embedded) 4.3.0 via +`ScanContract`, which launches its own `CaptureActivity`: + +```kotlin +ScanOptions().apply { + setDesiredBarcodeFormats(ScanOptions.QR_CODE) + setPrompt(stringRes(id = Res.string.point_to_the_qr_code)) + setBeepEnabled(false) + setOrientationLocked(false) + addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN) +} +``` + +Four call sites: + +| Call site | Payload expected | +| --- | --- | +| `ShowQRScreen.kt:182` (`NIP19QrCodeScanner`) | any `nostr:` / NIP-19 entity → `Route` | +| `KeyTextField.kt:106` | `nsec` / `ncryptsec` / bunker login | +| `AddNwcWalletScreen.kt:207`, `AddClinkDebitWalletScreen.kt:172` | `nostr+walletconnect://` | +| `Nip46SignerScreen.kt:174` | `bunker://` | + +`libs.zxing` (ZXing **core**) is used only for *encoding* (`QrCodeDrawer.kt`, +desktop `QrCodeCanvas.kt`). The decoder we actually run is the one bundled inside +zxing-android-embedded. + +### 1.1 Why it is hard to scan — verified causes + +Each of these was checked against the library's source (4.x `master`) or our own code, not +recalled: + +1. **It is a Camera1 app.** `com.journeyapps.barcodescanner.camera.CameraManager` imports + `android.hardware.Camera`. Everything downstream inherits Camera1's limits: legacy-HAL + preview sizes, no per-frame 3A control, and no zoom API wired up at all. +2. **Autofocus is a 2-second timer, not continuous.** `CameraSettings` defaults to + `focusMode = FocusMode.AUTO` (`continuousFocusEnabled = false`), and `AutoFocusManager` + re-triggers `Camera.autoFocus()` on `AUTO_FOCUS_INTERVAL_MS = 2000L`. Between triggers the + frame can stay out of focus for up to two seconds — this is the "hold it still… still… + still…" feel, and it is fatal for close-up codes where the lens needs to rack to macro. +3. **`MIXED_SCAN` halves our decode rate.** We pass `Intents.Scan.MIXED_SCAN`, which selects + `MixedDecoder`, whose `toBitmap()` flips a boolean and inverts *alternate frames*. So for a + normal dark-on-light QR — i.e. essentially every code we meet — half of all frames are spent + decoding an inverted image that can never match. +4. **No zoom, at all.** Not pinch, not a button, not automatic. A code on a laptop screen across + a desk, or a small printed code on a sticker, simply never resolves enough modules. +5. **No discoverable torch.** The stock `CaptureActivity` layout has no torch button; + `DecoratedBarcodeView` only maps it to the volume keys. In a bar or a meetup hallway — the + exact place people swap npubs — there is no way to light the code. +6. **The decode is cropped.** `BarcodeView` sets `decoderThread.setCropRect(getPreviewFramingRect())`, + and `CameraPreview` defaults to `marginFraction = 0.1d`, so anything outside the centred + viewfinder box is discarded even though the user can see it in the preview. +7. **One decode attempt per frame, no retry ladder.** `DecoderThread.decode()` builds a single + `HybridBinarizer` over the cropped luminance and calls the reader once. No `TRY_HARDER`, no + rotation retry, no multi-scale, no denoise. ZXing-Java's detector needs three clean finder + patterns; glare, a crease, or a ~15° tilt past its tolerance and the frame is simply lost. +8. **It leaves the app.** `ScanContract` starts a separate activity with its own theme — no + Material3, no edge-to-edge, a visible cold-start hitch, and `setOrientationLocked(false)` + means rotating the phone recreates that activity and restarts the camera mid-scan. +9. **Failure is silent and ambiguous.** `NIP19QrCodeScanner` maps both "user cancelled" and + "decoded fine, but `uriToRoute` returned null" to `onScan(null)`, and `ShowQRBody` reacts by + flipping `presenting = true`. The user sees the QR screen again with no message. A decoded + but unroutable payload (a bare hex pubkey, an `nsec1…`, a plain `https://` link, another + app's code) is indistinguishable from "the camera never read it" — which is very likely part + of why the reader *feels* broken even in cases where the decode succeeded. This is the same + complaint as [#417](https://github.com/vitorpamplona/amethyst/issues/417), which was closed + in 2023 but is still the current behaviour. +10. **No way in except the live camera.** You cannot scan a QR you were *sent* — a screenshot, a + photo in the gallery, an image in a DM — nor paste one from the clipboard. Today the only + path is to get a second screen to display it and point the phone at it. + +### 1.2 One happy accident + +`amethyst/build.gradle.kts:622-626` already declares `camera-core`, `camera-camera2`, +`camera-lifecycle`, `camera-view` and `camera-extensions`, and **nothing in the repo imports +`androidx.camera`** (grep across `.kt`/`.xml` returns only a `PreviewView` line in the generated +baseline profile). CameraX is already paid for in APK size and in the licence audit; we just +haven't used it. This plan finally does. + +--- + +## 2. Target architecture + +Replace the external-activity scanner with an in-app Compose screen: + +``` +Route.QrScanner ──► QrScannerScreen (Compose, Material3, edge-to-edge) + ├─ CameraX Preview (PreviewView) ─────┐ + ├─ CameraX ImageAnalysis ──► BarcodeDecoder ──► ScanResult + ├─ ScannerOverlay (cutout, hit boxes, torch, zoom, gallery) + └─ ScanOutcomeSheet (routes, or explains why it can't) + │ + gallery / clipboard / shared image ──► BarcodeDecoder.decode(Bitmap) ┘ +``` + +**`BarcodeDecoder`** is a small interface with two entry points — +`decode(ImageProxy): List` and `decode(Bitmap, cropRect, rotation): List` +— so the camera plumbing, the UI, and the tests are all independent of which engine sits behind +it. + +### 2.1 Decoder choice: `io.github.zxing-cpp:android` + +**Recommended: `io.github.zxing-cpp:android:3.1.1`, replacing `com.journeyapps:zxing-android-embedded`.** + +Licence (per CLAUDE.md, verified against the published POM at +`repo1.maven.org/maven2/io/github/zxing-cpp/android/3.1.1/android-3.1.1.pom`): +**Apache License 2.0 → permissive → OK, proceed.** No GPL family, no linking-exception question. + +Why it is the right engine here: + +- Its `Options` are exactly the knobs our failure modes need — `tryHarder`, `tryRotate`, + `tryInvert`, `tryDownscale`, `tryDenoise`, `binarizer`, `maxNumberOfSymbols`. In particular + `tryInvert` does inverted detection **inside one call as a fallback pass**, which is what + `MIXED_SCAN` was reaching for without throwing away half our frames. +- `read(image: ImageProxy)` reads the Y plane straight out of the CameraX buffer + (`planes[0].buffer`, `rowStride`, `cropRect`, `rotationDegrees`) — no YUV→RGB copy, no + `Bitmap` per frame. +- Every `Result` carries a `Position` (four corners + orientation), which we need for the + tappable multi-code overlay and for the auto-zoom heuristic. +- `sequenceId` / `sequenceIndex` / `sequenceSize` give us Structured Append (multi-part QR) for + free — the door to large payloads later. +- `minSdkVersion 21` (from the AAR manifest) against our `minSdk = 26`. Fine. + +Cost: a prebuilt `.so` per ABI — `arm64-v8a` 1.75 MB, `armeabi-v7a` 1.23 MB, `x86` 1.85 MB, +`x86_64` 1.82 MB uncompressed. With `splits.abi` already enabled, an arm64 APK grows by roughly +0.8 MB compressed, partly offset by dropping zxing-android-embedded. We already ship prebuilt +JNI (`secp256k1-kmp-jni-android`), so F-Droid precedent exists — but **confirm with the F-Droid +packaging before landing**, since a new prebuilt binary is the kind of thing their build +metadata cares about. + +**Why not ML Kit barcode scanning:** it is proprietary and GMS-shaped, so it could only ship in +the `play` flavor and `fdroid` would need a FOSS decoder anyway — meaning we'd build the +zxing-cpp path regardless and then maintain two. Because `BarcodeDecoder` is an interface, an +ML Kit implementation in `amethyst/src/play/` (the pattern `MLKitImageLabelService.kt` already +uses in both flavor trees) stays a cheap, later, measurement-driven option. Phase 0 exists to +decide whether it's ever needed. + +**Fallback if the native lib is rejected:** keep `libs.zxing` (already present, ZXing-Java, zero +new bytes) behind the same interface. Phases 1, 3 and 4 below — the camera stack, the feedback +model, and image import — are decoder-independent and carry most of the win on their own. + +--- + +## 3. Phases + +### Phase 0 — Build the ruler before cutting (do this first) + +Nothing here ships; it exists so every later claim is measured rather than asserted. + +- [ ] Assemble a fixture corpus under `amethyst/src/androidTest/assets/qr/`: ~60 PNGs of real + Amethyst payloads (`npub`, `nprofile` with relay hints, `nevent`, `naddr`, + `nostr+walletconnect://`, `bunker://`, a Concord invite) rendered by our own + `QrCodeDrawer` and then degraded — gaussian blur, 8/15/30° tilt, perspective, 30 %/10 % + contrast, inverted, JPEG artefacts, partial glare, photographed-off-a-screen moiré, and + a "small in frame" set at 120/80/48 px across. +- [ ] `QrDecodeCorpusTest` (androidTest): decode every fixture with (a) today's ZXing-Java + + `HybridBinarizer` path and (b) zxing-cpp with the Phase 2 options. Report a pass rate per + category and per-image median decode time. +- [ ] Record the baseline numbers in this file. **Gate:** if zxing-cpp does not clearly beat + ZXing-Java on the degraded sets, drop §2.1 and keep ZXing-Java behind `BarcodeDecoder`. + +### Phase 1 — In-app CameraX scanner (the structural fix) + +- [ ] `BarcodeDecoder` interface + `ScanResult` (text, bytes, format, corner positions, + sequence info) in `amethyst/.../ui/screen/loggedIn/qrcode/decode/`. +- [ ] `QrScannerScreen` — `PreviewView` + `ImageAnalysis` bound to `LocalLifecycleOwner` via + `ProcessCameraProvider`, `STRATEGY_KEEP_ONLY_LATEST`, `OUTPUT_IMAGE_FORMAT_YUV_420_888`, + analysis resolution requested at 1280×720 with a 1920×1080 fallback via + `ResolutionSelector`. Analysis runs on a single background executor; results hop back to + the main thread through a `StateFlow`. +- [ ] Register `Route.QrScanner` in `Routes.kt` / `AppNavigation.kt` and move the four call + sites onto it. `SimpleQrCodeScanner` keeps its `(String?) -> Unit` signature as a thin + wrapper so the wallet/bunker/login screens change by one import, not a rewrite. +- [ ] Camera permission with `accompanist-permissions` (already a dependency, used by + `TakePicture.kt`): inline rationale, and an "Open settings" path when permanently denied — + today a denied permission just bounces the user out of `CaptureActivity` with no + explanation. +- [ ] Focus: rely on CameraX's continuous AF, plus tap-to-focus through + `PreviewView.meteringPointFactory` → `FocusMeteringAction` with AF+AE, auto-cancel on. + This alone removes cause §1.1.2. +- [ ] Remove `com.journeyapps:zxing-android-embedded` from `libs.versions.toml` and + `amethyst/build.gradle.kts`. **Keep `libs.zxing`** — encoding still needs it. +- [ ] Delete the decorative crop: analyse the full frame (or a ≥90 % ROI), and let the + viewfinder cutout be purely visual. Fixes §1.1.6. + +### Phase 2 — Make each frame count + +- [ ] Decoder options: `formats = {QR_CODE, MICRO_QR_CODE, RMQR_CODE}`, `tryInvert = true`, + `tryRotate = true`, `tryDownscale = true`, `binarizer = LOCAL_AVERAGE`, + `maxNumberOfSymbols = 5`. `tryHarder` on the still/import path always; on the live path + alternate a cheap pass with a `tryHarder`/`tryDenoise` pass every Nth frame so the frame + rate stays interactive. +- [ ] **Zoom.** Pinch-to-zoom (`CameraControl.setZoomRatio` driven by a + `TransformableState`) plus a 1×/2× quick toggle. Then auto-zoom: after ~1.2 s with no + decode, ramp the zoom 1.0 → 2.0 → back over a couple of seconds; if a code *is* decoded + but its `Position` spans less than ~25 % of the frame's short side, zoom so it fills + ~60 % and re-read. Fixes §1.1.4, the single biggest "it just won't read" cause. +- [ ] **Torch.** A visible toggle in the overlay, plus auto-suggest: the analysis frame's Y plane + gives mean luminance for free — under a threshold for ~1 s, surface the torch button + prominently (never auto-fire it; that's rude in a bar and worse in a meeting). +- [ ] Dedupe: ignore an identical payload re-decoded within 1.5 s so a held-steady code doesn't + fire the handler repeatedly. +- [ ] Structured Append: accumulate parts keyed by `sequenceId`, show "2 of 3 captured", emit + once complete, time out after 30 s. Groundwork for large/animated payloads. + +### Phase 3 — Tell the user what happened + +This is the cheapest phase and probably the largest perceived improvement. + +- [ ] Split today's single `null` into an explicit outcome type: `Cancelled`, + `PermissionDenied`, `Decoded(text) → Route`, and `Decoded(text) → unroutable`. +- [ ] On a decode: haptic tick (`HapticFeedbackType.LongPress`, matching `SwipeToDelete.kt` and + the wallet wizard) plus a brief highlight drawn over the code's four corners. +- [ ] On **unroutable** content, show a bottom sheet with the decoded text, what we think it is, + and actions — *Open link* for `http(s)`, *Copy*, *Search*, *Try again*. Closes the + ambiguity behind §1.1.9 and [#417](https://github.com/vitorpamplona/amethyst/issues/417). +- [ ] When ≥2 codes are in frame, draw a tappable box over each and let the user pick, instead + of silently taking whichever ZXing found first. +- [ ] Extract payload classification out of `MainActivity.uriToRoute` into a pure, JVM-testable + `classifyScannedPayload(text): ScannedPayload` (nostr entity / wallet connect / bunker / + nostrconnect / http(s) / lightning / unknown). `uriToRoute` keeps routing; the scanner + gets a testable "what is this?" answer, and a bare 64-char hex pubkey — issue #417's + original case — becomes trivial to accept. + +### Phase 4 — Scan things that aren't in front of the camera + +- [ ] **From the gallery:** a picker button in the overlay + (`ActivityResultContracts.PickVisualMedia`) → decode the bitmap with the full + `tryHarder + tryRotate + tryInvert + tryDenoise` option set, and on failure retry at + 2× and 0.5× scale before giving up. +- [ ] **From the clipboard:** if the clipboard holds an image, offer "Scan copied image"; if it + holds text that `classifyScannedPayload` recognises, offer to use it directly. Covers the + overwhelmingly common "someone sent me a screenshot of their npub" flow. +- [ ] **Shared in:** accept `ACTION_SEND` with an `image/*` MIME type into the scanner, so + "Share → Amethyst" from a gallery or chat app resolves the code. (The manifest already has + an `ACTION_SEND` image target for new posts — this needs to be a distinct, explicitly + labelled entry, not a hijack of that one.) + +### Phase 5 — The display side + +Half of a scan is the code on the *other* screen. + +- [ ] `ShowQRScreen` does **not** boost brightness, while `ShareNoteAsQrScreen.kt:238-249` + already does. Extract that into a `KeepScreenBrightAndOn()` composable (brightness 1f + + `FLAG_KEEP_SCREEN_ON`, restoring the previous value on dispose) and use it on both. On a + dim OLED in dark mode this is the difference between scannable and not. +- [ ] `QrCodeDrawer.kt` hardcodes `CornerRadius(20f)` for the finder patterns regardless of how + many pixels a module is — at small draw sizes that rounds a meaningful fraction of the + finder away. Scale the radius with module size (cap around 20 % of a module). +- [ ] Error correction is fixed at `ErrorCorrectionLevel.Q`. For long payloads (an `nprofile` + with two relay hints, a Concord invite) Q pushes the version up, so modules get smaller — + and small modules, not error correction, are what actually defeats a camera at arm's + length. Measure Q vs M across our real payload lengths on the Phase 0 corpus and pick per + length rather than globally. +- [ ] Verify the `.clip(QuoteBorder)` 15 dp rounding never eats into the 4-module quiet zone at + the sizes we actually render. + +--- + +## 4. Testing + +| Level | What | +| --- | --- | +| JVM unit | `classifyScannedPayload` over every payload we claim to accept, plus junk, plus the bare-hex and `nsec` cases; Structured Append accumulator (ordering, duplicates, timeout). | +| androidTest | `QrDecodeCorpusTest` (Phase 0) as a permanent regression gate — pass rate per degradation category, asserted against the recorded baseline so a decoder or option change can't quietly regress. | +| androidTest | Bitmap-import path end to end: fixture → `decode(Bitmap)` → `ScannedPayload`. | +| Manual matrix | Printed sticker at 10/30/60 cm; phone screen at 30/60/100 cm; laptop screen across a desk; dark room with and without torch; behind glossy glass; 15°/30°/45° tilt; two codes in frame; inverted (light-on-dark) code. Record pass/fail before and after. | +| Macrobenchmark | Time from tapping "Scan QR" to first preview frame — the current external-activity cold start is part of what the change should erase. | + +## 5. Risks + +- **APK size** — ~0.8 MB compressed on arm64 for the native decoder, partly returned by + dropping zxing-android-embedded. Phase 0's gate is what justifies it. +- **F-Droid** — a new prebuilt `.so` from Maven Central; check with the F-Droid packaging before + landing rather than after. The ZXing-Java fallback keeps this from being a dead end. +- **Camera device variance** — CameraX is far more uniform than Camera1, but zoom ratio ranges + and torch availability still vary; every control must degrade to hidden rather than broken. +- **Scope creep into the display side** — Phase 5 is deliberately last and independent. +- **Desktop** — `desktopApp` has no scanner today and this plan doesn't add one. If webcam + scanning is ever wanted, `BarcodeDecoder` + the payload classifier are the reusable halves; + the CameraX screen is not. + +## 6. Out of scope + +Animated / BC-UR multi-frame QR *generation*, NFC handoff, and any change to what the four +existing call sites do with a successful payload. diff --git a/amethyst/plans/README.md b/amethyst/plans/README.md index 1aebbcf8fa..ca667f7c01 100644 --- a/amethyst/plans/README.md +++ b/amethyst/plans/README.md @@ -11,6 +11,7 @@ _Audited 2026-06-30. 21 plans: 19 shipped (archived), 1 in-progress, 1 queued, 0 ## Queued | Plan | Summary | | ---- | ------- | +| [2026-09-15-qr-reader-overhaul.md](2026-09-15-qr-reader-overhaul.md) | QR reader rebuilt on CameraX + zxing-cpp in-app (replacing the Camera1 zxing-android-embedded activity) — continuous AF, zoom, torch, full-frame decode, explicit failure feedback, and gallery/clipboard import. | | [2026-07-23-push-notification-redesign.md](2026-07-23-push-notification-redesign.md) | Per-kind tray notification redesign — accent colors, status-bar icons, MessagingStyle/BigPictureStyle/colorized zap cards, aggregation, Conversations/Bubbles; closes nutzap/onchain/repost/badge parity gaps. | | [2026-06-20-napplet-inter-applet.md](2026-06-20-napplet-inter-applet.md) | NAP-INC / NAP-INTENT inter-applet messaging — deferred; prerequisites (multi-applet hosting, archetype registry, `MESSAGING` capability) not yet built. | From 8f42e4cec2098a58a8a2ed9736e125547fad0659 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 02:27:40 +0000 Subject: [PATCH 002/127] feat(qr): rebuild the QR reader on CameraX + zxing-cpp The old scanner handed the whole job to zxing-android-embedded's CaptureActivity, which is a Camera1 app: autofocus is a 2s timer rather than continuous, there is no zoom and no discoverable torch, the decode is cropped to the viewfinder rect, and each frame gets exactly one un-retried attempt. We also passed MIXED_SCAN, which inverts alternate frames and so threw away half of every scan of an ordinary dark-on-light code. Replace it with an in-app Compose scanner on CameraX (already a declared but unused dependency) behind a BarcodeDecoder interface, with zxing-cpp as the engine: - Continuous autofocus, tap-to-focus, pinch zoom and a torch button; the analysis frame's Y plane gives mean luminance for free, so the torch is offered exactly when the scene is dark. - Auto-zoom sweeps 1x-2.5x while nothing is decoding, and stops for good once the user pinches. A code too small in frame is the one failure no amount of decoder tuning can fix. - tryInvert/tryRotate/tryDownscale on every frame and tryHarder/tryDenoise on every fifth, so a hard code gets several expensive attempts a second without the frame rate collapsing for easy ones. - The whole frame is decoded, not a centred box, and the preview and analysis use cases share one ViewPort so the overlay can highlight what we read. - Several codes in frame become tappable choices instead of a coin flip. - Structured Append (multi-part) codes are reassembled; we could not read them. Decoding a code we cannot route no longer looks identical to not reading one (issue #417): payloads are classified, and an unsupported one keeps the camera running and explains itself. Key material and pairing secrets are never echoed into that sheet -- a QR code is scanned in public by definition. A bare 64-char hex pubkey is re-encoded as an npub rather than rejected. Codes can now also be read from the gallery or the clipboard, which is how most of them actually arrive. On the display side, ShowQRScreen now pins brightness and keeps the screen awake (extracted from ShareNoteAsQrScreen, which already did), and the finder patterns' corner rounding scales with module size instead of a fixed 20px that deformed small codes. Licensing: io.github.zxing-cpp:android is Apache-2.0 (verified against its published POM) -- permissive, no linking-exception question. It adds a prebuilt .so per ABI, ~0.8 MB compressed on arm64 after the existing ABI split, partly returned by dropping zxing-android-embedded. Its AAR ships a consumer ProGuard keep rule, so R8 does not break the JNI boundary. Not yet verified: the plan's phase-0 decode corpus and the manual scan matrix both need a camera, so every camera-facing path here is compile-verified and reasoned-through only. Tracked in the plan's new section 7. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX --- amethyst/build.gradle.kts | 3 +- .../plans/2026-09-15-qr-reader-overhaul.md | 49 +- .../ui/note/share/ShareNoteAsQrScreen.kt | 45 -- .../qrcode/KeepScreenBrightAndAwake.kt | 77 +++ .../ui/screen/loggedIn/qrcode/QrCodeDrawer.kt | 14 +- .../screen/loggedIn/qrcode/QrCodeScanner.kt | 98 +-- .../ui/screen/loggedIn/qrcode/ShowQRScreen.kt | 4 + .../loggedIn/qrcode/scanner/BarcodeDecoder.kt | 155 +++++ .../qrcode/scanner/QrFrameAnalyzer.kt | 136 ++++ .../loggedIn/qrcode/scanner/QrImageImport.kt | 171 +++++ .../qrcode/scanner/QrScannerDialog.kt | 610 ++++++++++++++++++ .../qrcode/scanner/QrScannerOverlay.kt | 309 +++++++++ .../loggedIn/qrcode/scanner/QrScannerState.kt | 212 ++++++ .../qrcode/scanner/ScanOutcomeSheet.kt | 145 +++++ .../loggedIn/qrcode/scanner/ScanResult.kt | 85 +++ .../loggedIn/qrcode/scanner/ScannedPayload.kt | 159 +++++ .../scanner/StructuredAppendAccumulator.kt | 94 +++ .../qrcode/scanner/ScannedPayloadTest.kt | 139 ++++ .../StructuredAppendAccumulatorTest.kt | 113 ++++ .../font/material_symbols_outlined.ttf | Bin 478388 -> 484824 bytes .../composeResources/values/strings.xml | 29 + .../commons/icons/symbols/MaterialSymbols.kt | 3 + gradle/libs.versions.toml | 5 +- 23 files changed, 2563 insertions(+), 92 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/BarcodeDecoder.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrFrameAnalyzer.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrImageImport.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerOverlay.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerState.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanResult.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayload.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayloadTest.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulatorTest.kt diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index 0ea720bc54..9e234da0b4 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -504,8 +504,9 @@ dependencies { implementation(libs.accompanist.permissions) // For QR generation + // ZXing core encodes the QR codes we display; zxing-cpp decodes the ones we scan. implementation(libs.zxing) - implementation(libs.zxing.embedded) + implementation(libs.zxing.cpp) // OpenStreetMap tiles for road event location maps (kind 1315/1316) implementation(libs.osmdroid.android) diff --git a/amethyst/plans/2026-09-15-qr-reader-overhaul.md b/amethyst/plans/2026-09-15-qr-reader-overhaul.md index caff7d5e4b..9f4c49b50d 100644 --- a/amethyst/plans/2026-09-15-qr-reader-overhaul.md +++ b/amethyst/plans/2026-09-15-qr-reader-overhaul.md @@ -1,6 +1,6 @@ # QR Reader Overhaul -**Status:** queued (design + task plan; nothing implemented yet) +**Status:** phases 1-5 implemented (see §7 for what shipped and what did not); phase 0 outstanding **Goal:** make scanning a QR code in Amethyst fast and near-certain — codes that are small, far, dim, glossy, tilted, inverted, on a screen, or already sitting in the gallery should all resolve on the first try, and a code the app *can't* route should say so instead of silently @@ -300,3 +300,50 @@ Half of a scan is the code on the *other* screen. Animated / BC-UR multi-frame QR *generation*, NFC handoff, and any change to what the four existing call sites do with a successful payload. + + +--- + +## 7. What shipped, and what did not + +Implemented in this branch: + +| Phase | State | +| --- | --- | +| 0 — measurement corpus | **Not done.** No device or emulator in this environment, so the fixture corpus and `QrDecodeCorpusTest` could not be built *or run*, and none of the before/after decode-rate numbers that §1 and §2.1 call for exist yet. The gate it was supposed to provide — "drop zxing-cpp if it doesn't beat ZXing-Java" — has therefore not been exercised. | +| 1 — in-app CameraX scanner | Done. | +| 2 — per-frame decode quality | Done. | +| 3 — explicit outcomes | Done. | +| 4 — gallery / clipboard import | Done, minus the `ACTION_SEND` share-in target. | +| 5 — display side | Done. | + +### Deviations from the plan above + +- **A dialog, not a `Route`.** §2 proposed registering `Route.QrScanner`. One of the four call + sites is `KeyTextField` on the *logged-out* login screen, which lives outside the navigation + graph entirely, so a route could not serve it. `QrCodeScannerDialog` is a full-screen + `Dialog` instead, which also let all four call sites keep their existing + `SimpleQrCodeScanner { }` shape — the diff at each is one import. +- **`ScanOutcome` instead of "close, then explain".** For the sheet in §3 to appear, the scanner + has to still be open when the caller decides it cannot use the payload. So the callback returns + `ScanOutcome.Handled` / `ScanOutcome.NotSupported` rather than `Unit`, and the camera keeps + running through the explanation. +- **No "found but too small" auto-zoom branch.** §2 listed zooming toward a code whose + `Position` spans too little of the frame. That branch is unreachable: if the code decoded, we + are done with it. Auto-zoom now only sweeps while *nothing* is decoding, which is the case + that actually fails. +- **`ACTION_SEND` image share-in not wired.** The manifest already has an `ACTION_SEND` image + target aimed at new posts; adding a second, distinctly-labelled one is a manifest and routing + change that belongs with its own testing rather than bolted onto this branch. + +### Still to do + +1. **Phase 0, retroactively.** Build the corpus, run `QrDecodeCorpusTest` on a device, record + the numbers here, and confirm the engine choice against them. Until that happens, "zxing-cpp + beats ZXing-Java on degraded codes" is a well-founded expectation, not a measurement. +2. **The manual matrix in §4.** None of it has been run — there is no camera in this + environment. Every camera-facing behaviour in phases 1, 2 and 4 (binding, focus, torch, + auto-zoom, the overlay's coordinate mapping, the photo picker) is compile-verified and + reasoned-through only. +3. **F-Droid packaging sign-off** on the new prebuilt `.so`, per §5. +4. **The ECC level question** in §5 — still open, and still wants the corpus to answer it. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt index 9bbc15ae85..f760b19863 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt @@ -37,14 +37,12 @@ import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign @@ -61,7 +59,6 @@ import com.vitorpamplona.amethyst.commons.resources.share_as_qr_mode_nostr import com.vitorpamplona.amethyst.commons.resources.share_as_qr_mode_web import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote import com.vitorpamplona.amethyst.ui.components.LoadNote -import com.vitorpamplona.amethyst.ui.components.getActivityWindow import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -221,45 +218,3 @@ private fun ShareNoteAsQrScreenContent( * brightness/`keepScreenOn` state an incoming screen has already set. Nothing in the current nav * graph triggers that overlap, so this is left as a comment rather than code. */ -@Composable -private fun KeepScreenBrightAndAwake() { - val view = LocalView.current - // NOT `(view.context as? Activity)`: under Compose the context is routinely a - // ContextThemeWrapper, so that cast silently yields null and brightness never changes — - // no crash, no log, just a dead feature. getActivityWindow() unwraps the ContextWrapper - // chain (WindowUtils.kt:39-46). - val window = getActivityWindow() - - DisposableEffect(window, view) { - // Capture the RAW attribute, not a computed fraction. When no override is set this is - // BRIGHTNESS_OVERRIDE_NONE (-1f), and restoring that value returns the device to auto - // brightness. Restoring a *computed* fraction would install an override where none - // existed and silently disable auto-brightness for the rest of the session. - val previousBrightness = window?.attributes?.screenBrightness - - // F8: same capture/replay discipline as brightness above, and for the same reason. - // `view` is the Activity's single shared root ComposeView, and PlayerEventListener - // (ControlWhenPlayerIsActive.kt:150-165) owns this exact flag while media plays. - // Hard-setting `false` on dispose — instead of restoring what was here before this - // screen took it over — would clobber that ownership: navigating back from the QR - // screen while audio or video is still playing would let the screen sleep mid-playback. - val previousKeepScreenOn = view.keepScreenOn - - window?.let { - it.attributes = it.attributes.apply { screenBrightness = 1f } - } - view.keepScreenOn = true - - onDispose { - // Restore the captured value rather than calling a release helper: resetting to - // BRIGHTNESS_OVERRIDE_NONE unconditionally would clobber an override the user - // already had, e.g. one left by the fullscreen video controls. - window?.let { w -> - previousBrightness?.let { prev -> - w.attributes = w.attributes.apply { screenBrightness = prev } - } - } - view.keepScreenOn = previousKeepScreenOn - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt new file mode 100644 index 0000000000..c09e5029e6 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalView +import com.vitorpamplona.amethyst.ui.components.getActivityWindow + +/** + * Pins the screen to full brightness, and awake, while a QR code is on it. + * + * Half of "this QR code will not scan" is the phone showing it. A dark-mode OLED at the + * auto-brightness the room asked for can put so little contrast between the black and white + * modules that the other camera's binarizer cannot separate them — and the person holding it has + * no idea that is the problem, because to a human eye the code looks perfectly clear. + */ +@Composable +fun KeepScreenBrightAndAwake() { + val view = LocalView.current + // NOT `(view.context as? Activity)`: under Compose the context is routinely a + // ContextThemeWrapper, so that cast silently yields null and brightness never changes — + // no crash, no log, just a dead feature. getActivityWindow() unwraps the ContextWrapper + // chain (WindowUtils.kt:39-46). + val window = getActivityWindow() + + DisposableEffect(window, view) { + // Capture the RAW attribute, not a computed fraction. When no override is set this is + // BRIGHTNESS_OVERRIDE_NONE (-1f), and restoring that value returns the device to auto + // brightness. Restoring a *computed* fraction would install an override where none + // existed and silently disable auto-brightness for the rest of the session. + val previousBrightness = window?.attributes?.screenBrightness + + // F8: same capture/replay discipline as brightness above, and for the same reason. + // `view` is the Activity's single shared root ComposeView, and PlayerEventListener + // (ControlWhenPlayerIsActive.kt:150-165) owns this exact flag while media plays. + // Hard-setting `false` on dispose — instead of restoring what was here before this + // screen took it over — would clobber that ownership: navigating back from the QR + // screen while audio or video is still playing would let the screen sleep mid-playback. + val previousKeepScreenOn = view.keepScreenOn + + window?.let { + it.attributes = it.attributes.apply { screenBrightness = 1f } + } + view.keepScreenOn = true + + onDispose { + // Restore the captured value rather than calling a release helper: resetting to + // BRIGHTNESS_OVERRIDE_NONE unconditionally would clobber an override the user + // already had, e.g. one left by the fullscreen video controls. + window?.let { w -> + previousBrightness?.let { prev -> + w.attributes = w.attributes.apply { screenBrightness = prev } + } + } + view.keepScreenOn = previousKeepScreenOn + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt index a31a83b21c..cb3c1a7b64 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt @@ -48,6 +48,7 @@ import com.google.zxing.qrcode.encoder.ByteMatrix import com.google.zxing.qrcode.encoder.Encoder import com.google.zxing.qrcode.encoder.QRCode import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import kotlin.math.min /** * The quiet zone around the code, in **modules** — the QR spec's minimum of 4. @@ -59,6 +60,12 @@ import com.vitorpamplona.amethyst.ui.theme.QuoteBorder */ const val QR_QUIET_ZONE_MODULES = 4f +/** + * Corner rounding on the finder patterns, as a fraction of one module. Small enough to stay well + * inside what a decoder tolerates, large enough to keep the code from looking like a 1998 barcode. + */ +const val FINDER_CORNER_RADIUS_MODULES = 0.22f + @Preview @Composable fun QrCodeDrawerPreview() { @@ -90,7 +97,12 @@ fun QrCodeDrawer( // (zone included) is exactly as wide as the canvas. val rowHeight = size.height / (qrCode.matrix.height + QR_QUIET_ZONE_MODULES * 2f) val columnWidth = size.width / (qrCode.matrix.width + QR_QUIET_ZONE_MODULES * 2f) - val radius = CornerRadius(20f) + // Scale the rounding with the module size. A fixed 20px radius is a gentle touch on + // a large code and a serious deformation on a small one: the finder patterns are what + // a decoder locates first, and rounding away a third of a module's worth of their + // corners is exactly the kind of damage that makes a code readable on screen and + // unreadable in a photo of that screen. + val radius = CornerRadius(min(columnWidth, rowHeight) * FINDER_CORNER_RADIUS_MODULES) // Draw all of the finder patterns required by the QR spec. Calculate the ratio // of the number of rows/columns to the width and height diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt index ccb3c976a5..355c84bbce 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt @@ -20,64 +20,78 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import com.google.zxing.client.android.Intents -import com.journeyapps.barcodescanner.ScanContract -import com.journeyapps.barcodescanner.ScanOptions -import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.point_to_the_qr_code import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.QrCodeScannerDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.ScanOutcome +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.ScannedPayload +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.classifyScannedPayload import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CancellationException +/** + * Scans a QR code and navigates wherever it points. + * + * A payload we decode but cannot route no longer closes the scanner: it stays open and explains + * itself (see `ScanOutcomeSheet`), because "that is a Lightning invoice, not a profile" and "the + * camera never read anything" used to look identical from the outside. + */ @Composable fun NIP19QrCodeScanner( accountViewModel: AccountViewModel, onScan: (Route?) -> Unit, ) { - SimpleQrCodeScanner { - try { - if (it != null) { - onScan(uriToRoute(it, accountViewModel.account)) + QrCodeScannerDialog( + onDismiss = { onScan(null) }, + onScan = { contents -> + val route = routeFor(contents, accountViewModel) + if (route != null) { + onScan(route) + ScanOutcome.Handled } else { - onScan(null) + ScanOutcome.NotSupported } - } catch (e: Throwable) { - if (e is CancellationException) throw e - Log.e("NIP19 Scanner", "Error parsing $it", e) - // QR can be anything, do not throw errors. - onScan(null) - } - } + }, + ) } +/** + * The route a scanned string leads to, or null when nothing here can open it. + * + * A bare hex pubkey gets re-encoded as an npub first. Plenty of web tools hand out a raw + * 64-character key with no bech32 wrapper, and treating that as unreadable has been a reported + * papercut since 2023 (issue #417). + */ +private fun routeFor( + contents: String, + accountViewModel: AccountViewModel, +): Route? = + try { + val payload = classifyScannedPayload(contents) + val uri = if (payload is ScannedPayload.HexPubKey) payload.npub else contents + uriToRoute(uri, accountViewModel.account) + } catch (e: Throwable) { + if (e is CancellationException) throw e + Log.e("NIP19 Scanner", "Error parsing $contents", e) + // A QR code can hold anything at all. Never let one throw. + null + } + +/** + * Scans a QR code and hands back whatever it says. + * + * For callers that do their own validation — a wallet-connect URI, a `bunker://` offer, a key on + * the login screen. `null` means the user backed out. + */ @Composable fun SimpleQrCodeScanner(onScan: (String?) -> Unit) { - val qrLauncher = - rememberLauncherForActivityResult(ScanContract()) { - if (it.contents != null) { - onScan(it.contents) - } else { - onScan(null) - } - } - - val scanOptions = - ScanOptions().apply { - setDesiredBarcodeFormats(ScanOptions.QR_CODE) - setPrompt(stringRes(id = Res.string.point_to_the_qr_code)) - setBeepEnabled(false) - setOrientationLocked(false) - addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN) - } - - DisposableEffect(Unit) { - qrLauncher.launch(scanOptions) - onDispose {} - } + QrCodeScannerDialog( + onDismiss = { onScan(null) }, + onScan = { contents -> + onScan(contents) + ScanOutcome.Handled + }, + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt index 5f8e0c7fba..eebce7f091 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt @@ -195,6 +195,10 @@ fun PresentQR( accountViewModel: AccountViewModel, switchToScan: () -> Unit, ) { + // The other phone's camera needs contrast, and this screen was the one place we showed a QR + // code without asking for it (ShareNoteAsQrScreen has done so since it shipped). + KeepScreenBrightAndAwake() + RenderName(user, accountViewModel) Row( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/BarcodeDecoder.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/BarcodeDecoder.kt new file mode 100644 index 0000000000..67f6a03ee5 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/BarcodeDecoder.kt @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import android.graphics.Bitmap +import android.graphics.Rect +import androidx.camera.core.ImageProxy +import com.vitorpamplona.quartz.utils.Log +import zxingcpp.BarcodeReader + +/** + * How hard to work on one image. + * + * The live camera runs [Fast] on most frames and [Thorough] on every Nth, so a difficult code + * still gets the expensive treatment a few times a second without dropping the frame rate for + * the easy ones. Imported stills always get [Thorough] — there is only one image and the user is + * waiting on it. + */ +enum class DecodeEffort { + Fast, + Thorough, +} + +/** + * Decodes QR codes out of camera frames and still images. + * + * An interface, not a class, because the engine is a choice we want to be able to re-make: the + * camera plumbing, the overlay and every test above this line are engine-agnostic. + */ +interface BarcodeDecoder { + /** Decodes straight from a CameraX analysis frame. Called on the analysis executor. */ + fun decode( + image: ImageProxy, + effort: DecodeEffort, + ): List + + /** Decodes an imported still (gallery, clipboard, share). Called off the main thread. */ + fun decode( + bitmap: Bitmap, + effort: DecodeEffort = DecodeEffort.Thorough, + ): List +} + +/** + * [BarcodeDecoder] on zxing-cpp (Apache-2.0). + * + * Every option here maps to a failure the previous zxing-android-embedded scanner had: + * + * - `tryInvert` replaces the old `MIXED_SCAN`, which inverted *alternate frames* and so threw + * away half of all decode attempts on ordinary dark-on-light codes. zxing-cpp does the + * inverted pass as a fallback inside one call, so nothing is wasted. + * - `tryRotate` picks up codes held sideways. + * - `tryDownscale` finds codes that fill most of the frame, which a fixed-scale detector misses. + * - `tryHarder`/`tryDenoise` (Thorough only) are what actually rescue blurred, creased, + * low-contrast and photographed-off-a-screen codes. + * - `maxNumberOfSymbols = MAX_SYMBOLS` so several codes in frame become a choice for the user + * rather than a coin flip. + */ +class ZxingCppBarcodeDecoder : BarcodeDecoder { + private val fast = BarcodeReader(options(tryHarder = false)) + private val thorough = BarcodeReader(options(tryHarder = true)) + + private fun reader(effort: DecodeEffort) = if (effort == DecodeEffort.Fast) fast else thorough + + override fun decode( + image: ImageProxy, + effort: DecodeEffort, + ): List = + try { + reader(effort).read(image).toScanResults() + } catch (e: IllegalStateException) { + // read() rejects any format that is not YUV. We always request YUV_420_888, so this + // means the device handed us something else; log once per frame rather than crash. + Log.w("QrScanner") { "Unsupported analysis image format ${image.format}: ${e.message}" } + emptyList() + } + + override fun decode( + bitmap: Bitmap, + effort: DecodeEffort, + ): List = reader(effort).read(bitmap, Rect(0, 0, bitmap.width, bitmap.height)).toScanResults() + + private fun List.toScanResults(): List = + mapNotNull { result -> + val text = result.text + if (result.error != null || text.isNullOrEmpty()) return@mapNotNull null + + ScanResult( + text = text, + bounds = + result.position.let { + ScanBounds( + topLeft = ScanPoint(it.topLeft.x.toFloat(), it.topLeft.y.toFloat()), + topRight = ScanPoint(it.topRight.x.toFloat(), it.topRight.y.toFloat()), + bottomRight = ScanPoint(it.bottomRight.x.toFloat(), it.bottomRight.y.toFloat()), + bottomLeft = ScanPoint(it.bottomLeft.x.toFloat(), it.bottomLeft.y.toFloat()), + ) + }, + sequenceId = result.sequenceId, + sequenceIndex = result.sequenceIndex, + sequenceSize = result.sequenceSize, + ) + } + + companion object { + /** + * Enough to disambiguate a poster with a few codes on it without letting a page of + * barcodes turn every frame into a long detection run. + */ + const val MAX_SYMBOLS = 5 + + /** + * Only the square formats. Nostr uses plain QR, but Micro and rMQR cost nothing extra to + * accept and some hardware wallets and printed tags use them. Linear barcodes stay off: + * they have no meaning here and each extra family slows every frame down. + */ + private val FORMATS = + setOf( + BarcodeReader.Format.QR_CODE, + BarcodeReader.Format.MICRO_QR_CODE, + BarcodeReader.Format.RMQR_CODE, + ) + + private fun options(tryHarder: Boolean) = + BarcodeReader.Options( + formats = FORMATS, + tryHarder = tryHarder, + tryRotate = true, + tryInvert = true, + tryDownscale = true, + tryDenoise = tryHarder, + binarizer = BarcodeReader.Binarizer.LOCAL_AVERAGE, + maxNumberOfSymbols = MAX_SYMBOLS, + textMode = BarcodeReader.TextMode.PLAIN, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrFrameAnalyzer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrFrameAnalyzer.kt new file mode 100644 index 0000000000..d526f5e914 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrFrameAnalyzer.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.vitorpamplona.quartz.utils.Log +import kotlin.math.max + +/** One analysed frame: what was decoded, how big the analysed image was, and how dark it is. */ +data class FrameScan( + val results: List, + val frame: ScanFrame, + /** Mean luminance over a sparse sample of the Y plane, 0f (black) to 1f (white). */ + val brightness: Float, +) + +/** + * Decodes every camera frame and reports what it found. + * + * Two things happen per frame beyond the decode itself: + * + * 1. **Effort alternates.** Most frames get a fast pass; every [THOROUGH_EVERY]th gets + * `tryHarder` + `tryDenoise`. A stubborn code therefore still gets several expensive attempts + * a second, without the frame rate collapsing for codes that were never difficult. + * 2. **Brightness is measured.** A sparse sample of the Y plane costs almost nothing and is what + * lets the UI offer the torch exactly when the scene is too dark, rather than parking a + * permanent button in the corner or firing the light at people unprompted. + */ +class QrFrameAnalyzer( + private val decoder: BarcodeDecoder, + private val onFrame: (FrameScan) -> Unit, +) : ImageAnalysis.Analyzer { + private var frameCount = 0L + + override fun analyze(image: ImageProxy) { + image.use { + val effort = + if (frameCount++ % THOROUGH_EVERY == 0L) DecodeEffort.Thorough else DecodeEffort.Fast + + val brightness = meanLuminance(image) + + val results = + try { + decoder.decode(image, effort) + } catch (e: Exception) { + // A frame we cannot read is not worth killing the camera over. + Log.w("QrScanner") { "Decode failed on one frame: ${e.message}" } + emptyList() + } + + onFrame(FrameScan(results, rotatedFrameSize(image), brightness)) + } + } + + /** + * The size of the image the decoder actually saw. + * + * zxing-cpp is handed the crop rect and the rotation, and reports positions inside that + * cropped, rotated space — so a quarter-turn swaps width and height. The overlay maps + * [ScanBounds] onto the preview with this, so getting it wrong draws the highlight in the + * wrong place. + */ + private fun rotatedFrameSize(image: ImageProxy): ScanFrame { + val crop = image.cropRect + val quarterTurned = image.imageInfo.rotationDegrees % 180 != 0 + return if (quarterTurned) { + ScanFrame(crop.height(), crop.width()) + } else { + ScanFrame(crop.width(), crop.height()) + } + } + + /** + * Mean luminance over a grid of at most [LUMA_SAMPLES_PER_AXIS]² pixels. + * + * Uses absolute [java.nio.ByteBuffer.get] so it never disturbs the buffer position the + * decoder is about to read from. + */ + private fun meanLuminance(image: ImageProxy): Float { + val plane = image.planes.firstOrNull() ?: return 1f + val buffer = plane.buffer + val rowStride = plane.rowStride + val crop = image.cropRect + + val stepX = max(1, crop.width() / LUMA_SAMPLES_PER_AXIS) + val stepY = max(1, crop.height() / LUMA_SAMPLES_PER_AXIS) + + var sum = 0L + var count = 0 + var y = crop.top + while (y < crop.bottom) { + val row = y * rowStride + var x = crop.left + while (x < crop.right) { + val index = row + x + if (index in 0 until buffer.limit()) { + sum += buffer.get(index).toInt() and 0xFF + count++ + } + x += stepX + } + y += stepY + } + + return if (count == 0) 1f else sum.toFloat() / count / 255f + } + + companion object { + /** + * At ~30fps this is roughly six thorough passes a second — enough that a hard code + * resolves in well under a second, few enough that the analysis thread keeps up. + */ + const val THOROUGH_EVERY = 5L + + private const val LUMA_SAMPLES_PER_AXIS = 24 + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrImageImport.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrImageImport.kt new file mode 100644 index 0000000000..28e1d2601f --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrImageImport.kt @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import android.content.ClipboardManager +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.core.graphics.scale +import com.vitorpamplona.quartz.utils.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlin.math.max + +/** + * Decoding a QR code out of an image the user already has. + * + * This is the single biggest gap the camera-only scanner left: most QR codes people need to scan + * in a Nostr client arrive as a screenshot, a photo in a chat, or an image saved from a website. + * Before this, the only way to use one was to display it on a second screen and photograph it. + */ +object QrImageImport { + /** + * Longest edge we downscale a picked image to for the first attempt. + * + * Detection does not improve above this, and full-resolution phone photos are 50+ megapixels + * of mostly-wall that make a thorough pass take seconds. + */ + private const val FIRST_PASS_MAX_EDGE = 2_000 + + /** Below this, a code is likely too few pixels per module; the upscale pass is worth trying. */ + private const val SMALL_IMAGE_EDGE = 600 + + /** Decodes every QR code in the image at [uri], hardest-effort, with a retry ladder. */ + suspend fun decode( + context: Context, + uri: Uri, + decoder: BarcodeDecoder, + ): List = + withContext(Dispatchers.IO) { + val bounds = readBounds(context, uri) ?: return@withContext emptyList() + val longestEdge = max(bounds.outWidth, bounds.outHeight) + if (longestEdge <= 0) return@withContext emptyList() + + // Three passes, cheapest first. A code that a downscaled pass misses because its + // modules blurred together often survives at full resolution, and a code in a small + // thumbnail often needs *more* pixels per module than it shipped with. + val sampleSizes = + buildList { + add(sampleSizeFor(longestEdge, FIRST_PASS_MAX_EDGE)) + if (sampleSizeFor(longestEdge, FIRST_PASS_MAX_EDGE) != 1) add(1) + } + + for (sampleSize in sampleSizes) { + val found = decodeAt(context, uri, sampleSize, upscale = false, decoder) + if (found.isNotEmpty()) return@withContext found + } + + if (longestEdge <= SMALL_IMAGE_EDGE) { + val found = decodeAt(context, uri, sampleSize = 1, upscale = true, decoder) + if (found.isNotEmpty()) return@withContext found + } + + emptyList() + } + + /** Text sitting on the clipboard, or null when there is none. */ + fun clipboardText(context: Context): String? { + val clipboard = context.getSystemService(ClipboardManager::class.java) ?: return null + val clip = clipboard.primaryClip ?: return null + if (clip.itemCount == 0) return null + return clip + .getItemAt(0) + ?.coerceToText(context) + ?.toString() + ?.trim() + ?.takeIf { it.isNotEmpty() } + } + + /** An image sitting on the clipboard, or null when there is none. */ + fun clipboardImage(context: Context): Uri? { + val clipboard = context.getSystemService(ClipboardManager::class.java) ?: return null + val clip = clipboard.primaryClip ?: return null + if (clip.itemCount == 0) return null + return clip.getItemAt(0)?.uri + } + + private fun decodeAt( + context: Context, + uri: Uri, + sampleSize: Int, + upscale: Boolean, + decoder: BarcodeDecoder, + ): List { + val original = loadBitmap(context, uri, sampleSize) ?: return emptyList() + var upscaled: Bitmap? = null + return try { + if (upscale) upscaled = original.scale(original.width * 2, original.height * 2) + decoder.decode(upscaled ?: original, DecodeEffort.Thorough) + } catch (e: Exception) { + Log.w("QrScanner", "Could not decode picked image", e) + emptyList() + } finally { + upscaled?.recycle() + original.recycle() + } + } + + private fun readBounds( + context: Context, + uri: Uri, + ): BitmapFactory.Options? = + try { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + context.contentResolver.openInputStream(uri)?.use { BitmapFactory.decodeStream(it, null, options) } + options + } catch (e: Exception) { + Log.w("QrScanner", "Could not read image bounds", e) + null + } + + /** + * Always an ARGB_8888 software bitmap: zxing-cpp reads the pixels over JNI, and a + * hardware-backed bitmap has no pixels to read. + */ + private fun loadBitmap( + context: Context, + uri: Uri, + sampleSize: Int, + ): Bitmap? = + try { + val options = + BitmapFactory.Options().apply { + inSampleSize = sampleSize + inPreferredConfig = Bitmap.Config.ARGB_8888 + } + context.contentResolver.openInputStream(uri)?.use { BitmapFactory.decodeStream(it, null, options) } + } catch (e: Exception) { + Log.w("QrScanner", "Could not load picked image", e) + null + } + + /** The power-of-two `inSampleSize` that brings [longestEdge] to at most [target]. */ + private fun sampleSizeFor( + longestEdge: Int, + target: Int, + ): Int { + var sample = 1 + while (longestEdge / sample > target) sample *= 2 + return sample + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt new file mode 100644 index 0000000000..02e01a5130 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import android.Manifest +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.SystemClock +import android.util.Size +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.Camera +import androidx.camera.core.CameraSelector +import androidx.camera.core.FocusMeteringAction +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.Preview +import androidx.camera.core.UseCaseGroup +import androidx.camera.core.resolutionselector.AspectRatioStrategy +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionStrategy +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.lifecycle.awaitInstance +import androidx.camera.view.PreviewView +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.gestures.detectTransformGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.core.net.toUri +import androidx.lifecycle.compose.LocalLifecycleOwner +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.PermissionState +import com.google.accompanist.permissions.isGranted +import com.google.accompanist.permissions.rememberPermissionState +import com.google.accompanist.permissions.shouldShowRationale +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.point_to_the_qr_code +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_camera_blocked +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_camera_rationale +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_clipboard_empty +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_grant_camera +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_no_code_in_image +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_open_settings +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_try_again +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_unavailable +import com.vitorpamplona.amethyst.ui.call.openAppSettings +import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.quartz.utils.Log +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import kotlin.math.min + +/** What the caller did with a decoded payload — and therefore what the scanner does next. */ +enum class ScanOutcome { + /** The caller acted on it. Close the scanner. */ + Handled, + + /** Decoded fine, but this screen has nothing to do with it. Explain, and keep scanning. */ + NotSupported, +} + +/** + * Full-screen QR scanner. + * + * A dialog rather than a navigation route on purpose: one of its callers is the logged-out login + * field, which lives outside the navigation graph entirely. + */ +@Composable +fun QrCodeScannerDialog( + onDismiss: () -> Unit, + onScan: (String) -> ScanOutcome, +) { + Dialog( + onDismissRequest = onDismiss, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + dismissOnClickOutside = false, + decorFitsSystemWindows = false, + ), + ) { + SetDialogToEdgeToEdge() + Surface(modifier = Modifier.fillMaxSize(), color = Color.Black) { + QrScannerScreen(onDismiss = onDismiss, onScan = onScan) + } + } +} + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +private fun QrScannerScreen( + onDismiss: () -> Unit, + onScan: (String) -> ScanOutcome, +) { + val cameraPermission = rememberPermissionState(Manifest.permission.CAMERA) + + if (cameraPermission.status.isGranted) { + QrCameraScanner(onDismiss = onDismiss, onScan = onScan) + } else { + CameraPermissionGate(permission = cameraPermission, onDismiss = onDismiss) + } +} + +/** + * Asks for the camera, and explains itself when refused. + * + * The old scanner simply closed its activity when the permission was denied, which from the + * user's side is a button that does nothing. + */ +@OptIn(ExperimentalPermissionsApi::class) +@Composable +private fun CameraPermissionGate( + permission: PermissionState, + onDismiss: () -> Unit, +) { + val context = LocalContext.current + var asked by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + asked = true + permission.launchPermissionRequest() + } + + // `shouldShowRationale` is false both before the first ask and after a permanent denial, so + // the two are only distinguishable once we know we have asked. + val blocked = asked && !permission.status.shouldShowRationale + + Column( + modifier = Modifier.fillMaxSize().padding(32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = + if (blocked) { + stringRes(Res.string.qr_scanner_camera_blocked) + } else { + stringRes(Res.string.qr_scanner_camera_rationale) + }, + color = Color.White, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge, + ) + + if (blocked) { + Button(onClick = { openAppSettings(context) }) { + Text(stringRes(Res.string.qr_scanner_open_settings)) + } + } else { + Button(onClick = { permission.launchPermissionRequest() }) { + Text(stringRes(Res.string.qr_scanner_grant_camera)) + } + } + + TextButton(onClick = onDismiss) { + Text(stringRes(Res.string.qr_scanner_try_again), color = Color.White) + } + } +} + +@Composable +private fun QrCameraScanner( + onDismiss: () -> Unit, + onScan: (String) -> ScanOutcome, +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val haptic = LocalHapticFeedback.current + val scope = rememberCoroutineScope() + + val state = remember { QrScannerState() } + val decoder = remember { runCatching { ZxingCppBarcodeDecoder() }.getOrNull() } + + val currentOnScan by rememberUpdatedState(onScan) + val currentOnDismiss by rememberUpdatedState(onDismiss) + + val noCodeInImage = stringRes(Res.string.qr_scanner_no_code_in_image) + val clipboardEmpty = stringRes(Res.string.qr_scanner_clipboard_empty) + val decoderUnavailable = stringRes(Res.string.qr_scanner_unavailable) + + // One shared accept path: camera frames, a tapped candidate and an imported image all land + // here, so the haptic, the dedupe and the "we can't open this" branch behave identically + // however the payload arrived. + val submit: (String) -> Unit = { text -> + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + when (currentOnScan(text)) { + ScanOutcome.Handled -> currentOnDismiss() + ScanOutcome.NotSupported -> state.onRejected(classifyScannedPayload(text)) + } + } + + val analysisExecutor = remember { Executors.newSingleThreadExecutor() } + // CONFLATED: the analysis thread outruns the UI, and an old frame is worthless — the only + // one worth acting on is the newest. + val frames = remember { Channel(Channel.CONFLATED) } + + val previewView = + remember { + PreviewView(context).apply { + scaleType = PreviewView.ScaleType.FILL_CENTER + implementationMode = PreviewView.ImplementationMode.COMPATIBLE + } + } + + var camera by remember { mutableStateOf(null) } + var provider by remember { mutableStateOf(null) } + var viewSize by remember { mutableStateOf(IntSize.Zero) } + var focusRing by remember { mutableStateOf(null) } + + DisposableEffect(Unit) { + onDispose { + analysisExecutor.shutdown() + frames.close() + runCatching { provider?.unbindAll() } + } + } + + LaunchedEffect(decoder) { + if (decoder == null) state.notice = decoderUnavailable + } + + // Bind once the preview has a size: ViewPort needs a laid-out view, and binding both use + // cases under one viewport is what makes the overlay's coordinate mapping exact. + LaunchedEffect(decoder, viewSize) { + if (decoder == null || viewSize == IntSize.Zero) return@LaunchedEffect + + val cameraProvider = + try { + ProcessCameraProvider.awaitInstance(context) + } catch (e: Exception) { + Log.w("QrScanner", "Camera provider unavailable", e) + state.notice = decoderUnavailable + return@LaunchedEffect + } + provider = cameraProvider + + val preview = Preview.Builder().build().apply { setSurfaceProvider(previewView.surfaceProvider) } + + val analysis = + ImageAnalysis + .Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) + .setResolutionSelector(ANALYSIS_RESOLUTION) + .build() + .apply { + setAnalyzer(analysisExecutor, QrFrameAnalyzer(decoder) { frames.trySend(it) }) + } + + val group = + UseCaseGroup + .Builder() + .addUseCase(preview) + .addUseCase(analysis) + .apply { previewView.viewPort?.let { setViewPort(it) } } + .build() + + try { + cameraProvider.unbindAll() + camera = + cameraProvider.bindToLifecycle(lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, group).also { + state.torchAvailable = it.cameraInfo.hasFlashUnit() + state.maxZoomRatio = it.cameraInfo.zoomState.value + ?.maxZoomRatio ?: 1f + } + } catch (e: Exception) { + Log.w("QrScanner", "Could not bind the camera", e) + state.notice = decoderUnavailable + } + } + + LaunchedEffect(Unit) { + for (scan in frames) { + state.onFrame(scan, SystemClock.elapsedRealtime())?.let(submit) + } + } + + // One writer each for torch and zoom, driven off state rather than from the gesture handlers, + // so the auto-zoom sweep and a pinch cannot fight over the camera control. + LaunchedEffect(camera) { + val control = camera?.cameraControl ?: return@LaunchedEffect + snapshotFlow { state.torchOn }.collect { runCatching { control.enableTorch(it) } } + } + + LaunchedEffect(camera) { + val control = camera?.cameraControl ?: return@LaunchedEffect + snapshotFlow { state.zoomRatio }.collect { runCatching { control.setZoomRatio(it) } } + } + + AutoZoomSweep(state = state, enabled = camera != null) + + LaunchedEffect(state.notice) { + if (state.notice != null) { + delay(NOTICE_DURATION_MS) + state.notice = null + } + } + + LaunchedEffect(focusRing) { + if (focusRing != null) { + delay(FOCUS_RING_DURATION_MS) + focusRing = null + } + } + + val pickImage = + rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + if (uri == null || decoder == null) return@rememberLauncherForActivityResult + scope.launch { + val found = QrImageImport.decode(context, uri, decoder).firstOrNull()?.text + if (found == null) state.notice = noCodeInImage else submit(found) + } + } + + Box( + modifier = + Modifier + .fillMaxSize() + .onSizeChanged { viewSize = it } + .pointerInput(Unit) { + detectTransformGestures { _, _, zoom, _ -> + if (zoom != 1f) { + state.onPinch() + state.zoomRatio = (state.zoomRatio * zoom).coerceIn(1f, maxOf(1f, state.maxZoomRatio)) + } + } + }.pointerInput(Unit) { + detectTapGestures { tap -> + val picked = pickCandidateAt(tap, state, viewSize) + if (picked != null) { + state.onCandidateTapped(picked, SystemClock.elapsedRealtime())?.let(submit) + } else { + focusRing = tap + focusAt(previewView, camera, tap) + } + } + }, + ) { + AndroidView(factory = { previewView }, modifier = Modifier.fillMaxSize()) + + ScanOverlayCanvas(state = state, viewSize = viewSize, focusRing = focusRing) + + Text( + text = stringRes(Res.string.point_to_the_qr_code), + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier.align(Alignment.TopCenter).padding(top = 96.dp).fillMaxWidth(0.8f), + ) + + QrScannerControls( + state = state, + onClose = onDismiss, + onToggleTorch = { state.torchOn = !state.torchOn }, + onPickImage = { pickImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + onPaste = { + pasteFromClipboard( + context = context, + decoder = decoder, + onText = submit, + onImage = { uri -> + if (decoder != null) { + scope.launch { + val found = QrImageImport.decode(context, uri, decoder).firstOrNull()?.text + if (found == null) state.notice = noCodeInImage else submit(found) + } + } + }, + onEmpty = { state.notice = clipboardEmpty }, + ) + }, + ) + } + + state.rejected?.let { payload -> + ScanOutcomeSheet( + payload = payload, + onDismiss = { state.dismissRejection() }, + onOpenLink = { url -> + runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, url.toUri())) } + state.dismissRejection() + currentOnDismiss() + }, + onCopy = { text -> + copyToClipboard(context, text) + state.dismissRejection() + }, + ) + } +} + +/** Draws the aiming brackets, any codes we can see, and the tap-to-focus ring. */ +@Composable +private fun ScanOverlayCanvas( + state: QrScannerState, + viewSize: IntSize, + focusRing: Offset?, +) { + val highlight = MaterialTheme.colorScheme.primary + + Canvas(modifier = Modifier.fillMaxSize()) { + val mapping = ScanViewMapping.of(state.frame, viewSize) + + if (state.candidates.isEmpty()) { + drawViewfinderBrackets(Color.White.copy(alpha = 0.65f)) + } else if (mapping != null) { + state.candidates.forEach { candidate -> + candidate.bounds?.let { + drawPath( + path = it.toViewPath(mapping), + color = highlight, + style = Stroke(width = 4.dp.toPx()), + ) + } + } + } + + focusRing?.let { + drawCircle( + color = Color.White.copy(alpha = 0.8f), + radius = 28.dp.toPx(), + center = it, + style = Stroke(width = 2.dp.toPx()), + ) + } + } +} + +/** + * Sweeps the zoom while nothing is decoding. + * + * A code too small in frame to resolve is the single most common reason a scan fails, and no + * amount of decoder tuning fixes it — there are not enough pixels per module to read. Rather than + * leave the user to work that out and walk closer, the camera pushes in and back out on its own. + * It stops for good once the user pinches: they have taken over. + */ +@Composable +private fun AutoZoomSweep( + state: QrScannerState, + enabled: Boolean, +) { + LaunchedEffect(enabled, state.autoZoomEnabled) { + if (!enabled || !state.autoZoomEnabled) return@LaunchedEffect + + var sweep = 0f + while (isActive) { + delay(AUTO_ZOOM_TICK_MS) + + if (state.msSinceLastDetection < QrScannerState.AUTO_ZOOM_AFTER_MS) { + if (sweep != 0f) { + sweep = 0f + state.zoomRatio = 1f + } + continue + } + + val ceiling = min(state.maxZoomRatio, QrScannerState.AUTO_ZOOM_MAX) + if (ceiling <= 1.01f) continue + + sweep = (sweep + AUTO_ZOOM_TICK_MS.toFloat() / AUTO_ZOOM_PERIOD_MS) % 1f + val triangle = if (sweep < 0.5f) sweep * 2f else (1f - sweep) * 2f + state.zoomRatio = 1f + triangle * (ceiling - 1f) + } + } +} + +/** The visible code nearest the tap, or null when the tap was not on one. */ +private fun pickCandidateAt( + tap: Offset, + state: QrScannerState, + viewSize: IntSize, +): ScanResult? { + if (state.candidates.size <= 1) return null + val mapping = ScanViewMapping.of(state.frame, viewSize) ?: return null + + return state.candidates + .mapNotNull { candidate -> + val bounds = candidate.bounds ?: return@mapNotNull null + val center = bounds.centerInView(mapping) + val radius = maxOf(bounds.longestSide, MIN_TAP_RADIUS_PX) + val distance = (center - tap).getDistance() + if (distance <= radius) candidate to distance else null + }.minByOrNull { it.second } + ?.first +} + +private fun focusAt( + previewView: PreviewView, + camera: Camera?, + tap: Offset, +) { + val control = camera?.cameraControl ?: return + runCatching { + val point = previewView.meteringPointFactory.createPoint(tap.x, tap.y) + control.startFocusAndMetering( + FocusMeteringAction + .Builder(point, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE) + .setAutoCancelDuration(FOCUS_AUTO_CANCEL_SECONDS, TimeUnit.SECONDS) + .build(), + ) + } +} + +private fun pasteFromClipboard( + context: Context, + decoder: BarcodeDecoder?, + onText: (String) -> Unit, + onImage: (Uri) -> Unit, + onEmpty: () -> Unit, +) { + val image = QrImageImport.clipboardImage(context) + if (image != null && decoder != null) { + onImage(image) + return + } + + val text = QrImageImport.clipboardText(context) + if (!text.isNullOrBlank()) onText(text) else onEmpty() +} + +private fun copyToClipboard( + context: Context, + text: String, +) { + runCatching { + val clipboard = context.getSystemService(ClipboardManager::class.java) + clipboard?.setPrimaryClip(ClipData.newPlainText("", text)) + } +} + +/** + * 1280x720 is the sweet spot: plenty of pixels per module for a code at arm's length, while + * staying inside what every device can sustain at full frame rate through an analysis pipeline. + * Falling back higher before lower keeps detail on devices that cannot produce exactly this. + */ +private val ANALYSIS_RESOLUTION = + ResolutionSelector + .Builder() + .setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY) + .setResolutionStrategy( + ResolutionStrategy(Size(1280, 720), ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER), + ).build() + +private const val AUTO_ZOOM_TICK_MS = 100L +private const val AUTO_ZOOM_PERIOD_MS = 3_000f +private const val NOTICE_DURATION_MS = 3_000L +private const val FOCUS_RING_DURATION_MS = 800L +private const val FOCUS_AUTO_CANCEL_SECONDS = 4L +private const val MIN_TAP_RADIUS_PX = 120f diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerOverlay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerOverlay.kt new file mode 100644 index 0000000000..ac9fda3208 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerOverlay.kt @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.commons.icons.symbols.Icon +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.close +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_dark_hint +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_paste +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_pick_one +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_scan_image +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_sequence_progress +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_torch_off +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_torch_on +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_zoom_reset +import com.vitorpamplona.amethyst.ui.stringRes +import kotlin.math.max + +/** + * Maps the decoder's image space onto the preview. + * + * The preview is FILL_CENTER and the analysis frame shares its field of view — both use cases are + * bound under one [androidx.camera.core.ViewPort] — so one uniform scale plus a centring offset + * is exact. [max] rather than `min` because FILL_CENTER overfills and crops. + */ +class ScanViewMapping private constructor( + private val scale: Float, + private val dx: Float, + private val dy: Float, +) { + fun map(point: ScanPoint) = Offset(point.x * scale + dx, point.y * scale + dy) + + companion object { + fun of( + frame: ScanFrame, + viewSize: IntSize, + ): ScanViewMapping? { + if (frame.width <= 0 || frame.height <= 0 || viewSize.width <= 0 || viewSize.height <= 0) return null + + val scale = + max( + viewSize.width.toFloat() / frame.width, + viewSize.height.toFloat() / frame.height, + ) + return ScanViewMapping( + scale = scale, + dx = (viewSize.width - frame.width * scale) / 2f, + dy = (viewSize.height - frame.height * scale) / 2f, + ) + } + } +} + +fun ScanBounds.toViewPath(mapping: ScanViewMapping): Path = + Path().apply { + val start = mapping.map(topLeft) + moveTo(start.x, start.y) + mapping.map(topRight).let { lineTo(it.x, it.y) } + mapping.map(bottomRight).let { lineTo(it.x, it.y) } + mapping.map(bottomLeft).let { lineTo(it.x, it.y) } + close() + } + +fun ScanBounds.centerInView(mapping: ScanViewMapping): Offset = mapping.map(ScanPoint(centerX, centerY)) + +/** Four corner brackets marking where to aim. Purely decorative — we decode the whole frame. */ +fun DrawScope.drawViewfinderBrackets(color: Color) { + val side = minOf(size.width, size.height) * 0.68f + val left = (size.width - side) / 2f + val top = (size.height - side) / 2f + val arm = side * 0.12f + val stroke = Stroke(width = 3.dp.toPx(), cap = StrokeCap.Round) + + fun bracket( + x: Float, + y: Float, + dx: Float, + dy: Float, + ) { + drawPath( + Path().apply { + moveTo(x, y + dy * arm) + lineTo(x, y) + lineTo(x + dx * arm, y) + }, + color = color, + style = stroke, + ) + } + + bracket(left, top, 1f, 1f) + bracket(left + side, top, -1f, 1f) + bracket(left, top + side, 1f, -1f) + bracket(left + side, top + side, -1f, -1f) +} + +/** + * The chrome over the camera feed: close, torch, import, hints and progress. + * + * Deliberately a slot-free, single-purpose component — there is exactly one scanner screen, and + * pulling these five controls out into parameters would be ceremony without a second caller. + */ +@Composable +fun QrScannerControls( + state: QrScannerState, + onClose: () -> Unit, + onToggleTorch: () -> Unit, + onPickImage: () -> Unit, + onPaste: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier.fillMaxSize()) { + IconButton( + onClick = onClose, + modifier = Modifier.align(Alignment.TopStart).statusBarsPadding().padding(8.dp), + ) { + Icon( + symbol = MaterialSymbols.Close, + contentDescription = stringRes(Res.string.close), + tint = Color.White, + modifier = Modifier.size(28.dp), + ) + } + + if (!state.autoZoomEnabled && state.zoomRatio > 1.05f) { + ZoomChip( + zoomRatio = state.zoomRatio, + onReset = { state.resetZoom() }, + modifier = Modifier.align(Alignment.TopEnd).statusBarsPadding().padding(12.dp), + ) + } + + HintStack( + state = state, + modifier = + Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .padding(bottom = 96.dp, start = 24.dp, end = 24.dp), + ) + + Row( + modifier = + Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .fillMaxWidth() + .padding(bottom = 24.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp, Alignment.CenterHorizontally), + ) { + if (state.torchAvailable) { + OverlayButton( + symbol = if (state.torchOn) MaterialSymbols.FlashlightOff else MaterialSymbols.FlashlightOn, + description = + if (state.torchOn) { + stringRes(Res.string.qr_scanner_torch_off) + } else { + stringRes(Res.string.qr_scanner_torch_on) + }, + highlighted = state.torchOn, + onClick = onToggleTorch, + ) + } + OverlayButton( + symbol = MaterialSymbols.PhotoLibrary, + description = stringRes(Res.string.qr_scanner_scan_image), + onClick = onPickImage, + ) + OverlayButton( + symbol = MaterialSymbols.ContentPaste, + description = stringRes(Res.string.qr_scanner_paste), + onClick = onPaste, + ) + } + } +} + +/** + * The one line of text under the viewfinder. + * + * Only ever shows one message, most urgent first: a notice the user asked for ("no code in that + * picture") beats sequence progress, which beats "pick one of these", which beats the dark hint. + */ +@Composable +private fun HintStack( + state: QrScannerState, + modifier: Modifier = Modifier, +) { + val progress = state.sequenceProgress + val message = + when { + state.notice != null -> state.notice + progress != null -> stringRes(Res.string.qr_scanner_sequence_progress, progress.first, progress.second) + state.candidates.size > 1 -> stringRes(Res.string.qr_scanner_pick_one) + state.isDark && !state.torchOn -> stringRes(Res.string.qr_scanner_dark_hint) + else -> null + } ?: return + + Surface( + modifier = modifier, + shape = RoundedCornerShape(20.dp), + color = Color.Black.copy(alpha = 0.65f), + ) { + Text( + text = message, + color = Color.White, + fontSize = 14.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), + ) + } +} + +@Composable +private fun ZoomChip( + zoomRatio: Float, + onReset: () -> Unit, + modifier: Modifier = Modifier, +) { + Surface( + modifier = modifier.clickable(onClick = onReset), + shape = RoundedCornerShape(16.dp), + color = Color.Black.copy(alpha = 0.55f), + ) { + Text( + text = "%.1f× %s".format(zoomRatio, stringRes(Res.string.qr_scanner_zoom_reset)), + color = Color.White, + fontSize = 12.sp, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + ) + } +} + +@Composable +private fun OverlayButton( + symbol: MaterialSymbol, + description: String, + onClick: () -> Unit, + highlighted: Boolean = false, +) { + FilledIconButton( + onClick = onClick, + colors = + if (highlighted) { + IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ) + } else { + IconButtonDefaults.filledIconButtonColors( + containerColor = Color.Black.copy(alpha = 0.55f), + contentColor = Color.White, + ) + }, + modifier = Modifier.size(54.dp), + ) { + Icon(symbol = symbol, contentDescription = description, modifier = Modifier.size(26.dp)) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerState.kt new file mode 100644 index 0000000000..a2d1119a7f --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerState.kt @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +/** + * Everything the scanner UI draws, and the decision of what a frame means. + * + * Kept out of the composable so the accept/dedupe/multi-code rules are one readable block rather + * than conditions scattered through a camera callback. + */ +@Stable +class QrScannerState { + private val sequence = StructuredAppendAccumulator() + + /** The size of the image the decoder saw, for mapping [ScanResult.bounds] onto the preview. */ + var frame by mutableStateOf(ScanFrame(0, 0)) + private set + + /** + * Codes currently visible. One entry means we take it; several mean we draw them all and wait + * for a tap, because silently picking one of several codes is how you scan the poster next to + * the one you meant. + */ + var candidates by mutableStateOf>(emptyList()) + private set + + /** Set while a Structured Append payload is half-captured: captured count to total. */ + var sequenceProgress by mutableStateOf?>(null) + private set + + /** True when the scene has been too dark for [DARK_DWELL_MS]; the UI offers the torch. */ + var isDark by mutableStateOf(false) + private set + + var torchOn by mutableStateOf(false) + var torchAvailable by mutableStateOf(false) + + var zoomRatio by mutableFloatStateOf(1f) + var maxZoomRatio by mutableFloatStateOf(1f) + + /** + * Auto-zoom stops for good the first time the user pinches. Someone who has framed the shot + * themselves does not want the camera arguing about it. + */ + var autoZoomEnabled by mutableStateOf(true) + private set + + /** A payload we decoded but the caller could not use; drives the explain-what-happened sheet. */ + var rejected by mutableStateOf(null) + + /** A transient message (no code in that picture, empty clipboard, decoder unavailable). */ + var notice by mutableStateOf(null) + + private var lastSubmittedText: String? = null + private var lastSubmittedAt = 0L + private var darkSinceMs = 0L + + /** Milliseconds since anything at all was decoded — what auto-zoom watches. */ + var msSinceLastDetection by mutableStateOf(0L) + private set + + private var lastDetectionMs = 0L + + fun onPinch() { + autoZoomEnabled = false + } + + fun resetZoom() { + zoomRatio = 1f + } + + /** + * Folds one analysed frame into the UI state and decides whether we have an answer. + * + * Returns the payload to hand back to the caller, or null to keep scanning. Returning null is + * the normal case — nothing in frame, several codes in frame, a half-finished multi-part + * code, or the same code we just submitted. + */ + fun onFrame( + scan: FrameScan, + nowMs: Long, + ): String? { + frame = scan.frame + updateDarkness(scan.brightness, nowMs) + + // Seed the clock on the first frame. Left at zero, the very first empty frame would read + // as "nothing decoded since the epoch" and send auto-zoom hunting before the user has had + // a chance to aim. + if (lastDetectionMs == 0L) lastDetectionMs = nowMs + + // Nothing is decided while the "we can't open this" sheet is up: the user is reading it, + // and the offending code is very probably still sitting in front of the lens. + if (rejected != null) { + candidates = emptyList() + return null + } + + val found = scan.results.distinctBy { it.text } + if (found.isEmpty()) { + candidates = emptyList() + msSinceLastDetection = nowMs - lastDetectionMs + return null + } + + lastDetectionMs = nowMs + msSinceLastDetection = 0 + candidates = found + + // A multi-part code is never complete on its first part, so it can't be a single answer. + found.firstOrNull { it.isPartOfSequence }?.let { part -> + val joined = sequence.add(part, nowMs) + sequenceProgress = if (joined == null) sequence.captured to sequence.total else null + return joined?.let { accept(it, nowMs) } + } + + if (found.size > 1) return null + + return accept(found.first().text, nowMs) + } + + /** Taking one of several visible codes, because the user tapped it. */ + fun onCandidateTapped( + result: ScanResult, + nowMs: Long, + ): String? { + candidates = emptyList() + return accept(result.text, nowMs) + } + + /** + * Debounced hand-off. + * + * A code held in front of the lens decodes ~30 times a second. Without this the caller's + * handler fires 30 times, which for a navigation target means 30 stacked screens. + */ + private fun accept( + text: String, + nowMs: Long, + ): String? { + if (text == lastSubmittedText && nowMs - lastSubmittedAt < DEDUPE_MS) return null + lastSubmittedText = text + lastSubmittedAt = nowMs + return text + } + + /** Called when the caller rejects a payload, so the same code does not re-fire immediately. */ + fun onRejected(payload: ScannedPayload) { + rejected = payload + candidates = emptyList() + } + + /** Dismissing the sheet clears the dedupe latch so the user can retry the very same code. */ + fun dismissRejection() { + rejected = null + lastSubmittedText = null + lastSubmittedAt = 0 + } + + private fun updateDarkness( + brightness: Float, + nowMs: Long, + ) { + if (brightness > DARK_THRESHOLD) { + darkSinceMs = 0 + isDark = false + return + } + if (darkSinceMs == 0L) darkSinceMs = nowMs + isDark = nowMs - darkSinceMs >= DARK_DWELL_MS + } + + companion object { + /** Long enough that one steady code fires once; short enough to rescan on purpose. */ + const val DEDUPE_MS = 1_500L + + /** Mean luminance below this reads as "the torch would help". */ + const val DARK_THRESHOLD = 0.18f + + /** Don't offer the torch for a thumb over the lens or a moment of shadow. */ + const val DARK_DWELL_MS = 1_000L + + /** How long with nothing decoded before auto-zoom starts hunting. */ + const val AUTO_ZOOM_AFTER_MS = 1_200L + + /** Ceiling for the auto-zoom sweep — past this, focus and shake beat the extra reach. */ + const val AUTO_ZOOM_MAX = 2.5f + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt new file mode 100644 index 0000000000..1352a805b6 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_copy +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_cashu +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_lightning +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_nostr_unsupported +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_nsec +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_signer +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_text +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_wallet +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_web +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_open_link +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_try_again +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_unsupported_secret +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_unsupported_title +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.quartz.nip19Bech32.entities.NSec + +/** + * Explains a code we read but could not act on. + * + * This sheet is the whole point of classifying payloads. The old scanner collapsed "you + * cancelled", "the camera never read anything" and "that scanned perfectly but Amethyst has no + * screen for it" into the same silent return to the previous screen (issue #417), which trains + * people to believe the reader is broken when it is working exactly as designed. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ScanOutcomeSheet( + payload: ScannedPayload, + onDismiss: () -> Unit, + onOpenLink: (String) -> Unit, + onCopy: (String) -> Unit, +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) { + Column( + modifier = + Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(horizontal = 24.dp) + .padding(bottom = 24.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = stringRes(Res.string.qr_scanner_unsupported_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + ) + + Text( + text = explain(payload), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + // A QR code is scanned in public. Key material and pairing secrets never get echoed + // back onto the screen, no matter how useful it would be for debugging. + if (!payload.containsSecret) { + Text( + text = payload.raw, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + ) + } + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + if (payload is ScannedPayload.Web) { + Button(onClick = { onOpenLink(payload.url) }) { + Text(stringRes(Res.string.qr_scanner_open_link)) + } + } + if (!payload.containsSecret) { + TextButton(onClick = { onCopy(payload.raw) }) { + Text(stringRes(Res.string.qr_scanner_copy)) + } + } + TextButton(onClick = onDismiss) { + Text(stringRes(Res.string.qr_scanner_try_again)) + } + } + } + } +} + +@Composable +private fun explain(payload: ScannedPayload): String = + when (payload) { + is ScannedPayload.Nostr -> + if (payload.entity is NSec) { + stringRes(Res.string.qr_scanner_kind_nsec) + } else { + stringRes(Res.string.qr_scanner_kind_nostr_unsupported) + } + + is ScannedPayload.WalletConnect -> stringRes(Res.string.qr_scanner_kind_wallet) + is ScannedPayload.Bunker, is ScannedPayload.NostrConnect -> stringRes(Res.string.qr_scanner_kind_signer) + is ScannedPayload.Lightning -> stringRes(Res.string.qr_scanner_kind_lightning) + is ScannedPayload.Cashu -> stringRes(Res.string.qr_scanner_kind_cashu) + is ScannedPayload.Web -> stringRes(Res.string.qr_scanner_kind_web) + is ScannedPayload.HexPubKey, is ScannedPayload.Unknown -> stringRes(Res.string.qr_scanner_kind_text) + } + if (payload.containsSecret) "\n\n" + stringRes(Res.string.qr_scanner_unsupported_secret) else "" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanResult.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanResult.kt new file mode 100644 index 0000000000..ab0c14b126 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanResult.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +/** A point in the decoder's image space (origin top-left, after crop and rotation). */ +data class ScanPoint( + val x: Float, + val y: Float, +) + +/** + * The four corners of a decoded symbol, in the decoder's image space. + * + * Kept because they drive three things the old scanner could not do: highlighting the code we + * actually read, letting the user tap the right one when several are in frame, and measuring how + * small the symbol is so auto-zoom knows whether to push in. + */ +data class ScanBounds( + val topLeft: ScanPoint, + val topRight: ScanPoint, + val bottomRight: ScanPoint, + val bottomLeft: ScanPoint, +) { + val centerX: Float get() = (topLeft.x + topRight.x + bottomRight.x + bottomLeft.x) / 4f + val centerY: Float get() = (topLeft.y + topRight.y + bottomRight.y + bottomLeft.y) / 4f + + /** The longest edge of the quad — a rotation-independent stand-in for "how big is it". */ + val longestSide: Float + get() = + maxOf( + dist(topLeft, topRight), + dist(topRight, bottomRight), + dist(bottomRight, bottomLeft), + dist(bottomLeft, topLeft), + ) + + private fun dist( + a: ScanPoint, + b: ScanPoint, + ): Float { + val dx = a.x - b.x + val dy = a.y - b.y + return kotlin.math.sqrt(dx * dx + dy * dy) + } +} + +/** + * One decoded symbol. + * + * [sequenceSize] is greater than zero only for Structured Append codes — a payload split across + * several physical QR codes. [StructuredAppendAccumulator] reassembles those. + */ +data class ScanResult( + val text: String, + val bounds: ScanBounds?, + val sequenceId: String? = null, + val sequenceIndex: Int = -1, + val sequenceSize: Int = -1, +) { + val isPartOfSequence: Boolean get() = sequenceSize > 0 && sequenceIndex >= 0 +} + +/** The size of the image the decoder was handed, so callers can map [ScanBounds] onto a view. */ +data class ScanFrame( + val width: Int, + val height: Int, +) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayload.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayload.kt new file mode 100644 index 0000000000..2d07c6957a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayload.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser +import com.vitorpamplona.quartz.nip19Bech32.entities.Entity +import com.vitorpamplona.quartz.nip19Bech32.entities.NPub +import com.vitorpamplona.quartz.nip19Bech32.entities.NSec + +/** + * What a decoded QR string *is*, independent of whether any particular screen can act on it. + * + * The scanner needs this separately from routing: `uriToRoute` answers "where does this + * navigate", and collapses everything it doesn't recognise into `null` — which is how a + * perfectly good scan of an unsupported payload became indistinguishable from the camera never + * reading anything (issue #417). Classifying first lets the UI say *which* of those happened. + * + * Pure Kotlin on purpose: no Android, no `LocalCache`, no navigation. It is the one piece of the + * scanner that can be exhaustively unit-tested on the JVM. + */ +sealed interface ScannedPayload { + /** Exactly what the decoder read, trimmed. */ + val raw: String + + /** + * True when [raw] carries key material or a pairing secret — an `nsec`, an `ncryptsec`, a + * wallet-connect URI (its `secret=` is spendable), or a NIP-46 URI (its `secret=` authorises + * a signer). Any UI that echoes a payload back to the screen, copies it, or logs it MUST + * check this first. A QR code is scanned in public, over someone's shoulder, by definition. + */ + val containsSecret: Boolean + get() = false + + /** A NIP-19 entity, with or without the `nostr:` prefix. */ + data class Nostr( + override val raw: String, + val entity: Entity, + ) : ScannedPayload { + override val containsSecret get() = entity is NSec + } + + /** `nostrconnect://` — an app asking our signer to connect. Carries a `secret`. */ + data class NostrConnect( + override val raw: String, + ) : ScannedPayload { + override val containsSecret get() = true + } + + /** `bunker://` — a remote signer offering itself. Carries a `secret`. */ + data class Bunker( + override val raw: String, + ) : ScannedPayload { + override val containsSecret get() = true + } + + /** `nostr+walletconnect://` and friends. Carries a spendable `secret`. */ + data class WalletConnect( + override val raw: String, + ) : ScannedPayload { + override val containsSecret get() = true + } + + /** A BOLT-11 invoice, an LNURL, or a `lightning:` URI. */ + data class Lightning( + override val raw: String, + ) : ScannedPayload + + /** A Cashu token or payment request. Bearer money — never echo it. */ + data class Cashu( + override val raw: String, + ) : ScannedPayload { + override val containsSecret get() = true + } + + /** An `http(s)://` link. [url] is [raw] with any `web+nostr:` style wrapper removed. */ + data class Web( + override val raw: String, + val url: String, + ) : ScannedPayload + + /** + * A bare 64-character hex pubkey with no bech32 wrapper — what issue #417 hit in 2023 and + * what every "copy the pubkey" web tool still produces. [npub] is the same key encoded, so + * callers can hand it to the normal NIP-19 routing path. + */ + data class HexPubKey( + override val raw: String, + val npub: String, + ) : ScannedPayload + + /** Decoded fine, but it is not anything Amethyst knows how to act on. */ + data class Unknown( + override val raw: String, + ) : ScannedPayload +} + +private val HEX_64 = Regex("^[0-9a-fA-F]{64}$") + +private val LIGHTNING_PREFIXES = listOf("lightning:", "lnbc", "lntb", "lnbcrt", "lnurl") + +private val WALLET_CONNECT_PREFIXES = + listOf( + "nostr+walletconnect:", + "nostrwalletconnect:", + "nostr+walletconnect://", + "amethyst+walletconnect:", + ) + +/** + * Classify a decoded QR string. + * + * Scheme checks run before the NIP-19 scan on purpose: a `bunker://` or `nostrconnect://` URI + * embeds a hex pubkey and relay URLs, and letting the (deliberately permissive) NIP-19 regex + * look at those first risks matching a bech32-shaped fragment out of a relay path and routing + * somewhere absurd. + */ +fun classifyScannedPayload(text: String): ScannedPayload { + val raw = text.trim() + if (raw.isEmpty()) return ScannedPayload.Unknown(raw) + + val lower = raw.lowercase() + + if (lower.startsWith("bunker:")) return ScannedPayload.Bunker(raw) + if (lower.startsWith("nostrconnect:")) return ScannedPayload.NostrConnect(raw) + if (WALLET_CONNECT_PREFIXES.any { lower.startsWith(it) }) return ScannedPayload.WalletConnect(raw) + if (lower.startsWith("cashu") || lower.startsWith("creq")) return ScannedPayload.Cashu(raw) + if (LIGHTNING_PREFIXES.any { lower.startsWith(it) }) return ScannedPayload.Lightning(raw) + + Nip19Parser.uriToRoute(raw)?.let { return ScannedPayload.Nostr(raw, it.entity) } + + if (HEX_64.matches(raw)) { + val npub = runCatching { NPub.create(raw.lowercase()) }.getOrNull() + if (npub != null) return ScannedPayload.HexPubKey(raw, npub) + } + + if (lower.startsWith("http://") || lower.startsWith("https://")) { + return ScannedPayload.Web(raw, raw) + } + + return ScannedPayload.Unknown(raw) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt new file mode 100644 index 0000000000..964ec3ef9a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +/** + * Reassembles a Structured Append payload — one logical string split across several physical QR + * codes, each tagged with a sequence id, an index and a total. + * + * Why bother: a QR code's capacity falls off a cliff as the payload grows, because more data + * means more modules in the same physical space, and small modules are exactly what defeats a + * camera at arm's length. Splitting is how a long payload (a key backup, an `naddr` with several + * relay hints) stays scannable, and Structured Append is the standard way to do it. We could not + * read one at all before. + * + * Not thread-safe; it is driven from the analysis executor only. + */ +class StructuredAppendAccumulator( + private val timeoutMs: Long = DEFAULT_TIMEOUT_MS, +) { + private var sequenceId: String? = null + private var expected: Int = 0 + private var lastUpdateMs: Long = 0 + private val parts = mutableMapOf() + + /** Parts captured so far for the sequence in progress. */ + val captured: Int get() = parts.size + + /** How many parts the sequence in progress needs in total, or 0 when idle. */ + val total: Int get() = expected + + /** + * Feeds one decoded part in. + * + * Returns the joined payload once every part has been seen, or null while the sequence is + * still incomplete. A part from a different sequence, or one arriving after [timeoutMs] of + * silence, restarts the accumulation rather than corrupting it — someone who gives up + * halfway and points the camera at a different code should not get a splice of the two. + */ + fun add( + result: ScanResult, + nowMs: Long, + ): String? { + if (!result.isPartOfSequence) return null + + val id = result.sequenceId + val stale = nowMs - lastUpdateMs > timeoutMs + if (id != sequenceId || expected != result.sequenceSize || stale) { + reset() + sequenceId = id + expected = result.sequenceSize + } + + lastUpdateMs = nowMs + parts[result.sequenceIndex] = result.text + + if (parts.size < expected) return null + + val joined = (0 until expected).joinToString("") { parts[it] ?: return null } + reset() + return joined + } + + fun reset() { + sequenceId = null + expected = 0 + parts.clear() + } + + companion object { + /** + * Long enough to walk around a poster and catch the parts, short enough that an abandoned + * half-sequence does not linger into the next scan. + */ + const val DEFAULT_TIMEOUT_MS = 30_000L + } +} diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayloadTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayloadTest.kt new file mode 100644 index 0000000000..828e0609b2 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScannedPayloadTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import com.vitorpamplona.quartz.nip19Bech32.entities.NPub +import com.vitorpamplona.quartz.nip19Bech32.entities.NSec +import com.vitorpamplona.quartz.nip19Bech32.toNsec +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class ScannedPayloadTest { + private val pubkeyHex = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c" + private val npub = NPub.create(pubkeyHex) + + @Test + fun `npub with and without the nostr prefix both classify as nostr`() { + assertTrue(classifyScannedPayload(npub) is ScannedPayload.Nostr) + assertTrue(classifyScannedPayload("nostr:$npub") is ScannedPayload.Nostr) + assertTrue(classifyScannedPayload(" $npub ") is ScannedPayload.Nostr) + } + + @Test + fun `a bare hex pubkey is re-encoded rather than rejected`() { + val payload = classifyScannedPayload(pubkeyHex) + + assertTrue(payload is ScannedPayload.HexPubKey) + assertEquals(npub, (payload as ScannedPayload.HexPubKey).npub) + } + + @Test + fun `uppercase hex is accepted and normalises to the same npub`() { + val payload = classifyScannedPayload(pubkeyHex.uppercase()) + + assertTrue(payload is ScannedPayload.HexPubKey) + assertEquals(npub, (payload as ScannedPayload.HexPubKey).npub) + } + + @Test + fun `hex of the wrong length is not a pubkey`() { + assertTrue(classifyScannedPayload(pubkeyHex.drop(1)) is ScannedPayload.Unknown) + assertTrue(classifyScannedPayload(pubkeyHex + "ab") is ScannedPayload.Unknown) + } + + @Test + fun `signer and wallet schemes win over the nip19 scan`() { + assertTrue(classifyScannedPayload("bunker://$pubkeyHex?relay=wss%3A%2F%2Frelay.example") is ScannedPayload.Bunker) + assertTrue(classifyScannedPayload("nostrconnect://$pubkeyHex?relay=wss://r.example&secret=abc") is ScannedPayload.NostrConnect) + assertTrue(classifyScannedPayload("nostr+walletconnect://$pubkeyHex?relay=wss://r.example&secret=abc") is ScannedPayload.WalletConnect) + assertTrue(classifyScannedPayload("nostrwalletconnect://$pubkeyHex?secret=abc") is ScannedPayload.WalletConnect) + } + + @Test + fun `scheme matching ignores case`() { + assertTrue(classifyScannedPayload("BUNKER://$pubkeyHex") is ScannedPayload.Bunker) + assertTrue(classifyScannedPayload("Nostr+WalletConnect://$pubkeyHex") is ScannedPayload.WalletConnect) + } + + @Test + fun `lightning invoices and lnurls are recognised`() { + assertTrue(classifyScannedPayload("lnbc1u1pjxyz") is ScannedPayload.Lightning) + assertTrue(classifyScannedPayload("lightning:lnbc1u1pjxyz") is ScannedPayload.Lightning) + assertTrue(classifyScannedPayload("LNURL1DP68GURN8GHJ7") is ScannedPayload.Lightning) + } + + @Test + fun `web links are recognised and carry their url`() { + val payload = classifyScannedPayload("https://example.com/some/page") + + assertTrue(payload is ScannedPayload.Web) + assertEquals("https://example.com/some/page", (payload as ScannedPayload.Web).url) + } + + @Test + fun `an njump link resolves as the entity in its path, not as a web page`() { + // The NIP-19 scan runs before the http check, and matches anywhere in the string. That is + // deliberate: a scanned njump/nostr.at link should open the profile in the app rather + // than a web page whose only job is to redirect back into one. + assertTrue(classifyScannedPayload("https://njump.to/$npub") is ScannedPayload.Nostr) + } + + @Test + fun `plain text and blanks fall through to unknown`() { + assertTrue(classifyScannedPayload("WIFI:S:cafe;T:WPA;P:hunter2;;") is ScannedPayload.Unknown) + assertTrue(classifyScannedPayload("") is ScannedPayload.Unknown) + assertTrue(classifyScannedPayload(" ") is ScannedPayload.Unknown) + } + + @Test + fun `everything carrying key material or a pairing secret is marked secret`() { + val nsec = classifyScannedPayload(NSEC_FIXTURE) + assertTrue(nsec is ScannedPayload.Nostr) + assertTrue((nsec as ScannedPayload.Nostr).entity is NSec) + assertTrue(nsec.containsSecret) + + assertTrue(classifyScannedPayload("bunker://$pubkeyHex?secret=abc").containsSecret) + assertTrue(classifyScannedPayload("nostrconnect://$pubkeyHex?secret=abc").containsSecret) + assertTrue(classifyScannedPayload("nostr+walletconnect://$pubkeyHex?secret=abc").containsSecret) + assertTrue(classifyScannedPayload("cashuBo2Ftd").containsSecret) + } + + @Test + fun `public payloads are not marked secret`() { + assertFalse(classifyScannedPayload(npub).containsSecret) + assertFalse(classifyScannedPayload(pubkeyHex).containsSecret) + assertFalse(classifyScannedPayload("https://example.com").containsSecret) + assertFalse(classifyScannedPayload("lnbc1u1pjxyz").containsSecret) + assertFalse(classifyScannedPayload("just some text").containsSecret) + } + + @Test + fun `raw is always the trimmed input`() { + assertEquals(npub, classifyScannedPayload("\n $npub \t").raw) + } + + companion object { + /** A throwaway key, generated here so no real secret ever reaches a source file. */ + private val NSEC_FIXTURE = ByteArray(32) { (it + 1).toByte() }.toNsec() + } +} diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulatorTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulatorTest.kt new file mode 100644 index 0000000000..e20b9106b4 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulatorTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class StructuredAppendAccumulatorTest { + private fun part( + text: String, + index: Int, + size: Int, + id: String = "seq", + ) = ScanResult(text = text, bounds = null, sequenceId = id, sequenceIndex = index, sequenceSize = size) + + @Test + fun `parts in order join once the last one lands`() { + val acc = StructuredAppendAccumulator() + + assertNull(acc.add(part("abc", 0, 3), 0)) + assertNull(acc.add(part("def", 1, 3), 100)) + assertEquals("abcdefghi", acc.add(part("ghi", 2, 3), 200)) + } + + @Test + fun `parts out of order still join in index order`() { + val acc = StructuredAppendAccumulator() + + assertNull(acc.add(part("ghi", 2, 3), 0)) + assertNull(acc.add(part("abc", 0, 3), 10)) + assertEquals("abcdefghi", acc.add(part("def", 1, 3), 20)) + } + + @Test + fun `a repeated part does not complete the sequence`() { + val acc = StructuredAppendAccumulator() + + assertNull(acc.add(part("abc", 0, 3), 0)) + assertNull(acc.add(part("abc", 0, 3), 10)) + assertNull(acc.add(part("abc", 0, 3), 20)) + assertEquals(1, acc.captured) + } + + @Test + fun `progress reports captured against total`() { + val acc = StructuredAppendAccumulator() + + acc.add(part("a", 0, 3), 0) + assertEquals(1, acc.captured) + assertEquals(3, acc.total) + + acc.add(part("b", 1, 3), 10) + assertEquals(2, acc.captured) + } + + @Test + fun `a different sequence id restarts rather than splicing`() { + val acc = StructuredAppendAccumulator() + + acc.add(part("abc", 0, 2, id = "one"), 0) + assertNull(acc.add(part("xyz", 0, 2, id = "two"), 10)) + assertEquals(1, acc.captured) + assertEquals("xyzuvw", acc.add(part("uvw", 1, 2, id = "two"), 20)) + } + + @Test + fun `a part arriving after the timeout restarts the sequence`() { + val acc = StructuredAppendAccumulator(timeoutMs = 1_000) + + acc.add(part("abc", 0, 2), 0) + // The second part shows up two seconds late: treat it as the start of a new attempt + // rather than half of an abandoned one. + assertNull(acc.add(part("def", 1, 2), 3_000)) + assertEquals(1, acc.captured) + } + + @Test + fun `a result that is not part of a sequence is ignored`() { + val acc = StructuredAppendAccumulator() + + assertNull(acc.add(ScanResult("plain", bounds = null), 0)) + assertEquals(0, acc.captured) + } + + @Test + fun `completing a sequence clears the accumulator for the next one`() { + val acc = StructuredAppendAccumulator() + + acc.add(part("a", 0, 2), 0) + assertEquals("ab", acc.add(part("b", 1, 2), 10)) + assertEquals(0, acc.captured) + assertEquals(0, acc.total) + } +} diff --git a/commonsUI/src/commonMain/composeResources/font/material_symbols_outlined.ttf b/commonsUI/src/commonMain/composeResources/font/material_symbols_outlined.ttf index ee75754b15f86e293d35c41576ed347af45a2554..56d4724a422ef3556071d278a20a21fdc38cac90 100644 GIT binary patch delta 10715 zcmbt)3wTpS*Y27A{h-Jr~yR(JRD!s_V{*ywmvxSog`ZO<0BEfMKUB;|`d3=2eg|mRf%xQ)3m%y>H-Fwk4wL}LA$I`+vP-fA zZ~~cxK%WJVKV4DwQrRjb@BvW$q9-1nllWE3V*qM7yMKI6MLBtwqS&MY>FtZ>JpM@8 zq^e^8=l+4?>hdR^dOFzt$rONJHv zZoS`{WgYLdj)`4jjRAHa3kB#wucEme$y%mU}D~9QYv6ww8=c!^cMb_!R}4Jm`<4fFs(LCG{0wFYgU=Jm=2p_ zaa3*^Zk}l_HP#wWo171uUNe=LP8lzoCYuJLrfW?)liJv6MAI;qn~cWqOlIQ~#w#YJ zF)#Z0==SI-#ztG@8qiqP9zC&>qsh zj#l*wHbFaE>%x(GZL3zUou=8RS)tWwa&_8LZNBCU098QsxNMQUruvj(aVi;uc5$mQ zo_}4Wi2Wvo(sw=?R((@pOCm|qEwkiX7qJriU%zpJmDsA^Cq|AaaYk%JYwsZTe^%dA zX0o|^U@v~}$M)BSk^yV%`V2XkoSS-Xr0*dlhm0imz&w&hGSKky;58z`JFpRI;Vhhk z^CXqz!aA4@Igker!Y25WM8PSjhcgg_U*Q)x3P<1s6vMahJ;Xp7sG$>la061w(zTJw1JP`pfCxq}OH0GLkdy&zPU_VaCrHH~Y`*zb+Fp3p1b2+?3fip#OmS zfr|$I$H3pRG+9|$)3UZ?{gG|VUY>m=yDLYT6PuHkb9c`8oLM>LIcsu$&sFCp<&Mss zo$Fkd`$q28-0yQ+@?!Fa<;~7}E$?7nd%i7yYW{2a#|x4Q$_w5us4t8u99lTB@R7n* zgjr&1=x|X?(U_vOMJJ2AcdPE+HP|%x-oZ--ZyS7ah-^sz zA!6P0U@#6^Z z$gGizMt(H%bV)=>ddc%8pWZY0p4Io9xFXW?%i?jduQEyV7zR6_V@+kw~ar4 z-?ICDD4ke(;QoI1FS`HSgnkn$C*1eZ#Hfj5Cw?^X+@wL1o}6@Z^3cgAr}Ufh_0*)P z8>gO_W|%f?+TQ89>C>mbJpH#aYuVdn*Jli#Q8}aIf%_iVJCn>DJahTX;Db{ieE-3= zS%tHndwB0ozlLP(>?qD0?G!MgmU1U(Imkc@Q4kAv0rZVb&Pj-tMRG~mxdV*`ktXCM z2no@q$gIdbS%HQWmy-I@FB207CMJGKibMNZE8UDAvb)0LsR)fCJR2LBNVcR@=qf^$ zq_Sc+CW6~*QBdJp5S8Ohh~{#$lN_SKNN+9bA@#pVtXHvBIA29Y=z$(?p#btvNkRS~ zurxqRC9zc~bUgG!v6G_{B1sxb4a%z4%v_Is_=jubn zn>Tw(OP$_QQWAR0fh%o}%zwL*>?iX4pgps4v!>Kmp({;sWOypxKFMVNYrzQ6Gacbd zvgBxjno1Zzl2Ko%2F5!7?NB+X4=p4mUQ$w8dW$78=UOu=LVLU^707KNRM07O)Xt1o zkB{CNKE0NZg6g$r|ApTh&km`6=G^C)_fmi1shV?fE4i1rm$_Bk>YW+2^}IEre8Ce> zKQU|3qYo{avt+scZ+ANt7e~m4M9f<>=cxsY9-Y77=~+)K*8lA;W8R~S7HRH;=FN-x z`|ZwAS3N>B0TK?QO<)fiNP%n&ybG(x-xx&JR=;{j@p$R{U&?5qXyg%!w`p9A^oSs>s11IU33Kz=X-`RQpO$0h+eu?NVRV?chvN3!2$ z1NnU~kU!diG?W6l>ITwu1jvn3Kw7o|Y1;?HD+kiG07!QlYDNhh90DgNmI0^O2%Kge zaFHW`i{1vD$p@UZ1UTCZz&Tz9uFnzR`r(AT+JWnT2{_EmT<$U83SIo8AiCT)cmD0&q_t{Zog4do~1|6X{$Z1NYJ%;9hwaxHYSt!2J^$Z7>7( zfgQN5bAj7-3b=o70q(OCz_uQ{%fV+T# z{P7TQmp=gRniaUF?|^gf2Cf-5)tUla8*avjLi>w>3*-ZpIe;q90gZCj0yVu1)H)ET z?E|2F>w)%L3^Zpm&>}C;p-4PpE6`B}prbbe9h(I7{&_$rKLB*OQF zKr4~ii@5MhYM`s$0{TiN&^1SZzJd2|mIJNE3G3Q{uE#m=W&nME9MFGZ+u92BKgR>z zo(uHT1weO{0sRc|&r^VYaSZ6L3D|JAyY~RylM3|fwLtgd=DsTjC;fgM&_nxx9#aB6 zi4)J+fYzcW>PmrLLkl1Mhe&uH1SG5BF+5+I;z{RSu{j&u44Sxc^=?w7OMBul-1N_bhfd3)`_}xo@ z-_rp6-fh4i7y==#!*&h$ zb~QF1@SWKEIN-aBfDd3FT!igB@S#=^_*X$tOaVdnHVDQ#5bXINI1Yl)_cIXE27{2% z48q`S5QaYl!Z3q;iXIvR__5}?J*FlUk70$ z4!no>w(TH%at4I27K3mAnI8EFgj1J5sNV=e!#oh0R)XM81)&p#3A_y=?*LIe1fpgX zi27GSjLifwel>`Fvq4PR4&s0^5OZ;?Xg`QUGe8{afLL)0Fx?ValtCQ#aq z*tFH_e0LGe&V#-$g*(8NAqIXL+93V~)=UxzqPr>u@}W$S7}TUhNYJDR@~a#v2Y?Zq zwkFpo!4DaUR*A40VKu_f+DS4p_`ptHQX_0d2@>qun#^>G`T^{i46#TZgVbvWO7Px+ zt+<@{l>}vc zMSfwFsVGGT!kTX-DGI+O>+jsug%c7rf0Ep^Befi<_L zFHYxfsw7+0)+`;uC8)CO6s_KXO|xrl0r!XS*B~xdgHYD4vqQLJOiOno>p{r+p!H2eT{n(~^Vc&mBaG#I4bcq^w7md3s z`%Hpg{)~H=IXv5j2AQ<(kzm_bTqL6wk(wQN{s9S|dw?srbIHPE5+VkvdpI)cCOciC17kV7GI5LZa#16R-)Y`wk+_eFT!8VNqQhMJgX^t#uj zb(FckvW9BeUV352CW(6dCc2#2WiNbUs{{wO(gFq-*n4;n{zQToenN*cA7x5qiRg>3 zCHT_UbT~WSUfat!Ai-q^XcL3`AoT#dcHL1LSM%uiRD;q~d{3RyG29+v2cLG7US}t4 z03$YSjs1+oXWSWjgY935uoB^6wbG6j*3$QxWyT<^LD+CnqAtEjpSu(O?y>}XF4M2s zj_zeEBJ~$;3BKf}kFp)vk%=AQN!=1$*-f8gS)w;s@^YRDm?`J4G65Y(?Lca~R)WWA z`5Jb_MFsde~vN;v$77v(rYIoL2VJUi^19oFGzc6c`Z^wZMPk+2y* ztagmOYr}o`!f7UE_z^$50ZSL5{rE(-o82><6uTf+t=4E-Tie??QRG5?e~1qR_yEsy zTqqQ%jx}&0w>!ich|{b$hxK`M^ls7(HzFR z&Q5h_Cv4F2yhejsaxygqT3Z7F*9NV(y}egAp%7}yg>-?yrAvW8D8z9*Z?o|{l8Jh~ z&&P9qKgaugdcBBb#OJG$p(dC%3zMM;oXPAN$S7-0mtF?wxJkP(=&|mve*G z!1HFdcig<$%<~2pyi2mPSCJz}UfM_-8!eWXs0FvY5`|H#>*{b*Y*+0LM|(T2!hW(% z;jW~gF0FlWMVjoHWvzo2&V}XCGiG;3yA`8(r&8NXtXzPwo+NJ3%ALI zLs}i>JRFsW{CsDS57E&78&70;hq)6pMO<1N8L4k>ZehoNYY_v_xj84V40Z)O0z!xn zVa=k;6)lqm{r(^oMT%yvY1L?CvaYVy>PiywRvEn^P)0-8}V zh@98Ui5(p=F-k@j4&QZGI2;T*DcbGXvxFeKa9dkML>L{6gd-x_+QN*^VDNYh1}2T$ zt<$;PNap7G`uc9sU=X|O>v`VI$S8FMipJw#L}MdlNzf3He=GuTCCJ#L}$ zbTUUkm*Cm6`JI#hcXMwk3}Yq}T%~VjlK0rY%H6??9qnG=cyKA0jXRmKquCRg%mu+J zZLrt8A>?go@rG7P{Fqhu@KgAhygin!@=<2VXw%54k|nYNOk|_+gvZ%={HqOMQ^Ige zWF?r$5;2h_Vq#3hM3#t&EFKeCqTLyd6BIZ>#9ofQfRj`>f#RS6n+BT>n;u&PHgycv zB~chjoSu%|vBtv&iaG4l;&1{PGNc6Fz|TeeU>{C!Z5SHP3}=#0ON-$mf8gTO`SXXv zE^zK*BrcN?nf7e4hi8xa2yg6YkwvL)bae$=y1F`VboTGBx^bhWTVpg>4L*-XsaJ>E zApl!%a{uk+3Fht=@>Xg>jOYP{x>J*Jer}XIjp43!lN`&52cd*_kw|M*) z>ds?5)2kCjo!#&Iy;*zTeA-X z{g3UK0+n992Ezrk0rOZ4@^!5Si{(Ym3lZk{JV;Ep>)nZYVHW9mgW-Y~v`(6SE!uEf zX66#?A8wyFbnJ+U>90C@<;spLSAGs(iHg~=EgwvCOox>?rs{aH#%$%huLhgV6b5!DtwK_II}qvx`rfiabaTS^#-|| z5it^$2Qz{}rIO?DNdrkjA-V4qz2f6o+T<7ss>d4LY* zR%=?C3*J))bZ> z+hYBHOVxE%q0q~kJ3G6sc6NIGN-wW$Y`p60FhwUBgl>V|3r3aR zi+L8`61(te0TZrS+1lLvA}51R^PjbF?T8GhW?2BKU!U&jps$) z9}ai5h4Cxg#kaKuywS=ig$O}eP_9t~F-ueG6P=<;#FNC-OMf+)42F~xgW*>^$-aHL zP|~%tXAT{ztvzvq9k%HAi+ar6;g*(gSdS!>ivDK*mAd-+6DLlcVkG!r5&FSkFd7RB zjm964q+h=vEpXR2oIc&zdF>h_!6)N@Rj=3S8X9yut80TZ;^@%;ZD?+~+TLDU+t9#h znCmuhoX=;q`g{$@X37+$H#XSPaQN_z8)wfViM(47x=B(}e0&ubACI{^!td|qL%%j$ zIC-+Z{>&K{NXa%8-`=tKjen5>?3LI!Yy_JEn}|(bGb)`#IQ23X{eWn z-gdWK;Xh<=)Oq;cnv3tPrEclHb%mRs$_g^M2xlRDp-Y10ettfC`R!#=Aqwn`db21v zS)rjnQWqod6s5r4)+Z?iCu8zF!mlwVCJC0Cg_F?;M0yhwsK772vC@=rimdfzUQ;8P_+4qI|G!o!eSH%WqvCJEP> z+(~vLHZ0)Jk>s8>NBEG{Bzj9%{6Yy%TPR5N@!pzD>5~$Z6;BGMSs8>$&8kt;mPzo+ zWx{k;WgUa?Xk4c&!AVUn~5^1nez;@7*LV*|14SWSeKD3Vd6w z(QTEei?#}39m1^e%!&;oc1hHWb_o$o?gWIHPG0;*qTc?EFo;#2SV4yUJ)-p|iQ4kB zFrR6Vm2cP|HC{a_QGan#7{uLzSyjh>T7r|#2){A7S0~C}BsljM;VgsAb|aP|u-H>8 z!Ns)#W)>_7{Vp8FQ5lZP*l3-!a#5X7Wx%7|*DCa563jperr}Z^pETLpCA`OKKdd(2 z58>}*A~PG0Oq4N&GU^AgL>MWdX_4X}1J+*_^cAZW*atGiAxvXzaw<+9kt>mv=Ze)# zzgC3Vp8?b(BshPBXkw?4g)p<{X%i)QC3eh;(h+8Vqu4!Df*WRv7n!;AQXg3;!M=s! zdyKjl-u#RNA9+T+%+#KZOtKl1svag)Vi#MI6{U(0ZhTXM^>2wekq8g48{x1W9qvO3 zUh$zgK!w}dzFoYVscHevnt{XZpG(8`{~_MTX0b}B1({VG5+%nwBvvye6e7$jlSRiQ z>IKKd0%kU>7{#1=Tb%@-s}sAJV&iTxIe$%p`6lrVCO`AAB&6QhF7f`ZT|CN^z|6f5 o!tdaJ@4(YJNMs$%_EbtjzpB0T|r|A^-pY delta 6419 zcmY+I2Ut|c+Q;8HbIzfNyMVB}EX(dvL_~U%CLj=+4N8M7Pa0ck`oYkk~&zVLn6ayl2P;Z=?KV|H&oJAJ^E@Ow(VcN$tXWebw z-U;BEEr2SJPl3?7a=?=914k+2U)Tz?8r#9$%G8nke(IGkyPH!<3|U;UkYtjI^FkV_ za1-qbxoP&1{!X0Do26UCu418lnV33yI(v3BCVMJ9W#e}kKpSgqxNo>^R2hQ}y$wGa z*BibwxEU0N?uI}^nPIs>X~@(+)^F0UFwD|VGL-AX0raQ!)%v~qWBMcdIQ?k-aE~gF z$;?OZ=CMZiOgB}xQnwpGw^)ah>VY$_bJfk%_0k3EGEBNu_h~vDp1a0fgI70qf7a05 z#$D1jyHC@mYqeT;?P0e!+GOoGZJ^fCZLix*08Ol$2Y?&Z{HU3zE>pL%Hky^{GEKaC z4gNKh)zw$jHR=ZSa`gbs22HWXPE)4-S?!Kb1$ByMuqI1Yuez)@<*SR;sp<==Tj~Mo z5S-yUwL&ecUa0os-&56&s^e;B)f82OT2w{4&U1b0I#AU?74CB1rO7qeb+v1P%L*r@ z<1bF*ovNK$9mlY4ojL+2|8N53pN`JT)5=wtO-ry)F;;3T2QlW@) zRAwo|6quhKUUrydGuieF^G&;{ddZOoDJ|CHZ6~glZw4Ootg{Fzf~$ z{6S*jHW>=Xpd7ZCWrwq-I;~1OM!Qa1?=HH>xes$Mb3dn3>9TZ7bo+GAJd7Si9veLx z^)C8s{Y?EK{WXKp(ATiSu+Q+oXk*MXt~SKK0kCebRO0DhHp3DPkoPd(RZ2AWq+4iKWD!vzwLglrmksSmv_D7PyC1a zuk&x}*16l~-L7E0I!ac)BhMU7*MifNMj`%EMeZ;PaBN68#ZbUqe zw2d4V`FZ5F$kUO(MLv&mh%$LcB}Yw;Dvdf3^;>tl?up%}ci+^#Hrg(_cl5UC$1$!k zc`>Ci$73$WG{%atQL!np*|B3|O|ko9Z^dl&99R~q+oe8>2{@iXI>_lW4Rw#T_1 zPZN?8iV{i_?jEq%TCX&7T6J1$x;{N7eO7vT`tyv~jI|lpGHK?Z%=MY4GaIsK zmRpu@R&v&$tZ7+Gvi4=2%=#^>sTcGb-Rs-lUcHz0KG(aoPq#i(`fTbO+qbx%s^8Ln zkFtZaH)KERpVEI)|EoEQoJlzwa@q!@L<~4IP#m~+;G;pG4tkI~D%YHQZLl=hH2Crm z-H^;7WkVW<4j;O27#Wr_?EB%)!%f3ajUXf9M^xu&^9u7S^ZsXKLV>A5t0dtSo+sSGAq_yT|-EBvtQkIyw=n`!x!tx>mseVbmMoi;_|IT+lt(#hmtv|*_9NUbDQJH zO7oiLPe_T`p(S|GF*v~!{3o~oErbw>*bqC?fhdR*=|o)F&LldbCr09hn}uy7e-c1K zaWh4cXc9|$unk61$Va3%=|}pLL1Zu)YF^!C1&cwuS-Oz8}cW1R#4efb1&(3qX!!0XeY@$jL?^r%wVoi%0A) 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 Profile Banner Point to the QR Code + Amethyst needs the camera to scan QR codes. Nothing is recorded or uploaded — frames are decoded on the device and discarded. + Allow camera + Camera access is turned off for Amethyst. You can turn it back on in Android settings. + Open settings + The QR decoder could not start on this device. You can still paste the code as text. + Turn on the light + Turn off the light + It is dark — try the light + Scan a picture + Paste + No QR code found in that picture + Nothing on the clipboard to scan + Tap the code you want + Captured %1$s of %2$s parts — keep going + Reset zoom + Amethyst can\'t open this code + This code carries a private key or a pairing secret, so its contents are not shown here. Use the screen that expects it. + This is a private key. Paste it on the login screen instead. + This is a wallet connection. Add it from Wallet settings. + This is a remote signer. Add it from the NIP-46 signer screen. + This is a Lightning invoice. + This is a Cashu token. + This is a web link. + This is plain text, not a Nostr code. + This is a Nostr code, but not one this screen can open. + Open link + Copy + Copied + Scan again Show QR Profile Picture Your Profile Picture diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbols.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbols.kt index c4425921a5..1ac39519bf 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbols.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbols.kt @@ -115,6 +115,8 @@ object MaterialSymbols { val FileOpen = MaterialSymbol("\uEAF3") val FilterAlt = MaterialSymbol("\uEF4F") val FitnessCenter = MaterialSymbol("\uEB43") + val FlashlightOff = MaterialSymbol("\uF00A") + val FlashlightOn = MaterialSymbol("\uF00B") val Folder = MaterialSymbol("\uE2C7") val FolderZip = MaterialSymbol("\uEB2C") val ForkRight = MaterialSymbol("\uEBAC") @@ -189,6 +191,7 @@ object MaterialSymbols { val PersonRemove = MaterialSymbol("\uEF66") val Phone = MaterialSymbol("\uF0D4") val Photo = MaterialSymbol("\uE432") + val PhotoLibrary = MaterialSymbol("\uE413") val PictureAsPdf = MaterialSymbol("\uE415") val PictureInPicture = MaterialSymbol("\uE8AA") val PlayArrow = MaterialSymbol("\uE037") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08582067ef..3dd4e182d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -93,7 +93,8 @@ jcodec = "0.2.5" commonsImaging = "1.0.0-alpha6" thumbnailator = "0.4.21" zxing = "3.5.4" -zxingAndroidEmbedded = "4.3.0" +# zxing-cpp: Apache-2.0. Native QR/barcode detector used by the in-app CameraX scanner. +zxingCpp = "3.1.1" webkit = "1.17.0" # Cross-process UI embedding (SurfaceControlViewHost wrapper) for the in-app browser surface. Apache-2.0. privacysandboxUi = "1.0.0-alpha17" @@ -252,7 +253,7 @@ vico-charts-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", ver zelory-image-compressor = { group = "id.zelory", name = "compressor", version.ref = "zelory" } zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zoomable" } zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" } -zxing-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } +zxing-cpp = { group = "io.github.zxing-cpp", name = "android", version.ref = "zxingCpp" } androidx-window-core-android = { group = "androidx.window", name = "window-core-android", version.ref = "windowCoreAndroid" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinTest" } From 1f3929d74adaa18a8ff46d8892357a977c9cc469 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 02:37:44 +0000 Subject: [PATCH 003/127] fix(qr): correct two compile errors in the new scanner - ShareNoteAsQrScreen never got the import for the KeepScreenBrightAndAwake composable that moved out of it, so the call site did not resolve. - StructuredAppendAccumulator bailed out of a joinToString lambda with `return null`. joinToString is not inline, so that non-local return is illegal; rebuilt as an explicit loop over the expected indices. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX --- .../amethyst/ui/note/share/ShareNoteAsQrScreen.kt | 1 + .../qrcode/scanner/StructuredAppendAccumulator.kt | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt index f760b19863..cb2418f6dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt @@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.KeepScreenBrightAndAwake import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt index 964ec3ef9a..86d6a845d8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/StructuredAppendAccumulator.kt @@ -73,9 +73,17 @@ class StructuredAppendAccumulator( if (parts.size < expected) return null - val joined = (0 until expected).joinToString("") { parts[it] ?: return null } + // Built with an explicit loop rather than joinToString: bailing out on a missing index + // needs a real `return`, and joinToString is not inline, so a non-local one is illegal + // there. The count can be right and an index still missing, if a malformed code + // reported an index outside 0 until sequenceSize. + val joined = StringBuilder() + for (index in 0 until expected) { + joined.append(parts[index] ?: return null) + } + reset() - return joined + return joined.toString() } fun reset() { From 3397c4198548a103b464615fc0b901b39fbdb922 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 14:09:31 +0000 Subject: [PATCH 004/127] =?UTF-8?q?feat:=20My=20Fitness=20=E2=80=94=20a=20?= =?UTF-8?q?personal=20training=20dashboard=20over=20Health=20Connect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Google rejected the Health Connect declaration a second time, this time as "use of permission is not a permitted/valid use case". That was correct. The policy admits six use cases; the nearest is "Fitness, wellness and coaching — apps designed to help users track, monitor, analyze, manage, and improve their physical fitness". Amethyst did none of those. Its Workouts section was a feed of other people's kind 1301 events plus a composer, so the honest description of the integration was "read health data in order to publish it", which is not an approved use case and reads as the prohibited "publicly displaying or socially sharing sensitive data". So the app now does the thing the permissions are for. My Fitness (Workouts → chart icon) summarises the user's own training back to them: this week against last week, the four-week weekly average, where the time goes by activity, best efforts, active days and a consecutive-day streak. It is computed on device, requires no account interaction, sends nothing, and never asks the user to post. Publishing becomes one optional per-workout action on a dashboard row. Every permission now earns itself against that screen rather than against the composer: exercise drives counts, time, breakdown, active days and streak; distance, calories, steps and elevation drive weekly totals, trends and bests; heart rate drives the duration-weighted effort average and the max. WorkoutStats holds all the arithmetic as pure Kotlin so it is testable without Health Connect, a device, or Compose — 13 tests cover window trimming, the seven-day split, absent-metric handling, duration weighting, ordering, streak edges (a rest day today does not break a live streak; a missed day does) and the weekly average. The window is 28 days, not 30: Health Connect serves 30 days without READ_HEALTH_DATA_HISTORY, which Amethyst does not request, and four whole weeks keeps the "this week vs last week" comparison inside that limit. The declaration, PRIVACY.md and the store listing are rewritten to lead with tracking and describe sharing as the secondary, per-item, user-confirmed action it now is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019egdJyBHnrATZHjs86up8f --- PRIVACY.md | 32 +- .../workouts/health/HealthConnectManager.kt | 8 +- .../service/workouts/health/WorkoutStats.kt | 265 ++++++++ .../amethyst/ui/navigation/AppNavigation.kt | 2 + .../amethyst/ui/navigation/routes/Routes.kt | 3 + .../topbars/UserDrawerSearchTopBar.kt | 5 + .../loggedIn/workouts/WorkoutsTopBar.kt | 24 +- .../workouts/fitness/MyFitnessFormat.kt | 79 +++ .../workouts/fitness/MyFitnessScreen.kt | 591 ++++++++++++++++++ .../workouts/fitness/MyFitnessViewModel.kt | 92 +++ .../suggestion/DetectedWorkoutCarousel.kt | 2 +- .../workouts/health/WorkoutStatsTest.kt | 249 ++++++++ .../composeResources/values/strings.xml | 37 ++ docs/health-connect-play-declaration.md | 254 ++++---- .../android/en-US/full_description.txt | 38 +- 15 files changed, 1529 insertions(+), 152 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessFormat.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt diff --git a/PRIVACY.md b/PRIVACY.md index 42a65123c3..ab3c669159 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -3,7 +3,7 @@ **App:** Amethyst (Android Nostr client)
**Publisher:** Vitor Pamplona
**Contact:** amethyst@vitorpamplona.com
-**Last updated:** 2026-09-12 +**Last updated:** 2026-09-15 Amethyst is free, open-source software (MIT License — see `LICENSE`). It is not a service. There is no Amethyst server, no Amethyst account, and the developer has no access to data stored on your device. @@ -32,33 +32,37 @@ Configuration, cached events, keys, drafts, and other operational data live in t ### Health and fitness data (Health Connect) -Amethyst's **Workouts** section lets you publish a summary of a finished workout to the Nostr relays you choose (a NIP-101e kind 1301 event), so the people who follow you can see it. To save you typing the numbers in by hand, Amethyst can read the workout your watch or fitness app already saved to **Android Health Connect** and pre-fill the post. +Amethyst's **My Fitness** screen (Workouts → the chart icon) summarises your own training for you: how much you did this week against last week, how your time splits across activities, your best efforts, how many days you trained, and your current streak. It builds that picture from the workouts your watch or fitness app has already saved to **Android Health Connect**. -The feature is optional and off until you grant the permissions. Amethyst asks for them only when you open the New Workout composer — never on first launch. +This is what the health permissions are for. The summary is computed on your phone and shown to you; nothing is sent anywhere to produce it, and you never have to post anything to use it. + +Separately, you may choose to publish one workout as a Nostr post (a NIP-101e kind 1301 event) so the people who follow you can see it. That takes a deliberate tap on "Share this workout", shows you the pre-filled post, and waits for you to confirm. It is never automatic. + +The feature is optional and off until you grant the permissions. Amethyst asks for them only when you open My Fitness or the New Workout composer — never on first launch. **What Amethyst reads, and what each type is for:** | Health Connect data type | Permission | What it is used for | | --- | --- | --- | -| ExerciseSession | `READ_EXERCISE` | The workout itself: activity type, start time and duration — the title, date and duration of the post. | -| Distance | `READ_DISTANCE` | The distance of the run, ride, walk or swim. | -| ActiveCaloriesBurned | `READ_ACTIVE_CALORIES_BURNED` | The energy the workout burned. | -| TotalCaloriesBurned | `READ_TOTAL_CALORIES_BURNED` | Fallback energy figure for sources that only record total energy. | -| HeartRate | `READ_HEART_RATE` | Average and maximum heart rate over the workout — how hard the effort was. | -| Steps | `READ_STEPS` | The step count of a run, walk or hike. | -| ElevationGained | `READ_ELEVATION_GAINED` | How much you climbed. | +| ExerciseSession | `READ_EXERCISE` | The workout itself: activity type, start and end. Drives your workout count, training time, per-activity breakdown, active days and streak. | +| Distance | `READ_DISTANCE` | Weekly distance, the change against last week, your weekly average, distance per activity, and your longest distance. | +| ActiveCaloriesBurned | `READ_ACTIVE_CALORIES_BURNED` | Weekly energy burned and its week-over-week change. | +| TotalCaloriesBurned | `READ_TOTAL_CALORIES_BURNED` | Fallback for the same figure, for watches and apps that only record total energy. | +| HeartRate | `READ_HEART_RATE` | Average and maximum heart rate per workout, your duration-weighted average for the period, and your highest heart rate. | +| Steps | `READ_STEPS` | Your weekly step average and your highest step count. | +| ElevationGained | `READ_ELEVATION_GAINED` | Your weekly climb average and your biggest climb. | Health Connect groups a few data types under one permission: `READ_EXERCISE` also covers CyclingPedalingCadence and `READ_STEPS` also covers StepsCadence. Amethyst does not read, store, or publish cadence — those types come attached to the permissions above and are never requested separately. **Limits on this access:** - **Read-only.** Amethyst never writes to Health Connect. -- **Foreground only.** Reads happen only while the New Workout composer is on screen. Amethyst does not request `READ_HEALTH_DATA_IN_BACKGROUND` and has no background health worker. -- **Last 7 days only.** Only sessions that finished in the previous 7 days are offered. Amethyst does not request `READ_HEALTH_DATA_HISTORY`. +- **Foreground only.** Reads happen only while the My Fitness screen or the New Workout composer is on screen. Amethyst does not request `READ_HEALTH_DATA_IN_BACKGROUND` and has no background health worker. +- **Last four weeks only.** Amethyst reads a rolling 28-day window and cannot see anything older. It does not request `READ_HEALTH_DATA_HISTORY`. - **No location.** Amethyst does not request `READ_EXERCISE_ROUTE`, so it never receives the GPS track of a workout. -- **Nothing is uploaded automatically.** Health data stays on your device until you pick a suggestion, review the pre-filled post, and publish it yourself. The developer runs no server; a published post goes to the Nostr relays you configured, and those numbers then become public like any other post you make. +- **Nothing is uploaded automatically.** Health data stays on your device. The My Fitness summary is computed locally and never transmitted. A workout only leaves your phone if you tap "Share this workout", review the pre-filled post, and publish it yourself — one workout at a time. The developer runs no server; a published post goes to the Nostr relays you configured, and those numbers then become public like any other post you make. - **No other use.** Health data is never used for advertising, analytics, profiling, or sale, and is never shared with third parties. It is not used to determine your eligibility for insurance, credit, or employment, and is not transferred to any such party. -- **Revocable.** Turn the feature off under Settings → Compose → "Suggest workouts to share", or revoke the permissions in Health Connect at any time. Amethyst keeps the workout suggestions it has already shown only in memory; revoking access stops all reads immediately. +- **Revocable.** Revoke the permissions in Health Connect at any time — My Fitness immediately drops back to its prompt — or turn the composer suggestions off under Settings → Compose Settings → "Suggest workouts to share". Amethyst keeps the summary and the suggestions only in memory; revoking access stops all reads immediately. ### What relays can see diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt index 8ffd1397d7..5c9c9befef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt @@ -128,12 +128,12 @@ class HealthConnectManager( * are skipped. Returns an empty list (never throws) if Health Connect is * unavailable or a read fails. */ - suspend fun readNewWorkouts( + suspend fun readWorkouts( since: Instant, now: Instant = Instant.now(), ): List { if (!isAvailable(context)) { - Log.i(TAG) { "readNewWorkouts: Health Connect unavailable (status=${HealthConnectClient.getSdkStatus(context)})" } + Log.i(TAG) { "readWorkouts: Health Connect unavailable (status=${HealthConnectClient.getSdkStatus(context)})" } return emptyList() } @@ -150,12 +150,12 @@ class HealthConnectManager( timeRangeFilter = TimeRangeFilter.between(since, now), ), ) - Log.i(TAG) { "readNewWorkouts: ${response.records.size} exercise session(s) in window $since .. $now" } + Log.i(TAG) { "readWorkouts: ${response.records.size} exercise session(s) in window $since .. $now" } val mapped = response.records.mapNotNull { mapSession(it) } // Fold split-up sessions of the same activity (a long run broken around // breaks) into one suggestion so the composer offers the whole effort. val merged = WorkoutMerger.mergeCloseWorkouts(mapped) - Log.i(TAG) { "readNewWorkouts: mapped ${mapped.size} -> ${merged.size} workout(s) after type/duration filtering and merging" } + Log.i(TAG) { "readWorkouts: mapped ${mapped.size} -> ${merged.size} workout(s) after type/duration filtering and merging" } merged } catch (e: Exception) { if (e is CancellationException) throw e diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt new file mode 100644 index 0000000000..eac9ea8ff0 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service.workouts.health + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import kotlin.math.roundToInt + +/** + * Turns a flat list of [DetectedWorkout] into the figures the My Fitness screen shows the + * user about their own training: period totals, week-over-week movement, a per-activity + * breakdown, bests, and how consistently they have been showing up. + * + * Pure and platform-free on purpose — every number the screen displays is derived here, so + * the arithmetic can be tested without Health Connect, a device, or Compose. + * + * Everything is scoped to [WINDOW_DAYS]. Health Connect only serves the most recent 30 days + * unless an app also holds `READ_HEALTH_DATA_HISTORY`, which Amethyst deliberately does not + * request; four whole weeks is the largest honest window inside that limit, and it makes the + * "this week vs last week" comparison land inside it too. + */ +object WorkoutStats { + /** Four whole weeks — see the class note on the 30-day Health Connect limit. */ + const val WINDOW_DAYS = 28L + + /** Days in the recent-comparison period. */ + const val WEEK_DAYS = 7L + + /** + * Totals over some set of workouts. Sums are plain sums; [avgHeartRate] is + * duration-weighted so a two-hour ride outweighs a ten-minute walk. + */ + @Immutable + data class Totals( + val workoutCount: Int = 0, + val durationSeconds: Long = 0, + val distanceMeters: Double = 0.0, + val calories: Int = 0, + val steps: Int = 0, + val elevationGainMeters: Double = 0.0, + val avgHeartRate: Int? = null, + val maxHeartRate: Int? = null, + ) { + val isEmpty: Boolean get() = workoutCount == 0 + } + + /** One activity's slice of the window, e.g. "Running: 5 workouts, 42 km". */ + @Immutable + data class ActivityTotals( + val exercise: ExerciseType, + val totals: Totals, + ) + + /** A single best effort in the window, with the workout that set it. */ + @Immutable + data class Best( + val kind: BestKind, + val workout: DetectedWorkout, + ) + + enum class BestKind { LONGEST_DISTANCE, LONGEST_DURATION, BIGGEST_CLIMB, MOST_STEPS, HIGHEST_HEART_RATE } + + /** Everything the My Fitness screen renders. */ + @Immutable + data class Report( + val windowTotals: Totals, + val thisWeek: Totals, + val previousWeek: Totals, + val weeklyAverage: Totals, + val byActivity: List, + val bests: List, + val activeDays: Int, + val currentStreakDays: Int, + val workouts: List, + ) { + val isEmpty: Boolean get() = windowTotals.isEmpty + } + + /** + * Builds the report from [workouts] — which the caller should already have limited to + * [WINDOW_DAYS] — as of [now]. [zone] decides day boundaries for the streak and + * active-day counts, so a workout at 23:30 counts for that day, not the next. + */ + fun report( + workouts: List, + now: Instant = Instant.now(), + zone: ZoneId = ZoneId.systemDefault(), + ): Report { + val windowStart = now.epochSecond - WINDOW_DAYS * SECONDS_PER_DAY + val weekStart = now.epochSecond - WEEK_DAYS * SECONDS_PER_DAY + val previousWeekStart = weekStart - WEEK_DAYS * SECONDS_PER_DAY + + val inWindow = + workouts + .filter { it.startTimeEpochSeconds >= windowStart } + .sortedByDescending { it.startTimeEpochSeconds } + + val thisWeek = inWindow.filter { it.startTimeEpochSeconds >= weekStart } + val previousWeek = inWindow.filter { it.startTimeEpochSeconds in previousWeekStart until weekStart } + + val windowTotals = total(inWindow) + + return Report( + windowTotals = windowTotals, + thisWeek = total(thisWeek), + previousWeek = total(previousWeek), + weeklyAverage = perWeek(windowTotals), + byActivity = byActivity(inWindow), + bests = bests(inWindow), + activeDays = activeDays(inWindow, zone).size, + currentStreakDays = streak(inWindow, now, zone), + workouts = inWindow, + ) + } + + private const val SECONDS_PER_DAY = 86_400L + + /** Sums a set of workouts. Absent metrics contribute nothing rather than zero-ing the total. */ + fun total(workouts: List): Totals { + if (workouts.isEmpty()) return Totals() + + val withHeartRate = workouts.filter { it.avgHeartRate != null } + val heartRateWeight = withHeartRate.sumOf { it.durationSeconds } + + return Totals( + workoutCount = workouts.size, + durationSeconds = workouts.sumOf { it.durationSeconds }, + distanceMeters = workouts.sumOf { it.distanceMeters ?: 0.0 }, + calories = workouts.sumOf { it.calories ?: 0 }, + steps = workouts.sumOf { it.steps ?: 0 }, + elevationGainMeters = workouts.sumOf { it.elevationGainMeters ?: 0.0 }, + avgHeartRate = + when { + withHeartRate.isEmpty() -> null + heartRateWeight > 0 -> + withHeartRate + .sumOf { it.avgHeartRate!!.toDouble() * it.durationSeconds } + .div(heartRateWeight) + .roundToInt() + else -> withHeartRate.map { it.avgHeartRate!! }.average().roundToInt() + }, + maxHeartRate = workouts.mapNotNull { it.maxHeartRate }.maxOrNull(), + ) + } + + /** The window's totals expressed per week, for "your weekly average" lines. */ + private fun perWeek(totals: Totals): Totals { + val weeks = WINDOW_DAYS.toDouble() / WEEK_DAYS + return Totals( + workoutCount = (totals.workoutCount / weeks).roundToInt(), + durationSeconds = (totals.durationSeconds / weeks).toLong(), + distanceMeters = totals.distanceMeters / weeks, + calories = (totals.calories / weeks).roundToInt(), + steps = (totals.steps / weeks).roundToInt(), + elevationGainMeters = totals.elevationGainMeters / weeks, + // Averages don't divide: the mean heart rate of a week is the mean of the window. + avgHeartRate = totals.avgHeartRate, + maxHeartRate = totals.maxHeartRate, + ) + } + + /** Per-activity totals, busiest first (by time spent, then by count). */ + private fun byActivity(workouts: List): List = + workouts + .groupBy { it.exercise } + .map { (exercise, list) -> ActivityTotals(exercise, total(list)) } + .sortedWith( + compareByDescending { it.totals.durationSeconds } + .thenByDescending { it.totals.workoutCount }, + ) + + /** + * The standout efforts of the window. A best is only reported when the metric is present + * and positive, so a user whose watch records no elevation never sees an empty "biggest + * climb" card. + */ + private fun bests(workouts: List): List = + listOfNotNull( + workouts + .filter { (it.distanceMeters ?: 0.0) > 0 } + .maxByOrNull { it.distanceMeters!! } + ?.let { Best(BestKind.LONGEST_DISTANCE, it) }, + workouts + .filter { it.durationSeconds > 0 } + .maxByOrNull { it.durationSeconds } + ?.let { Best(BestKind.LONGEST_DURATION, it) }, + workouts + .filter { (it.elevationGainMeters ?: 0.0) > 0 } + .maxByOrNull { it.elevationGainMeters!! } + ?.let { Best(BestKind.BIGGEST_CLIMB, it) }, + workouts + .filter { (it.steps ?: 0) > 0 } + .maxByOrNull { it.steps!! } + ?.let { Best(BestKind.MOST_STEPS, it) }, + workouts + .filter { (it.maxHeartRate ?: 0) > 0 } + .maxByOrNull { it.maxHeartRate!! } + ?.let { Best(BestKind.HIGHEST_HEART_RATE, it) }, + ) + + /** The distinct local dates on which the user trained. */ + private fun activeDays( + workouts: List, + zone: ZoneId, + ): Set = workouts.mapTo(HashSet()) { it.localDate(zone) } + + /** + * Consecutive days up to today on which the user trained. A rest day today does not break + * a streak that is otherwise alive — the count then runs back from yesterday — so the + * number only resets once a whole day has genuinely been missed. + */ + private fun streak( + workouts: List, + now: Instant, + zone: ZoneId, + ): Int { + val days = activeDays(workouts, zone) + if (days.isEmpty()) return 0 + + val today = now.atZone(zone).toLocalDate() + var cursor = if (today in days) today else today.minusDays(1) + + var streak = 0 + while (cursor in days) { + streak++ + cursor = cursor.minusDays(1) + } + return streak + } + + private fun DetectedWorkout.localDate(zone: ZoneId): LocalDate = Instant.ofEpochSecond(startTimeEpochSeconds).atZone(zone).toLocalDate() + + /** + * Percentage change from [previous] to [current], or null when there is no previous value + * to compare against (an arrow up from zero says nothing). + */ + fun percentChange( + current: Double, + previous: Double, + ): Int? { + if (previous <= 0.0) return null + return (((current - previous) / previous) * 100).roundToInt() + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index b02470bd75..8f58c62abb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -316,6 +316,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.wizard.CashuWalletWi import com.vitorpamplona.amethyst.ui.screen.loggedIn.webBookmarks.WebBookmarksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.NewWorkoutScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.WorkoutsScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness.MyFitnessScreen import com.vitorpamplona.amethyst.ui.screen.loggedOff.AddAccountDialog import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.nip01Core.core.Address @@ -467,6 +468,7 @@ fun BuildNavigation( composableFromBottomArgs { AwardBadgeScreen(it.kind, it.pubKeyHex, it.dTag, accountViewModel, nav) } composableFromEndArgs { PicturesScreen(accountViewModel, nav, it.attachments, it.message) } composableFromEnd { WorkoutsScreen(accountViewModel, nav) } + composableFromEnd { MyFitnessScreen(accountViewModel, nav) } composableFromEnd { GitRepositoriesScreen(accountViewModel, nav) } composableFromEnd { HighlightsScreen(accountViewModel, nav) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index 02039f5dba..821385270f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -116,6 +116,9 @@ sealed class Route { @Serializable object Workouts : Route() + /** The signed-in user's own training summary, read from Health Connect. Not a feed. */ + @Serializable object MyFitness : Route() + @Serializable object GitRepositories : Route() @Serializable object Highlights : Route() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt index f74d25ca16..e834ee9e99 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.navigation.topbars import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton @@ -66,6 +67,7 @@ fun UserDrawerSearchTopBar( accountViewModel: AccountViewModel, nav: INav, baseFilter: SearchQuery?, + extraActions: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit, ) { ShorterTopAppBar( @@ -80,6 +82,9 @@ fun UserDrawerSearchTopBar( }, navigationIcon = { TopBarNavigationIcon(accountViewModel, nav) }, actions = { + // Screen-specific actions sit left of search, which stays the rightmost affordance + // everywhere it appears. + extraActions() IconButton(onClick = { nav.nav(searchRouteFor(baseFilter)) }) { SearchIcon(modifier = Size22Modifier, MaterialTheme.colorScheme.placeholderText) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt index d92d706cf1..3b172486c6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt @@ -20,22 +20,30 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.commons.icons.symbols.Icon +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.my_fitness_open import com.vitorpamplona.amethyst.commons.resources.select_list_to_filter import com.vitorpamplona.amethyst.commons.search.SearchSeed import com.vitorpamplona.amethyst.commons.search.asSearchQuery import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.topbars.FeedFilterSpinner import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar import com.vitorpamplona.amethyst.ui.screen.FeedDefinition import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size22Modifier +import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent @Composable @@ -51,7 +59,21 @@ fun WorkoutsTopBar( val me = accountViewModel.userProfile().pubkeyHex val seed = remember(list, me) { SearchSeed.merge(SearchSeed.ofKinds(WorkoutRecordEvent.KIND), list.asSearchQuery(me)) } - UserDrawerSearchTopBar(accountViewModel, nav, seed) { + UserDrawerSearchTopBar( + accountViewModel, + nav, + seed, + extraActions = { + IconButton(onClick = { nav.nav(Route.MyFitness) }) { + Icon( + symbol = MaterialSymbols.AutoMirrored.ShowChart, + contentDescription = stringRes(Res.string.my_fitness_open), + modifier = Size22Modifier, + tint = MaterialTheme.colorScheme.placeholderText, + ) + } + }, + ) { WorkoutsTopNavFilterBar( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessFormat.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessFormat.kt new file mode 100644 index 0000000000..576b2c7d38 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessFormat.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness + +import androidx.compose.runtime.Composable +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_ft +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_km +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_m +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_mi +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.phonePrefersMiles +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.DistanceTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.Elevation +import kotlin.math.roundToInt +import kotlin.math.roundToLong + +/** + * Display helpers for the My Fitness dashboard. The unit choice follows the phone's own + * measurement preference, the same way the workout composer does, so a user never sees + * kilometres on a screen where the rest of the app shows them miles. + */ +internal fun prefersMiles(): Boolean = phonePrefersMiles() + +/** `7h 12m` / `42m` / `45s` — a total, so hours run past 24 rather than wrapping. */ +internal fun formatDuration(totalSeconds: Long): String { + if (totalSeconds <= 0) return "0m" + + val hours = totalSeconds / 3600 + val minutes = (totalSeconds % 3600) / 60 + + return when { + hours > 0 -> "${hours}h ${minutes}m" + minutes > 0 -> "${minutes}m" + else -> "${totalSeconds}s" + } +} + +/** Distance in the user's unit, to one decimal — the bare number, paired with [distanceUnit]. */ +internal fun formatDistanceValue( + meters: Double, + miles: Boolean, +): String { + val value = if (miles) meters / DistanceTag.METERS_PER_MILE else meters / 1000.0 + return ((value * 10).roundToLong() / 10.0).toString() +} + +@Composable +internal fun distanceUnit(miles: Boolean): String = if (miles) stringRes(Res.string.my_fitness_unit_mi) else stringRes(Res.string.my_fitness_unit_km) + +/** Climb reads better as a whole number of metres or feet than as a decimal. */ +internal fun formatElevationValue( + meters: Double, + miles: Boolean, +): String { + val value = if (miles) meters / Elevation.METERS_PER_FOOT else meters + return value.roundToInt().toString() +} + +@Composable +internal fun elevationUnit(miles: Boolean): String = if (miles) stringRes(Res.string.my_fitness_unit_ft) else stringRes(Res.string.my_fitness_unit_m) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt new file mode 100644 index 0000000000..f31f9aa8a5 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness + +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.health.connect.client.PermissionController +import androidx.lifecycle.compose.LifecycleResumeEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.commons.icons.symbols.Icon +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.my_fitness_active_days +import com.vitorpamplona.amethyst.commons.resources.my_fitness_avg_heart_rate +import com.vitorpamplona.amethyst.commons.resources.my_fitness_best_biggest_climb +import com.vitorpamplona.amethyst.commons.resources.my_fitness_best_highest_heart_rate +import com.vitorpamplona.amethyst.commons.resources.my_fitness_best_longest_distance +import com.vitorpamplona.amethyst.commons.resources.my_fitness_best_longest_duration +import com.vitorpamplona.amethyst.commons.resources.my_fitness_best_most_steps +import com.vitorpamplona.amethyst.commons.resources.my_fitness_bests +import com.vitorpamplona.amethyst.commons.resources.my_fitness_by_activity +import com.vitorpamplona.amethyst.commons.resources.my_fitness_calories +import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_button +import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_message +import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_title +import com.vitorpamplona.amethyst.commons.resources.my_fitness_distance +import com.vitorpamplona.amethyst.commons.resources.my_fitness_elevation +import com.vitorpamplona.amethyst.commons.resources.my_fitness_empty +import com.vitorpamplona.amethyst.commons.resources.my_fitness_max_heart_rate +import com.vitorpamplona.amethyst.commons.resources.my_fitness_recent +import com.vitorpamplona.amethyst.commons.resources.my_fitness_share +import com.vitorpamplona.amethyst.commons.resources.my_fitness_steps +import com.vitorpamplona.amethyst.commons.resources.my_fitness_streak +import com.vitorpamplona.amethyst.commons.resources.my_fitness_this_week +import com.vitorpamplona.amethyst.commons.resources.my_fitness_time +import com.vitorpamplona.amethyst.commons.resources.my_fitness_title +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unavailable +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_bpm +import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_kcal +import com.vitorpamplona.amethyst.commons.resources.my_fitness_vs_last_week +import com.vitorpamplona.amethyst.commons.resources.my_fitness_weekly_average +import com.vitorpamplona.amethyst.commons.resources.my_fitness_window +import com.vitorpamplona.amethyst.commons.resources.my_fitness_window_note +import com.vitorpamplona.amethyst.commons.resources.my_fitness_workouts +import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_connect_details +import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout +import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager +import com.vitorpamplona.amethyst.service.workouts.health.WorkoutStats +import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.health.HealthConnectRationaleActivity +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.labelRes +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.suggestion.toNewWorkoutRoute +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.symbol +import com.vitorpamplona.amethyst.ui.stringRes + +/** + * The user's own training, summarised: how much they did this week against last, what they + * spent the time on, their best efforts, and how many days in a row they have shown up. + * + * This is the reason Amethyst reads Health Connect at all — the numbers are for the person + * who recorded them. Publishing one as a note is an optional action from the workout list, + * never a precondition for seeing any of this. + */ +@Composable +fun MyFitnessScreen( + accountViewModel: AccountViewModel, + nav: INav, +) { + val viewModel: MyFitnessViewModel = viewModel() + val context = LocalContext.current + val state by viewModel.state.collectAsStateWithLifecycle() + + val permissionLauncher = + rememberLauncherForActivityResult(PermissionController.createRequestPermissionResultContract()) { + viewModel.refresh(context) + } + + // Re-checks permissions as well as data, so revoking access in Health Connect drops the + // screen back to its prompt instead of leaving stale numbers up. + LifecycleResumeEffect(Unit) { + viewModel.refresh(context) + onPauseOrDispose {} + } + + Scaffold( + topBar = { TopBarWithBackButton(stringRes(Res.string.my_fitness_title), nav) }, + ) { padding -> + Surface(modifier = Modifier.padding(padding)) { + when (val current = state) { + MyFitnessViewModel.State.Loading -> CenteredBox { CircularProgressIndicator() } + + MyFitnessViewModel.State.Unavailable -> + CenteredBox { + Text( + text = stringRes(Res.string.my_fitness_unavailable), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 32.dp), + ) + } + + MyFitnessViewModel.State.NeedsPermission -> + ConnectPrompt( + onDetails = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) }, + onConnect = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) }, + ) + + is MyFitnessViewModel.State.Ready -> + if (current.report.isEmpty) { + CenteredBox { + Text( + text = stringRes(Res.string.my_fitness_empty), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 32.dp), + ) + } + } else { + Dashboard(current.report) { workout, label -> + nav.nav(workout.toNewWorkoutRoute(label)) + } + } + } + } + } +} + +@Composable +private fun CenteredBox(content: @Composable () -> Unit) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { content() } +} + +@Composable +private fun ConnectPrompt( + onDetails: () -> Unit, + onConnect: () -> Unit, +) { + Column( + modifier = Modifier.fillMaxSize().padding(24.dp), + verticalArrangement = Arrangement.spacedBy(14.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.primaryContainer, modifier = Modifier.size(56.dp)) { + Box(contentAlignment = Alignment.Center) { + Icon( + symbol = MaterialSymbols.DirectionsRun, + contentDescription = null, + modifier = Modifier.size(30.dp), + tint = MaterialTheme.colorScheme.onPrimaryContainer, + ) + } + } + Text(stringRes(Res.string.my_fitness_connect_title), style = MaterialTheme.typography.titleMedium) + Text( + text = stringRes(Res.string.my_fitness_connect_message), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + TextButton(onClick = onDetails) { Text(stringRes(Res.string.workout_suggestion_connect_details)) } + Button(onClick = onConnect) { Text(stringRes(Res.string.my_fitness_connect_button)) } + } + } +} + +@Composable +private fun Dashboard( + report: WorkoutStats.Report, + onShare: (DetectedWorkout, String) -> Unit, +) { + val miles = remember { prefersMiles() } + + Column( + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp), + verticalArrangement = Arrangement.spacedBy(18.dp), + ) { + ThisWeekCard(report, miles) + ConsistencyRow(report) + WindowTotalsCard(report, miles) + ActivityBreakdown(report, miles) + BestEfforts(report, miles) + RecentWorkouts(report, miles, onShare) + + Text( + text = stringRes(Res.string.my_fitness_window_note), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } +} + +@Composable +private fun ThisWeekCard( + report: WorkoutStats.Report, + miles: Boolean, +) { + SectionCard(stringRes(Res.string.my_fitness_this_week)) { + StatGrid( + listOf( + StatCell( + stringRes(Res.string.my_fitness_workouts), + report.thisWeek.workoutCount.toString(), + null, + WorkoutStats.percentChange(report.thisWeek.workoutCount.toDouble(), report.previousWeek.workoutCount.toDouble()), + ), + StatCell( + stringRes(Res.string.my_fitness_time), + formatDuration(report.thisWeek.durationSeconds), + null, + WorkoutStats.percentChange(report.thisWeek.durationSeconds.toDouble(), report.previousWeek.durationSeconds.toDouble()), + ), + StatCell( + stringRes(Res.string.my_fitness_distance), + formatDistanceValue(report.thisWeek.distanceMeters, miles), + distanceUnit(miles), + WorkoutStats.percentChange(report.thisWeek.distanceMeters, report.previousWeek.distanceMeters), + ), + StatCell( + stringRes(Res.string.my_fitness_calories), + report.thisWeek.calories.toString(), + stringRes(Res.string.my_fitness_unit_kcal), + WorkoutStats.percentChange(report.thisWeek.calories.toDouble(), report.previousWeek.calories.toDouble()), + ), + ), + ) + } +} + +@Composable +private fun ConsistencyRow(report: WorkoutStats.Report) { + Row(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) { + HighlightTile(report.currentStreakDays.toString(), stringRes(Res.string.my_fitness_streak), Modifier.weight(1f)) + HighlightTile(report.activeDays.toString(), stringRes(Res.string.my_fitness_active_days), Modifier.weight(1f)) + HighlightTile(report.windowTotals.workoutCount.toString(), stringRes(Res.string.my_fitness_workouts), Modifier.weight(1f)) + } +} + +@Composable +private fun WindowTotalsCard( + report: WorkoutStats.Report, + miles: Boolean, +) { + SectionCard("${stringRes(Res.string.my_fitness_window)} · ${stringRes(Res.string.my_fitness_weekly_average)}") { + val cells = + buildList { + add(StatCell(stringRes(Res.string.my_fitness_time), formatDuration(report.weeklyAverage.durationSeconds), null, null)) + add( + StatCell( + stringRes(Res.string.my_fitness_distance), + formatDistanceValue(report.weeklyAverage.distanceMeters, miles), + distanceUnit(miles), + null, + ), + ) + if (report.windowTotals.steps > 0) { + add(StatCell(stringRes(Res.string.my_fitness_steps), report.weeklyAverage.steps.toString(), null, null)) + } + if (report.windowTotals.elevationGainMeters > 0) { + add( + StatCell( + stringRes(Res.string.my_fitness_elevation), + formatElevationValue(report.weeklyAverage.elevationGainMeters, miles), + elevationUnit(miles), + null, + ), + ) + } + report.windowTotals.avgHeartRate?.let { + add(StatCell(stringRes(Res.string.my_fitness_avg_heart_rate), it.toString(), stringRes(Res.string.my_fitness_unit_bpm), null)) + } + report.windowTotals.maxHeartRate?.let { + add(StatCell(stringRes(Res.string.my_fitness_max_heart_rate), it.toString(), stringRes(Res.string.my_fitness_unit_bpm), null)) + } + } + StatGrid(cells) + } +} + +@Composable +private fun ActivityBreakdown( + report: WorkoutStats.Report, + miles: Boolean, +) { + if (report.byActivity.isEmpty()) return + + SectionCard(stringRes(Res.string.my_fitness_by_activity)) { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + report.byActivity.forEach { activity -> + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Icon( + symbol = activity.exercise.symbol(), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringRes(activity.exercise.labelRes()), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.weight(1f), + ) + Text( + text = activitySummary(activity, miles), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + } +} + +@Composable +private fun activitySummary( + activity: WorkoutStats.ActivityTotals, + miles: Boolean, +): String { + val parts = mutableListOf() + parts.add("${activity.totals.workoutCount}×") + parts.add(formatDuration(activity.totals.durationSeconds)) + if (activity.totals.distanceMeters > 0) { + parts.add("${formatDistanceValue(activity.totals.distanceMeters, miles)} ${distanceUnit(miles)}") + } + return parts.joinToString(" · ") +} + +@Composable +private fun BestEfforts( + report: WorkoutStats.Report, + miles: Boolean, +) { + if (report.bests.isEmpty()) return + + SectionCard(stringRes(Res.string.my_fitness_bests)) { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + report.bests.forEach { best -> + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = bestLabel(best.kind), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.weight(1f), + ) + Text( + text = bestValue(best, miles), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + ) + } + } + } + } +} + +@Composable +private fun bestLabel(kind: WorkoutStats.BestKind): String = + when (kind) { + WorkoutStats.BestKind.LONGEST_DISTANCE -> stringRes(Res.string.my_fitness_best_longest_distance) + WorkoutStats.BestKind.LONGEST_DURATION -> stringRes(Res.string.my_fitness_best_longest_duration) + WorkoutStats.BestKind.BIGGEST_CLIMB -> stringRes(Res.string.my_fitness_best_biggest_climb) + WorkoutStats.BestKind.MOST_STEPS -> stringRes(Res.string.my_fitness_best_most_steps) + WorkoutStats.BestKind.HIGHEST_HEART_RATE -> stringRes(Res.string.my_fitness_best_highest_heart_rate) + } + +@Composable +private fun bestValue( + best: WorkoutStats.Best, + miles: Boolean, +): String = + when (best.kind) { + WorkoutStats.BestKind.LONGEST_DISTANCE -> + "${formatDistanceValue(best.workout.distanceMeters ?: 0.0, miles)} ${distanceUnit(miles)}" + WorkoutStats.BestKind.LONGEST_DURATION -> formatDuration(best.workout.durationSeconds) + WorkoutStats.BestKind.BIGGEST_CLIMB -> + "${formatElevationValue(best.workout.elevationGainMeters ?: 0.0, miles)} ${elevationUnit(miles)}" + WorkoutStats.BestKind.MOST_STEPS -> (best.workout.steps ?: 0).toString() + WorkoutStats.BestKind.HIGHEST_HEART_RATE -> + "${best.workout.maxHeartRate ?: 0} ${stringRes(Res.string.my_fitness_unit_bpm)}" + } + +@Composable +private fun RecentWorkouts( + report: WorkoutStats.Report, + miles: Boolean, + onShare: (DetectedWorkout, String) -> Unit, +) { + SectionCard(stringRes(Res.string.my_fitness_recent)) { + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + report.workouts.take(RECENT_LIMIT).forEach { workout -> + // Resolved here rather than in the click lambda: displayLabel reads a string + // resource, which only composition can do. + val label = workout.displayLabel() + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Icon( + symbol = workout.exercise.symbol(), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + TextButton(onClick = { onShare(workout, label) }) { + Text(stringRes(Res.string.my_fitness_share), style = MaterialTheme.typography.labelMedium) + } + } + Text( + text = workoutSummary(workout, miles), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + } +} + +private const val RECENT_LIMIT = 15 + +@Composable +private fun DetectedWorkout.displayLabel(): String = title?.takeIf { it.isNotBlank() } ?: stringRes(exercise.labelRes()) + +@Composable +private fun workoutSummary( + workout: DetectedWorkout, + miles: Boolean, +): String { + val parts = mutableListOf() + parts.add(formatDuration(workout.durationSeconds)) + workout.distanceMeters?.takeIf { it > 0 }?.let { parts.add("${formatDistanceValue(it, miles)} ${distanceUnit(miles)}") } + workout.calories?.takeIf { it > 0 }?.let { parts.add("$it ${stringRes(Res.string.my_fitness_unit_kcal)}") } + workout.avgHeartRate?.takeIf { it > 0 }?.let { parts.add("$it ${stringRes(Res.string.my_fitness_unit_bpm)}") } + workout.elevationGainMeters?.takeIf { it > 0 }?.let { parts.add("${formatElevationValue(it, miles)} ${elevationUnit(miles)}") } + return parts.joinToString(" · ") +} + +@Composable +private fun SectionCard( + title: String, + content: @Composable () -> Unit, +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary, + ) + OutlinedCard(shape = RoundedCornerShape(14.dp), modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier.padding(14.dp)) { content() } + } + } +} + +@Composable +private fun HighlightTile( + value: String, + label: String, + modifier: Modifier = Modifier, +) { + OutlinedCard(shape = RoundedCornerShape(14.dp), modifier = modifier) { + Column( + modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(text = value, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + +private data class StatCell( + val label: String, + val value: String, + val unit: String?, + val percentChange: Int?, +) + +/** Two-column grid of stats — a plain Column of Rows so it nests inside the scrolling page. */ +@Composable +private fun StatGrid(cells: List) { + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + cells.chunked(2).forEach { row -> + Row(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) { + row.forEach { cell -> + StatCellView(cell, Modifier.weight(1f)) + } + // Keeps a lone trailing cell at half width instead of stretching it across. + if (row.size == 1) Box(Modifier.weight(1f)) + } + } + } +} + +@Composable +private fun StatCellView( + cell: StatCell, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Row(verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(3.dp)) { + Text(text = cell.value, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + cell.unit?.let { + Text( + text = it, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 2.dp), + ) + } + } + Text( + text = cell.label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + cell.percentChange?.let { change -> + Text( + text = "${if (change >= 0) "+" else ""}$change% ${stringRes(Res.string.my_fitness_vs_last_week)}", + style = MaterialTheme.typography.labelSmall, + color = + if (change >= 0) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt new file mode 100644 index 0000000000..6246ded88b --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness + +import android.content.Context +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager +import com.vitorpamplona.amethyst.service.workouts.health.WorkoutStats +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import java.time.Duration +import java.time.Instant + +/** + * State holder for the My Fitness dashboard: the user's own training over the last + * [WorkoutStats.WINDOW_DAYS], read from Health Connect and summarised by [WorkoutStats]. + * + * Nothing here publishes or touches the network. The dashboard is the user looking at their + * own numbers; sharing one of them is a separate, deliberate action from the workout list. + */ +@Stable +class MyFitnessViewModel : ViewModel() { + @Immutable + sealed interface State { + /** First load, or a reload after a permission change. */ + data object Loading : State + + /** No Health Connect provider on this device — nothing to offer. */ + data object Unavailable : State + + /** Provider present, permissions not granted yet. */ + data object NeedsPermission : State + + /** Granted and summarised. [WorkoutStats.Report.isEmpty] covers "nothing recorded yet". */ + data class Ready( + val report: WorkoutStats.Report, + ) : State + } + + private val _state = MutableStateFlow(State.Loading) + val state: StateFlow = _state.asStateFlow() + + private var manager: HealthConnectManager? = null + + /** + * Refreshes the dashboard. Safe to call on every resume: it re-checks permissions first, + * so revoking access in Health Connect drops the screen back to its prompt rather than + * leaving stale numbers on display. + */ + fun refresh(context: Context) { + viewModelScope.launch { + if (!HealthConnectManager.isAvailable(context)) { + _state.value = State.Unavailable + return@launch + } + + val hc = manager ?: HealthConnectManager(context.applicationContext).also { manager = it } + + if (!hc.hasAllPermissions()) { + _state.value = State.NeedsPermission + return@launch + } + + val now = Instant.now() + val workouts = hc.readWorkouts(now.minus(Duration.ofDays(WorkoutStats.WINDOW_DAYS)), now) + _state.value = State.Ready(WorkoutStats.report(workouts, now)) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt index 6a0a88fd85..58528ea2d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt @@ -114,7 +114,7 @@ fun DetectedWorkoutCarousel( workouts = if (ok) { val since = Instant.now().minus(Duration.ofDays(HealthConnectManager.LOOKBACK_DAYS)) - manager.readNewWorkouts(since).sortedByDescending { it.startTimeEpochSeconds } + manager.readWorkouts(since).sortedByDescending { it.startTimeEpochSeconds } } else { emptyList() } diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt new file mode 100644 index 0000000000..b8f728ebf3 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service.workouts.health + +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime + +class WorkoutStatsTest { + private val zone: ZoneId = ZoneId.of("UTC") + + /** Noon UTC so a test never straddles a day boundary by accident. */ + private val now: Instant = ZonedDateTime.of(2026, 3, 15, 12, 0, 0, 0, zone).toInstant() + + private var nextId = 0 + + private fun workout( + daysAgo: Long, + exercise: ExerciseType = ExerciseType.RUNNING, + durationSeconds: Long = 1800, + distanceMeters: Double? = null, + calories: Int? = null, + avgHeartRate: Int? = null, + maxHeartRate: Int? = null, + steps: Int? = null, + elevationGainMeters: Double? = null, + ) = DetectedWorkout( + id = "w${nextId++}", + exercise = exercise, + title = null, + startTimeEpochSeconds = now.epochSecond - daysAgo * 86_400L, + durationSeconds = durationSeconds, + distanceMeters = distanceMeters, + calories = calories, + avgHeartRate = avgHeartRate, + maxHeartRate = maxHeartRate, + steps = steps, + elevationGainMeters = elevationGainMeters, + source = "Samsung Health", + ) + + @Test + fun `an empty history reports empty rather than zeroes everywhere`() { + val report = WorkoutStats.report(emptyList(), now, zone) + + assertTrue(report.isEmpty) + assertEquals(0, report.activeDays) + assertEquals(0, report.currentStreakDays) + assertTrue(report.bests.isEmpty()) + } + + @Test + fun `workouts older than the window are excluded`() { + val report = + WorkoutStats.report( + listOf(workout(daysAgo = 3), workout(daysAgo = 40, distanceMeters = 99_000.0)), + now, + zone, + ) + + assertEquals(1, report.windowTotals.workoutCount) + assertEquals(0.0, report.windowTotals.distanceMeters, 0.001) + } + + @Test + fun `this week and last week are split at the seven day boundary`() { + val report = + WorkoutStats.report( + listOf( + workout(daysAgo = 1, distanceMeters = 5000.0), + workout(daysAgo = 6, distanceMeters = 3000.0), + workout(daysAgo = 8, distanceMeters = 10_000.0), + workout(daysAgo = 20, distanceMeters = 1000.0), + ), + now, + zone, + ) + + assertEquals(2, report.thisWeek.workoutCount) + assertEquals(8000.0, report.thisWeek.distanceMeters, 0.001) + assertEquals(1, report.previousWeek.workoutCount) + assertEquals(10_000.0, report.previousWeek.distanceMeters, 0.001) + assertEquals(4, report.windowTotals.workoutCount) + } + + /** A missing metric must not be counted as a zero that drags an average down. */ + @Test + fun `absent metrics contribute nothing to totals`() { + val report = + WorkoutStats.report( + listOf( + workout(daysAgo = 1, distanceMeters = 5000.0, calories = 300, steps = 6000), + workout(daysAgo = 2), + ), + now, + zone, + ) + + assertEquals(5000.0, report.windowTotals.distanceMeters, 0.001) + assertEquals(300, report.windowTotals.calories) + assertEquals(6000, report.windowTotals.steps) + assertNull(report.windowTotals.avgHeartRate) + } + + @Test + fun `average heart rate is duration weighted across the window`() { + val report = + WorkoutStats.report( + listOf( + workout(daysAgo = 1, durationSeconds = 3600, avgHeartRate = 160), + workout(daysAgo = 2, durationSeconds = 600, avgHeartRate = 100), + ), + now, + zone, + ) + + // (160*3600 + 100*600) / 4200 = 151.4 -> 151 + assertEquals(151, report.windowTotals.avgHeartRate) + } + + @Test + fun `the activity breakdown is ordered by time spent`() { + val report = + WorkoutStats.report( + listOf( + workout(daysAgo = 1, exercise = ExerciseType.RUNNING, durationSeconds = 600), + workout(daysAgo = 2, exercise = ExerciseType.CYCLING, durationSeconds = 7200), + workout(daysAgo = 3, exercise = ExerciseType.RUNNING, durationSeconds = 600), + ), + now, + zone, + ) + + assertEquals(ExerciseType.CYCLING, report.byActivity[0].exercise) + assertEquals(1, report.byActivity[0].totals.workoutCount) + assertEquals(ExerciseType.RUNNING, report.byActivity[1].exercise) + assertEquals(2, report.byActivity[1].totals.workoutCount) + } + + @Test + fun `a best is only reported for metrics that are actually present`() { + val report = + WorkoutStats.report( + listOf(workout(daysAgo = 1, distanceMeters = 5000.0)), + now, + zone, + ) + + val kinds = report.bests.map { it.kind } + assertTrue(WorkoutStats.BestKind.LONGEST_DISTANCE in kinds) + assertTrue(WorkoutStats.BestKind.LONGEST_DURATION in kinds) + assertTrue(WorkoutStats.BestKind.BIGGEST_CLIMB !in kinds) + assertTrue(WorkoutStats.BestKind.MOST_STEPS !in kinds) + assertTrue(WorkoutStats.BestKind.HIGHEST_HEART_RATE !in kinds) + } + + @Test + fun `two workouts on the same day count as one active day`() { + val report = + WorkoutStats.report( + listOf(workout(daysAgo = 1), workout(daysAgo = 1), workout(daysAgo = 3)), + now, + zone, + ) + + assertEquals(2, report.activeDays) + } + + @Test + fun `the streak counts consecutive days back from today`() { + val report = + WorkoutStats.report( + listOf(workout(daysAgo = 0), workout(daysAgo = 1), workout(daysAgo = 2), workout(daysAgo = 5)), + now, + zone, + ) + + assertEquals(3, report.currentStreakDays) + } + + /** Resting today shouldn't wipe out a streak that is still alive. */ + @Test + fun `a rest day today keeps yesterday's streak alive`() { + val report = + WorkoutStats.report( + listOf(workout(daysAgo = 1), workout(daysAgo = 2)), + now, + zone, + ) + + assertEquals(2, report.currentStreakDays) + } + + @Test + fun `missing a whole day resets the streak`() { + val report = + WorkoutStats.report( + listOf(workout(daysAgo = 2), workout(daysAgo = 3)), + now, + zone, + ) + + assertEquals(0, report.currentStreakDays) + } + + @Test + fun `the weekly average divides the four week window by four`() { + val report = + WorkoutStats.report( + List(8) { workout(daysAgo = (it * 3).toLong(), distanceMeters = 5000.0) }, + now, + zone, + ) + + assertEquals(8, report.windowTotals.workoutCount) + assertEquals(2, report.weeklyAverage.workoutCount) + assertEquals(10_000.0, report.weeklyAverage.distanceMeters, 0.001) + } + + @Test + fun `percent change is null when there is nothing to compare against`() { + assertNull(WorkoutStats.percentChange(current = 10.0, previous = 0.0)) + assertEquals(50, WorkoutStats.percentChange(current = 15.0, previous = 10.0)) + assertEquals(-25, WorkoutStats.percentChange(current = 7.5, previous = 10.0)) + } +} diff --git a/commons/src/commonMain/composeResources/values/strings.xml b/commons/src/commonMain/composeResources/values/strings.xml index 6cee50140c..019c73f205 100644 --- a/commons/src/commonMain/composeResources/values/strings.xml +++ b/commons/src/commonMain/composeResources/values/strings.xml @@ -683,6 +683,43 @@ Nothing leaves your phone until you tap a suggestion and publish the post yourself. Amethyst has no server: the post goes to the Nostr relays you configured. The whole feature is optional. Turn it off under Settings → Compose, or revoke the permissions in Health Connect at any time — the rest of Amethyst keeps working. Read the full privacy policy + My Fitness + My Fitness + Last 4 weeks + Health Connect serves the last 30 days, so Amethyst summarises four whole weeks. Nothing here leaves your phone. + This week + vs last week + Weekly average + Workouts + Active days + Day streak + Time + Distance + Calories + Steps + Climb + Avg HR + Max HR + By activity + Best efforts + Longest distance + Longest workout + Biggest climb + Most steps + Highest heart rate + Recent workouts + No workouts recorded in the last 4 weeks. Once your watch or fitness app saves one, it shows up here. + Health Connect is not available on this device, so Amethyst has no workouts to summarise. + Track your training + Connect Health Connect to see your weekly totals, week-over-week trend, best efforts and training streak. Everything stays on your phone. + Connect + Share this workout + bpm + kcal + km + mi + m + ft No nSites found yet. Reload The in-app browser needs Android 11 or newer. diff --git a/docs/health-connect-play-declaration.md b/docs/health-connect-play-declaration.md index 5bc2ed8b12..09fc252b6b 100644 --- a/docs/health-connect-play-declaration.md +++ b/docs/health-connect-play-declaration.md @@ -1,168 +1,190 @@ # Health Connect — Play Console declaration -Source text for the Health Connect permissions declaration in Play Console, written against what -`HealthConnectManager` actually does. Keep this file and the declaration in sync: Google re-reviews -the declaration on every Health Connect permission change. +Source text for the Health Connect permissions declaration in Play Console. Keep this file and +the declaration in sync: Google re-reviews the declaration on every Health Connect permission +change. -Related code and copy: +**Approved use case claimed: "Fitness, wellness and coaching"** — *"apps designed to help users +track, monitor, analyze, manage, and improve their physical fitness."* -- `amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt` — the only place the app touches Health Connect. -- `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt` — the only UI that calls it. -- `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/health/HealthConnectRationaleActivity.kt` — the in-app rationale screen Health Connect links to. -- `PRIVACY.md` § "Health and fitness data (Health Connect)" — the public policy the declaration points at. +## Why the earlier declaration was rejected, and what changed + +The first submission described the feature as "read finished workouts so the user can post them +to their social network." That is not one of the six approved use cases, and it reads as the +prohibited "publicly displaying or socially sharing sensitive data." It was rejected as *"Use of +permission is not a permitted/valid use case."* + +The app changed, not just the wording. Amethyst now has **My Fitness**, a personal training +dashboard that summarises the user's own Health Connect workouts for them: weekly totals, +week-over-week movement, a per-activity breakdown, best efforts, active days and a training +streak. It is reached from the Workouts screen and works fully whether or not the user ever +publishes anything. Publishing a workout is now one optional action on a row of that dashboard. + +**The permissions serve the dashboard.** If the declaration below is ever re-read against the +app, the test to apply is: every data type listed is rendered back to the user as their own +statistic, on a screen that has no posting requirement. + +Related code: + +- `service/workouts/health/WorkoutStats.kt` — all dashboard arithmetic; pure, unit-tested. +- `ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt` — the dashboard. +- `service/workouts/health/HealthConnectManager.kt` — the only place the app touches Health Connect. +- `ui/screen/loggedIn/workouts/health/HealthConnectRationaleActivity.kt` — the in-app rationale screen. +- `PRIVACY.md` § "Health and fitness data (Health Connect)". --- ## 1. App functionality -> Amethyst is a social media client for the Nostr protocol — an open, decentralized social network. -> Users post updates and their followers read them. There is no Amethyst server and no Amethyst -> account: posts are signed on the device and sent to the public relay servers the user chooses. +> Amethyst is a social client for Nostr, an open decentralized social protocol. It also includes +> a fitness feature, **My Fitness**, which is what uses Health Connect. > -> One of the things users post is a workout summary. Amethyst's Workouts section publishes a -> structured workout post (a NIP-101e "kind 1301" event) — the activity, when it happened, how long -> it lasted, and the metrics that describe the effort — so the people who follow the user can see -> what they did, congratulate them, and compare with their own. This is the fitness equivalent of -> sharing a run on Strava or a ride on Garmin Connect, except the post goes to the user's own -> chosen relays instead of a company's servers. +> My Fitness is a personal training dashboard. It reads the workouts the user's watch or fitness +> app has already saved to Health Connect and turns them into a picture of how that person is +> training: how much they did this week and whether that is up or down on last week, how their +> time splits across running, cycling, walking, swimming and the gym, their best efforts, how +> many days they trained, and their current streak of consecutive active days. > -> Health Connect is used for exactly one thing: to fill that post in. Without it the user has to -> retype numbers their watch already recorded — activity, duration, distance, calories, heart rate, -> steps, climb — which is slow and error-prone enough that most people simply do not post. With it, -> the composer shows the workouts that finished in the last 7 days as one-tap cards; tapping one -> pre-fills the form, and the user then edits and decides whether to publish. +> The purpose is to help the user track, monitor, analyze and improve their own physical +> fitness. The numbers are shown to the person who recorded them. No part of the dashboard +> requires posting anything, and nothing is transmitted anywhere to produce it — the summary is +> computed on the device from Health Connect data and displayed. +> +> Separately, and entirely optionally, a user who wants to tell their followers about a +> particular workout can tap "Share this workout" on a row of that dashboard. That opens a +> composer pre-filled with the workout's figures, which the user reviews and chooses to publish +> or discard. Sharing is a user-initiated action on top of the tracking feature, with per-post +> review and consent; it is never automatic, and the tracking feature is fully usable without it. ## 2. Reviewer walkthrough -> 1. Install and open Amethyst, and sign in (a new key can be generated in-app). -> 2. Open the navigation drawer (hamburger, top-left) and, under the **Feeds** section, tap -> **Workouts**. -> 3. Tap the **+** button. -> 4. At the top of the composer is a card titled **"Share your workouts"**. Tap **What Amethyst -> reads** to see the in-app rationale screen listing each data type and its purpose, then -> **Connect** to trigger the Health Connect permission request. -> 5. Grant the permissions. The card is replaced by a horizontal list of the workouts Health Connect -> holds from the last 7 days, labelled **"From Health Connect"**. -> 6. Tap any workout. The composer below is pre-filled with its activity type, title, duration, -> distance, calories, heart rate, steps and elevation gain, ready to edit and publish. +> 1. Install and open Amethyst, and sign in (a key can be generated in-app). +> 2. Open the navigation drawer (hamburger, top-left), and under **Feeds** tap **Workouts**. +> 3. In the top bar, tap the **chart icon** to open **My Fitness**. +> 4. The screen explains what will be read and offers **What Amethyst reads** (the full rationale +> screen) and **Connect**. Tap Connect and grant the permissions. +> 5. The dashboard appears: "This week" totals with the change against last week; the streak, +> active-days and workout-count tiles; the four-week weekly average; the per-activity +> breakdown; best efforts; and the recent-workout list. +> 6. Everything above is the tracking feature. To see the optional sharing path, tap **Share this +> workout** on any row — it opens a pre-filled composer that the user must confirm. > -> Note for testing on an emulator or a fresh device: the carousel only appears once Health Connect -> actually holds a finished exercise session from the last 7 days, written by some fitness app or -> watch. With an empty Health Connect database the composer correctly shows nothing. +> Note for a fresh test device: the dashboard needs Health Connect to actually hold finished +> exercise sessions from the last four weeks, written by some fitness app or watch. With an empty +> Health Connect database it correctly reports that nothing has been recorded. ## 3. Per-permission justification -Paste one row per permission into the corresponding field. +Each permission maps to a statistic the dashboard shows the user about their own training. ### READ_EXERCISE — ExerciseSession (and CyclingPedalingCadence) -> This is the workout itself and the anchor for everything else. Amethyst reads the exercise -> session's activity type, start time and end time, and turns them into the post's activity, date -> and duration: "Running, 42:15, yesterday". The session's time window is also what every other -> metric below is aggregated over, so without this permission the feature cannot exist at all — the -> app would have no notion of "a workout" to attach numbers to. The session's own title, when the -> source app set one, becomes the suggested post title. +> The workout itself, and the spine of the whole dashboard. Amethyst reads each session's +> activity type, start time and end time to produce: the count of workouts this week versus last +> week, total training time, the per-activity breakdown ("Cycling: 3×, 4h 10m"), the number of +> active days, and the consecutive-day training streak. Every other metric below is aggregated +> over the session's time window, so without this permission there is no notion of "a workout" to +> attach any statistic to and the feature cannot exist. > -> Benefit to the user: the workout their watch recorded appears as a one-tap suggestion instead of a -> blank form. +> Benefit to the user: they can see how consistently and how much they are actually training, +> which is the basic question a training summary answers. > -> Note on CyclingPedalingCadence: Health Connect grants that data type under the same -> READ_EXERCISE permission. Amethyst does not read, store, or publish cadence — it reads only -> ExerciseSessionRecord. There is no separate permission available to request one without the other. +> CyclingPedalingCadence is granted by Health Connect under this same permission. Amethyst does +> not read, store, or display cadence — it reads only ExerciseSessionRecord. There is no separate +> permission that grants one without the other. ### READ_DISTANCE — Distance -> Amethyst aggregates the distance recorded over the workout's time window and fills it into the -> distance field of the post. Distance is the single most important number in a running, cycling, -> walking, hiking, rowing or swimming post — "5.2 km" is what the post is about, and a shared -> workout without it is largely meaningless to the people reading it. +> Aggregated per workout and summed into the dashboard's distance figures: distance this week, +> the percentage change against last week, the four-week weekly average, distance per activity, +> and the "Longest distance" best effort. > -> Benefit to the user: they do not have to look up and retype the distance their watch already -> measured, and the figure published is the accurate recorded one rather than a remembered estimate. +> Benefit to the user: distance is the primary training-load measure for running, cycling, +> walking, hiking, rowing and swimming. Seeing this week's total against last week's is how a +> user knows whether they are building up or falling off. ### READ_ACTIVE_CALORIES_BURNED — ActiveCaloriesBurned -> Amethyst aggregates active calories over the workout's time window and fills in the post's energy -> field. Active calories (energy burned by the activity, excluding resting metabolism) are the -> correct figure for describing a workout, and the one other fitness apps and Nostr fitness clients -> publish, so using it keeps Amethyst's posts comparable with theirs. +> Aggregated per workout and summed into the dashboard's energy figures: calories this week and +> the change against last week. Active calories — energy burned by the activity, excluding +> resting metabolism — are the correct measure of a workout's cost. > -> Benefit to the user: the effort figure in their post is the one their device computed, and it is -> filled in automatically. +> Benefit to the user: a view of training energy expenditure over time, and whether it is rising +> or falling week to week. ### READ_TOTAL_CALORIES_BURNED — TotalCaloriesBurned -> Fallback for the field above. Not every source writes ActiveCaloriesBurned — several popular -> watches and fitness apps record only total energy for a session. When active calories are absent, -> Amethyst uses total calories for the same field so the energy figure is not simply blank for those -> users. When active calories are present they are always preferred, because total calories include -> basal burn and would over-report the workout. +> Fallback for the same figure. Several widely used watches and fitness apps record only total +> energy for a session and never write ActiveCaloriesBurned. Where active calories are missing, +> Amethyst uses total calories so the energy statistics are not simply blank for those users; +> where active calories exist they are always preferred, because total calories include basal +> burn and would overstate the workout. > -> Benefit to the user: the feature works consistently regardless of which watch or fitness app they -> use, instead of silently dropping a metric for a subset of devices. +> Benefit to the user: the dashboard reports energy consistently regardless of which watch or app +> they use, instead of silently omitting the metric for a subset of devices. ### READ_HEART_RATE — HeartRate -> Amethyst aggregates the average and maximum heart rate over the workout's time window and fills in -> the post's two heart-rate fields. Heart rate is the standard measure of how hard an effort was and -> is what makes two workouts of the same distance comparable — an easy recovery run and a hard -> tempo run look identical without it. It is a headline field of the NIP-101e workout post format -> Amethyst publishes. +> Aggregated per workout into the average and maximum heart rate, then combined across the window +> into a duration-weighted average heart rate (so a two-hour ride weighs more than a ten-minute +> walk), a maximum for the period, the per-workout effort shown on each row of the recent list, +> and the "Highest heart rate" best effort. > -> Benefit to the user: their followers can see how hard the session actually was, not just how far -> it went, without the user transcribing two more numbers by hand. +> Benefit to the user: heart rate is how a user distinguishes an easy week from a hard one at the +> same distance, and the standard signal for whether they are training too hard or too easily. It +> is the single most informative intensity measure the dashboard can show. ### READ_STEPS — Steps (and StepsCadence) -> Amethyst aggregates the step count over the workout's time window and fills in the post's steps -> field. For walking, running and hiking posts the step count is a primary metric — for a walk it is -> often the metric the user cares about most — and it is one of the fields of the workout post -> format. +> Aggregated per workout and summed into the weekly step average and the "Most steps" best +> effort. > -> Benefit to the user: walk, run and hike posts carry the step count automatically. +> Benefit to the user: for walking and hiking — and for many users the majority of their activity +> — step count is the metric they actually track, and a weekly average is how they judge whether +> they are keeping it up. > -> Note on StepsCadence: Health Connect grants that data type under the same READ_STEPS permission. -> Amethyst does not read, store, or publish cadence — it reads only the aggregated step count. -> There is no separate permission available to request one without the other. +> StepsCadence is granted by Health Connect under this same permission. Amethyst does not read, +> store, or display cadence — it reads only the aggregated step count. There is no separate +> permission that grants one without the other. ### READ_ELEVATION_GAINED — ElevationGained -> Amethyst aggregates elevation gained over the workout's time window and fills in the post's climb -> field. Elevation is what distinguishes a flat ride or run from a hilly one — 30 km with 800 m of -> climbing is a completely different effort from 30 km on the flat — and it is the defining metric -> of a hiking post. It is one of the fields of the workout post format. +> Aggregated per workout and summed into the weekly climb average and the "Biggest climb" best +> effort, and shown per workout in the recent list. > -> Benefit to the user: hill and trail workouts are described accurately in the post rather than -> looking like flat ones. +> Benefit to the user: elevation is what separates a flat week from a hard hilly one at identical +> distance and time. Without it, a user training on hills sees no difference between a punishing +> week and an easy one, and the dashboard misrepresents their training load. -## 4. Scope and data handling (state this alongside the table above) +## 4. Scope and data handling -> - **Read-only.** Amethyst holds no write permissions and never writes to Health Connect. -> - **Foreground only.** Reads happen only while the New Workout composer is on screen, in direct -> response to the user opening it. Amethyst does not request READ_HEALTH_DATA_IN_BACKGROUND and -> has no background worker, service or job that touches health data. -> - **Last 7 days only.** Only sessions finishing in the previous 7 days are read. Amethyst does not -> request READ_HEALTH_DATA_HISTORY. -> - **No location.** Amethyst does not request READ_EXERCISE_ROUTE and never receives the GPS track -> of a workout. -> - **No transmission without an explicit user action.** Health data is used to populate an on-screen -> form. Nothing leaves the device unless the user taps a suggestion, reviews the pre-filled post, -> and publishes it — at which point the post goes to the Nostr relays that user configured. The -> developer operates no server, so no health data is ever received, stored or processed by the -> developer or any third party on the developer's behalf. -> - **No secondary use.** Health data is never used for advertising, analytics, profiling, marketing -> or sale, is never shared with data brokers or information-resellers, and is never used for -> determining eligibility for insurance, credit or employment. -> - **No persistence beyond the session.** Suggestions are held in memory while the composer is open. +> - **Read-only.** Amethyst holds no Health Connect write permissions and never writes to it. +> - **Foreground only.** Reads happen only while the user has the My Fitness dashboard or the +> workout composer on screen. Amethyst does not request READ_HEALTH_DATA_IN_BACKGROUND and has +> no background worker, service or job that touches health data. +> - **Last four weeks only.** The dashboard reads a rolling 28-day window. Amethyst does not +> request READ_HEALTH_DATA_HISTORY and cannot see anything older. +> - **No location.** Amethyst does not request READ_EXERCISE_ROUTE and never receives the GPS +> track of a workout. +> - **No transmission to produce the feature.** The dashboard is computed on the device and +> displayed. Nothing is uploaded to render it. The developer operates no server, so no health +> data is ever received, stored or processed by the developer or any third party acting for the +> developer. +> - **Sharing is separate, explicit and per-item.** A user may choose to publish one workout as a +> post. That takes a deliberate tap, shows the user exactly what will be published, and requires +> them to confirm. It is never automatic, never bulk, and never a condition of using the +> tracking feature. Published posts go to the Nostr relays that user configured. +> - **No secondary use.** Health data is never used for advertising, analytics, profiling, +> marketing or sale, never shared with data brokers, and never used to determine eligibility for +> insurance, credit or employment. +> - **No persistence beyond the session.** The summary is held in memory while the screen is open. > The only thing stored is whatever the user chose to publish, as an ordinary post. -> - **Revocable, and optional.** The user can turn the feature off at Settings → Compose Settings → -> "Suggest workouts to share", or revoke the permissions in Health Connect, at any time; the rest -> of the app is unaffected. -> - **In-app rationale.** Amethyst handles both ACTION_SHOW_PERMISSIONS_RATIONALE (Android 13 and +> - **Revocable and optional.** The user can revoke the permissions in Health Connect at any time — +> the dashboard immediately returns to its prompt — or turn the composer suggestions off under +> Settings → Compose Settings. The rest of the app is unaffected. +> - **In-app rationale.** Amethyst handles ACTION_SHOW_PERMISSIONS_RATIONALE (Android 13 and > below) and ACTION_VIEW_PERMISSION_USAGE + CATEGORY_HEALTH_PERMISSIONS (Android 14+), showing a -> screen that lists each data type, its purpose, and a link to the full privacy policy. The same -> screen is reachable in-app from the "What Amethyst reads" link on the Connect card, before the -> permission request. -> - **Privacy policy:** https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md — see the -> section "Health and fitness data (Health Connect)", which lists every data type, its purpose, and -> each of the limits above. +> screen listing each data type, its purpose and a link to the privacy policy. The same screen +> is reachable from "What Amethyst reads" before any permission is requested. +> - **Privacy policy:** https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md — see +> "Health and fitness data (Health Connect)". diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 5f11d74d80..acab602da2 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -13,23 +13,29 @@ therefore it works.

  • Browse dedicated feeds: articles, pictures, short videos, live streams, podcasts, music, communities, classifieds, calendars and more.
  • Send and receive Bitcoin Lightning zaps.
  • -
  • Share your workouts — see below.
  • +
  • Track your training and share a workout — see below.
  • -

    Workouts

    -

    Amethyst has a Workouts section where you can publish a summary of a run, ride, walk, hike, swim, row -or gym session so the people who follow you can see what you did. Open the left drawer, tap -Workouts, then the + button to write one.

    -

    Typing every number by hand is tedious, so Amethyst can pre-fill the post from a workout your watch or -fitness app already saved to Android Health Connect (Samsung Health, Google Fit, Fitbit, Garmin Connect, -Strava and others all write there). With your permission, Amethyst reads finished workouts from the last -7 days and offers them as one-tap suggestions: pick one and the activity, duration, distance, calories, -heart rate, steps and elevation gain are filled in for you. You review the post and decide whether to -publish it.

    -

    This is optional and read-only. Amethyst asks for Health Connect permission only when you open the New -Workout composer, never reads in the background, never writes to Health Connect, never asks for your -exercise route or location, and never uploads anything until you publish a post yourself. You can switch -it off under Settings → Compose Settings, or revoke access in Health Connect at any time. Full -details are in the privacy policy at +

    My Fitness — track your training

    +

    Amethyst includes a personal training dashboard. Open the left drawer, tap Workouts, then +the chart icon in the top bar. With your permission it reads the workouts your watch or fitness app +has already saved to Android Health Connect (Samsung Health, Google Fit, Fitbit, Garmin Connect, +Strava and others all write there) and shows you:

    +
      +
    • How much you trained this week, and whether that is up or down on last week.
    • +
    • Your four-week weekly average for time, distance, steps and climb.
    • +
    • Where your time goes — running, cycling, walking, hiking, swimming, rowing, the gym.
    • +
    • Your best efforts: longest distance, longest workout, biggest climb, most steps, highest heart rate.
    • +
    • How many days you trained, and your current streak of consecutive active days.
    • +
    +

    All of it is computed on your phone and shown to you. Nothing is uploaded to produce it, and you +never have to post anything to use it.

    +

    Sharing a workout (optional)

    +

    If you want to tell your followers about a particular session, tap Share this workout on any +row. Amethyst opens a post pre-filled with that workout's figures; you review it and decide whether to +publish. One workout at a time, always with your confirmation, never automatic.

    +

    The Health Connect integration is read-only. Amethyst never writes to Health Connect, never reads in +the background, never asks for your exercise route or location, and only ever looks at the last four +weeks. You can revoke access in Health Connect at any time. Full details are in the privacy policy at github.com/vitorpamplona/amethyst.

    Amethyst is free and open source (MIT). There is no Amethyst server and no Amethyst account: your posts go straight to the Nostr relays you configure.

    From 491756979a0e6ea3d28360a863aeec0133859f92 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 17:19:04 +0000 Subject: [PATCH 005/127] feat: give My Fitness its own navigation destination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My Fitness was reachable only from an icon in the Workouts top bar, which buried the one screen the Health Connect permissions exist to serve. It is now a NavBarItem, so it appears in the drawer and can be pinned to the bottom bar like any other destination. It sits under "You" rather than "Feeds": every other Feeds entry is a stream of other people's events, while this is the user's own training — the same reason Profile, Drafts and Wallet live there. Four places had to stay in sync, three of them guarded: DrawerSectionsTest and BottomBarCategoriesTest fail the build when a catalog id is missing from a section or a picker category, and BottomBarFeedPreloaders' exhaustive `when` caught the fourth. My Fitness reads Health Connect on the device and issues no REQs, so its preload branch is deliberately empty. Adding the enum value needs no migration: nav items are persisted by name and unknown names are already dropped on decode, so an older build ignores the row rather than failing the settings blob. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019egdJyBHnrATZHjs86up8f --- .../amethyst/ui/navigation/bottombars/NavBarItem.kt | 9 +++++++++ .../amethyst/ui/navigation/drawer/DrawerSections.kt | 1 + .../ui/screen/loggedIn/BottomBarFeedPreloaders.kt | 4 ++++ amethyst/src/main/res/values/strings.xml | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt index 3c4fcef1d3..2050b16fcf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt @@ -40,6 +40,7 @@ enum class NavBarItem { DISCOVER, NOTIFICATIONS, PROFILE, + MY_FITNESS, MY_LISTS, BOOKMARKS, WEB_BOOKMARKS, @@ -255,6 +256,13 @@ val NavBarCatalog: Map = icon = MaterialSymbols.DirectionsRun, resolveRoute = { Route.Workouts }, ), + NavBarItem.MY_FITNESS to + NavBarItemDef( + id = NavBarItem.MY_FITNESS, + labelRes = R.string.my_fitness, + icon = MaterialSymbols.AutoMirrored.ShowChart, + resolveRoute = { Route.MyFitness }, + ), NavBarItem.GIT_REPOSITORIES to NavBarItemDef( id = NavBarItem.GIT_REPOSITORIES, @@ -505,6 +513,7 @@ val BottomBarCategories: List = MaterialSymbols.AccountCircle, listOf( NavBarItem.PROFILE, + NavBarItem.MY_FITNESS, NavBarItem.MY_LISTS, NavBarItem.BOOKMARKS, NavBarItem.WEB_BOOKMARKS, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt index 17014d4dd4..3bb35a47a2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt @@ -107,6 +107,7 @@ private val DrawerNavigateItems: List = private val DrawerYouItems: List = listOf( NavBarItem.PROFILE, + NavBarItem.MY_FITNESS, NavBarItem.MY_LISTS, NavBarItem.BOOKMARKS, NavBarItem.WEB_BOOKMARKS, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt index 809f89d5da..5575fdf25b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt @@ -117,6 +117,10 @@ private fun PreloadFor( // Favorite apps is a device-local launcher grid — nothing to preload from relays. NavBarItem.FAVORITE_APPS -> {} + // My Fitness summarises the user's own Health Connect data on the device. It issues no + // REQs, so there is nothing to warm up from relays. + NavBarItem.MY_FITNESS -> {} + NavBarItem.CALENDARS, NavBarItem.CALENDAR_COLLECTIONS, -> CalendarsFilterAssemblerSubscription(accountViewModel) diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index a67ef65287..eb7f3259d4 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -2771,4 +2771,8 @@ rationale screen. Needs to be an Android resource (not a commons Compose resource) because android:label on the manifest entry can only reference @string/. --> Health Connect and Amethyst + + + My Fitness From 40ab7e6ed2f03a04aefee06622061ff9673564a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 19:20:09 +0000 Subject: [PATCH 006/127] feat: build My Fitness from the whole training log, not just Health Connect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My Fitness was a Health Connect viewer: the dashboard was empty without the permission, and it did not count workouts the user had logged in Amethyst's own composer. The app asked you to record a workout and then ignored it. The log is now built from both sources Amethyst can learn from — Health Connect sessions, and the user's own published kind 1301 events (their manual entries, and anything posted from another NIP-101e client). Neither source alone is the truth: Health Connect cannot see a workout typed by hand or one posted elsewhere, and cannot look past 30 days; the published events carry none of the device detail. The consequence that matters: My Fitness now works with no health permissions at all. Health Connect became an enhancement rather than a gate — a banner over a working dashboard instead of a wall in front of an empty one. That is also the honest form of the Play declaration's argument, which the previous build could not make: Health Connect enriches a tracking feature that exists on its own rather than being that feature. Where a workout appears in both — the usual case once someone shares one their watch recorded — the Health Connect copy wins: it keeps the metrics the post dropped and a recorded start time rather than a publish timestamp. Matching is on activity type plus a 15-minute start window, wide because a published event often carries only the minute the user typed. Matching on time alone would swallow a genuine second session of a different activity, so the type must agree. A published workout with no start tag falls back to created_at. That is the publish time, not the workout time, which is close enough for the day-level buckets (streaks, active days, the week split) and is the only signal a manual entry has. 17 new tests: the dedupe boundary in both directions, same-time-different- activity, ordering, and the mapper's unit conversions (miles and feet land as metres — otherwise every total is out by 1.6x) plus its zero-is-absent rule. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019egdJyBHnrATZHjs86up8f --- PRIVACY.md | 2 + .../workouts/health/DetectedWorkout.kt | 17 +- .../service/workouts/health/TrainingLog.kt | 101 ++++++++++++ .../workouts/fitness/MyFitnessScreen.kt | 82 +++++++--- .../workouts/fitness/MyFitnessViewModel.kt | 114 ++++++++++--- .../health/PublishedWorkoutMappingTest.kt | 144 +++++++++++++++++ .../workouts/health/TrainingLogTest.kt | 152 ++++++++++++++++++ .../composeResources/values/strings.xml | 3 +- docs/health-connect-play-declaration.md | 24 ++- 9 files changed, 583 insertions(+), 56 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt diff --git a/PRIVACY.md b/PRIVACY.md index ab3c669159..1363eaf653 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -36,6 +36,8 @@ Amethyst's **My Fitness** screen (Workouts → the chart icon) summarises your o This is what the health permissions are for. The summary is computed on your phone and shown to you; nothing is sent anywhere to produce it, and you never have to post anything to use it. +My Fitness also counts the workouts you have logged in Amethyst itself, so it works whether or not you connect Health Connect. Connecting adds the sessions your watch recorded and the details a hand-typed workout does not carry — heart rate, steps and climb. + Separately, you may choose to publish one workout as a Nostr post (a NIP-101e kind 1301 event) so the people who follow you can see it. That takes a deliberate tap on "Share this workout", shows you the pre-filled post, and waits for you to confirm. It is never automatic. The feature is optional and off until you grant the permissions. Amethyst asks for them only when you open My Fitness or the New Workout composer — never on first launch. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt index f8f8637e55..f92e7d141f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt @@ -23,8 +23,18 @@ package com.vitorpamplona.amethyst.service.workouts.health import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType +/** Where a workout in the training log came from. */ +enum class WorkoutOrigin { + /** Recorded by a watch or fitness app and read from Health Connect. */ + HEALTH_CONNECT, + + /** A kind 1301 the user published themselves — typed into Amethyst, or posted from another client. */ + PUBLISHED, +} + /** - * A finished workout read from Health Connect and mapped to the fields Amethyst + * A finished workout in the user's training log: read from Health Connect, or + * recovered from a kind 1301 they published. Carries the fields Amethyst * can publish as a NIP-101e kind 1301 event. Platform-neutral and free of any * Health Connect types so it can feed the navigation route and the suggestion * UI directly. @@ -49,6 +59,11 @@ data class DetectedWorkout( val elevationGainMeters: Double?, /** Human-readable name of the app/device that wrote the record (e.g. "Samsung Health"). */ val source: String, + /** + * Which store this came from. Defaults to Health Connect because that is where the type + * originated and where every constructor but the published-event mapper still builds from. + */ + val origin: WorkoutOrigin = WorkoutOrigin.HEALTH_CONNECT, /** * How many Health Connect sessions this workout represents. 1 for a raw * session; higher when [WorkoutMerger] combined several close-by same-type diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt new file mode 100644 index 0000000000..639ccee6c4 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service.workouts.health + +import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent +import kotlin.math.abs + +/** + * The user's training log: everything they have done, from both places Amethyst can learn about + * it. + * + * Health Connect knows what their watch recorded. It does not know about a workout they typed + * into Amethyst by hand, or one they posted from another NIP-101e client, and it cannot see + * further back than 30 days. Their own kind 1301 events know all of those and none of the + * device detail. Neither source alone is the truth, so the dashboard reads both. + * + * The practical consequence is that My Fitness works with no health permissions at all — a user + * who never connects Health Connect still gets a summary of what they have logged. + */ +object TrainingLog { + /** + * How far apart two records of the same activity can start and still be judged the same + * workout. Generous on purpose: a published event often carries only the minute the user + * typed, or the time the post went out, while Health Connect has the exact second. + */ + const val DEDUPE_TOLERANCE_SECONDS = 900L + + /** + * Combines both sources into one log, newest first. + * + * Where the same workout appears in both — the usual case once a user shares one that came + * from their watch — the Health Connect copy wins: it carries the metrics the published + * event may have dropped (heart rate, steps, climb), and its start time is the recorded one + * rather than a publish timestamp. + */ + fun merge( + healthConnect: List, + published: List, + ): List { + val deduped = + published.filterNot { candidate -> + healthConnect.any { it.isProbablySameWorkoutAs(candidate) } + } + + return (healthConnect + deduped).sortedByDescending { it.startTimeEpochSeconds } + } + + private fun DetectedWorkout.isProbablySameWorkoutAs(other: DetectedWorkout): Boolean = + exercise == other.exercise && + abs(startTimeEpochSeconds - other.startTimeEpochSeconds) <= DEDUPE_TOLERANCE_SECONDS +} + +/** + * Reads a published workout back into the training log, or null when it carries nothing a + * summary can use — no recognisable activity, or no duration. + * + * Start time prefers the explicit `start` tag and falls back to the event's own `created_at`. + * That fallback is the publish time, not the workout time: a manual entry has no better signal, + * and for day-level bucketing (streaks, active days, the week split) it is close enough, since + * people post a workout the same day they do it. + */ +fun WorkoutRecordEvent.toDetectedWorkout(): DetectedWorkout? { + val activity = activityType() ?: return null + + val duration = durationSeconds() ?: return null + if (duration <= 0) return null + + return DetectedWorkout( + id = id, + exercise = activity, + title = title()?.takeIf { it.isNotBlank() }, + startTimeEpochSeconds = workoutStartTime() ?: workoutStart() ?: createdAt, + durationSeconds = duration, + distanceMeters = distance()?.toMeters()?.takeIf { it > 0 }, + calories = calories()?.takeIf { it > 0 }, + avgHeartRate = avgHeartRate()?.takeIf { it > 0 }, + maxHeartRate = maxHeartRate()?.takeIf { it > 0 }, + steps = steps()?.takeIf { it > 0 }, + elevationGainMeters = elevationGain()?.toMeters()?.takeIf { it > 0 }, + source = workoutSource() ?: "", + origin = WorkoutOrigin.PUBLISHED, + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt index f31f9aa8a5..3af4c0422d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt @@ -68,6 +68,7 @@ import com.vitorpamplona.amethyst.commons.resources.my_fitness_best_most_steps import com.vitorpamplona.amethyst.commons.resources.my_fitness_bests import com.vitorpamplona.amethyst.commons.resources.my_fitness_by_activity import com.vitorpamplona.amethyst.commons.resources.my_fitness_calories +import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_banner import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_button import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_message import com.vitorpamplona.amethyst.commons.resources.my_fitness_connect_title @@ -82,7 +83,6 @@ import com.vitorpamplona.amethyst.commons.resources.my_fitness_streak import com.vitorpamplona.amethyst.commons.resources.my_fitness_this_week import com.vitorpamplona.amethyst.commons.resources.my_fitness_time import com.vitorpamplona.amethyst.commons.resources.my_fitness_title -import com.vitorpamplona.amethyst.commons.resources.my_fitness_unavailable import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_bpm import com.vitorpamplona.amethyst.commons.resources.my_fitness_unit_kcal import com.vitorpamplona.amethyst.commons.resources.my_fitness_vs_last_week @@ -120,6 +120,10 @@ fun MyFitnessScreen( val context = LocalContext.current val state by viewModel.state.collectAsStateWithLifecycle() + // Which account's workouts to summarise. Re-runs on an account switch, which resets the + // dashboard to Loading rather than showing the previous user's numbers. + viewModel.init(accountViewModel.userProfile().pubkeyHex) + val permissionLauncher = rememberLauncherForActivityResult(PermissionController.createRequestPermissionResultContract()) { viewModel.refresh(context) @@ -139,34 +143,34 @@ fun MyFitnessScreen( when (val current = state) { MyFitnessViewModel.State.Loading -> CenteredBox { CircularProgressIndicator() } - MyFitnessViewModel.State.Unavailable -> - CenteredBox { - Text( - text = stringRes(Res.string.my_fitness_unavailable), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = 32.dp), - ) - } - - MyFitnessViewModel.State.NeedsPermission -> - ConnectPrompt( - onDetails = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) }, - onConnect = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) }, - ) - is MyFitnessViewModel.State.Ready -> if (current.report.isEmpty) { - CenteredBox { - Text( - text = stringRes(Res.string.my_fitness_empty), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = 32.dp), + // Nothing logged yet. Offering Health Connect is the useful thing to do + // when it could fill the screen; otherwise just say the log is empty. + if (current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE) { + ConnectPrompt( + onDetails = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) }, + onConnect = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) }, ) + } else { + CenteredBox { + Text( + text = stringRes(Res.string.my_fitness_empty), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 32.dp), + ) + } } } else { - Dashboard(current.report) { workout, label -> + Dashboard( + report = current.report, + // Only offered when it would actually add something: a device with no + // provider gets no banner to act on. + showConnectBanner = current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE, + onDetails = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) }, + onConnect = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) }, + ) { workout, label -> nav.nav(workout.toNewWorkoutRoute(label)) } } @@ -183,6 +187,32 @@ private fun CenteredBox(content: @Composable () -> Unit) { ) { content() } } +/** + * Shown above a dashboard that is already working, when Health Connect could add device-recorded + * workouts to it. Deliberately a strip rather than a blocking card: the summary below it is real, + * and this only offers to make it richer. + */ +@Composable +private fun ConnectBanner( + onDetails: () -> Unit, + onConnect: () -> Unit, +) { + OutlinedCard(shape = RoundedCornerShape(14.dp), modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text(stringRes(Res.string.my_fitness_connect_title), style = MaterialTheme.typography.titleSmall) + Text( + text = stringRes(Res.string.my_fitness_connect_banner), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { + TextButton(onClick = onDetails) { Text(stringRes(Res.string.workout_suggestion_connect_details)) } + TextButton(onClick = onConnect) { Text(stringRes(Res.string.my_fitness_connect_button)) } + } + } + } +} + @Composable private fun ConnectPrompt( onDetails: () -> Unit, @@ -219,6 +249,9 @@ private fun ConnectPrompt( @Composable private fun Dashboard( report: WorkoutStats.Report, + showConnectBanner: Boolean, + onDetails: () -> Unit, + onConnect: () -> Unit, onShare: (DetectedWorkout, String) -> Unit, ) { val miles = remember { prefersMiles() } @@ -227,6 +260,7 @@ private fun Dashboard( modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp), verticalArrangement = Arrangement.spacedBy(18.dp), ) { + if (showConnectBanner) ConnectBanner(onDetails = onDetails, onConnect = onConnect) ThisWeekCard(report, miles) ConsistencyRow(report) WindowTotalsCard(report, miles) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt index 6246ded88b..e2c38ea854 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt @@ -25,38 +25,61 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager +import com.vitorpamplona.amethyst.service.workouts.health.TrainingLog import com.vitorpamplona.amethyst.service.workouts.health.WorkoutStats +import com.vitorpamplona.amethyst.service.workouts.health.toDetectedWorkout +import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.time.Duration import java.time.Instant /** * State holder for the My Fitness dashboard: the user's own training over the last - * [WorkoutStats.WINDOW_DAYS], read from Health Connect and summarised by [WorkoutStats]. + * [WorkoutStats.WINDOW_DAYS], summarised by [WorkoutStats]. * - * Nothing here publishes or touches the network. The dashboard is the user looking at their - * own numbers; sharing one of them is a separate, deliberate action from the workout list. + * The log is built from both sources Amethyst has — Health Connect, and the user's own published + * kind 1301 events (see [TrainingLog]) — so the dashboard is useful before any health permission + * is granted and stays useful if one is revoked. Health Connect adds device detail; it is not a + * precondition. + * + * Nothing here publishes or touches the network. The dashboard is the user looking at their own + * numbers; sharing one is a separate, deliberate action from the workout list. */ @Stable class MyFitnessViewModel : ViewModel() { + /** Whether the richer Health Connect source is switched on, and whether it even could be. */ + enum class HealthConnectStatus { + /** Granted and contributing to the log. */ + CONNECTED, + + /** A provider is installed but Amethyst has no permissions — worth offering. */ + AVAILABLE, + + /** No provider on this device; there is nothing to offer. */ + UNAVAILABLE, + } + @Immutable sealed interface State { /** First load, or a reload after a permission change. */ data object Loading : State - /** No Health Connect provider on this device — nothing to offer. */ - data object Unavailable : State - - /** Provider present, permissions not granted yet. */ - data object NeedsPermission : State - - /** Granted and summarised. [WorkoutStats.Report.isEmpty] covers "nothing recorded yet". */ + /** + * Summarised. [WorkoutStats.Report.isEmpty] covers "nothing logged yet", which is a + * normal state rather than an error — a new user has published nothing and may not have + * connected Health Connect. + */ data class Ready( val report: WorkoutStats.Report, + val healthConnect: HealthConnectStatus, ) : State } @@ -65,28 +88,67 @@ class MyFitnessViewModel : ViewModel() { private var manager: HealthConnectManager? = null + /** The pubkey whose workouts this dashboard summarises. Set by the screen before refreshing. */ + private var pubkeyHex: String? = null + + fun init(pubkeyHex: String) { + if (this.pubkeyHex != pubkeyHex) { + this.pubkeyHex = pubkeyHex + _state.value = State.Loading + } + } + /** - * Refreshes the dashboard. Safe to call on every resume: it re-checks permissions first, - * so revoking access in Health Connect drops the screen back to its prompt rather than + * Rebuilds the dashboard. Safe to call on every resume: it re-checks permissions as well as + * data, so revoking access in Health Connect drops those workouts out of the log rather than * leaving stale numbers on display. */ fun refresh(context: Context) { viewModelScope.launch { - if (!HealthConnectManager.isAvailable(context)) { - _state.value = State.Unavailable - return@launch - } - - val hc = manager ?: HealthConnectManager(context.applicationContext).also { manager = it } - - if (!hc.hasAllPermissions()) { - _state.value = State.NeedsPermission - return@launch - } - val now = Instant.now() - val workouts = hc.readWorkouts(now.minus(Duration.ofDays(WorkoutStats.WINDOW_DAYS)), now) - _state.value = State.Ready(WorkoutStats.report(workouts, now)) + val since = now.minus(Duration.ofDays(WorkoutStats.WINDOW_DAYS)) + + val status = healthConnectStatus(context) + val fromHealthConnect = + if (status == HealthConnectStatus.CONNECTED) { + manager?.readWorkouts(since, now).orEmpty() + } else { + emptyList() + } + + val fromRelays = publishedWorkouts(since.epochSecond) + + _state.value = + State.Ready( + report = WorkoutStats.report(TrainingLog.merge(fromHealthConnect, fromRelays), now), + healthConnect = status, + ) + } + } + + private suspend fun healthConnectStatus(context: Context): HealthConnectStatus { + if (!HealthConnectManager.isAvailable(context)) return HealthConnectStatus.UNAVAILABLE + + val hc = manager ?: HealthConnectManager(context.applicationContext).also { manager = it } + return if (hc.hasAllPermissions()) HealthConnectStatus.CONNECTED else HealthConnectStatus.AVAILABLE + } + + /** + * The user's own kind 1301 events from the local cache, newer than [sinceEpochSeconds]. + * + * Scans off the main thread: LocalCache holds every event the session has seen, and this + * walks all of them. + */ + private suspend fun publishedWorkouts(sinceEpochSeconds: Long): List { + val mine = pubkeyHex ?: return emptyList() + + return withContext(Dispatchers.Default) { + LocalCache.notes + .filterIntoSet { _, note -> + val event = note.event + event is WorkoutRecordEvent && event.pubKey == mine + }.mapNotNull { (it.event as WorkoutRecordEvent).toDetectedWorkout() } + .filter { it.startTimeEpochSeconds >= sinceEpochSeconds } } } } diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt new file mode 100644 index 0000000000..e8c3ee06a6 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service.workouts.health + +import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.AvgHeartRateTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.CaloriesTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.DistanceTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.DurationTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ElevationGainTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.StepsTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.TitleTag +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.WorkoutStartTimeTag +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test + +class PublishedWorkoutMappingTest { + private val createdAt = 1_800_000_000L + + private fun event(vararg tags: Array) = + WorkoutRecordEvent( + id = "eventid", + pubKey = "pubkey", + createdAt = createdAt, + tags = arrayOf(*tags), + content = "", + sig = "sig", + ) + + @Test + fun `a full workout round-trips into the training log`() { + val workout = + event( + ExerciseTag.assemble(ExerciseType.RUNNING), + TitleTag.assemble("Morning Run"), + DurationTag.assemble(1800), + DistanceTag.assemble(5.0, DistanceTag.KILOMETERS), + CaloriesTag.assemble(380), + AvgHeartRateTag.assemble(150), + StepsTag.assemble(6000), + WorkoutStartTimeTag.assemble(createdAt - 3600), + ).toDetectedWorkout() + + assertNotNull(workout) + assertEquals(ExerciseType.RUNNING, workout!!.exercise) + assertEquals("Morning Run", workout.title) + assertEquals(1800, workout.durationSeconds) + assertEquals(5000.0, workout.distanceMeters!!, 0.001) + assertEquals(380, workout.calories) + assertEquals(150, workout.avgHeartRate) + assertEquals(6000, workout.steps) + assertEquals(createdAt - 3600, workout.startTimeEpochSeconds) + assertEquals(WorkoutOrigin.PUBLISHED, workout.origin) + } + + /** Miles on the wire must land as metres in the log, or every total is wrong by 1.6x. */ + @Test + fun `an imperial distance is converted to metres`() { + val workout = + event( + ExerciseTag.assemble(ExerciseType.RUNNING), + DurationTag.assemble(1800), + DistanceTag.assemble(3.0, DistanceTag.MILES), + ).toDetectedWorkout() + + assertEquals(4828.032, workout!!.distanceMeters!!, 0.001) + } + + @Test + fun `an elevation in feet is converted to metres`() { + val workout = + event( + ExerciseTag.assemble(ExerciseType.HIKING), + DurationTag.assemble(3600), + ElevationGainTag.assemble(1000.0, "ft"), + ).toDetectedWorkout() + + assertEquals(304.8, workout!!.elevationGainMeters!!, 0.01) + } + + /** + * A workout typed in by hand carries no start time. Falling back to created_at keeps it in + * the right day bucket for streaks and the week split. + */ + @Test + fun `a manual entry without a start time falls back to the publish time`() { + val workout = + event( + ExerciseTag.assemble(ExerciseType.YOGA), + DurationTag.assemble(600), + ).toDetectedWorkout() + + assertEquals(createdAt, workout!!.startTimeEpochSeconds) + } + + @Test + fun `an event with no recognisable activity is skipped`() { + assertNull(event(DurationTag.assemble(1800)).toDetectedWorkout()) + } + + @Test + fun `an event with no duration is skipped`() { + assertNull(event(ExerciseTag.assemble(ExerciseType.RUNNING)).toDetectedWorkout()) + } + + /** Zeroes on the wire are absences, not measurements — they must not drag totals or bests. */ + @Test + fun `zero metrics are treated as absent`() { + val workout = + event( + ExerciseTag.assemble(ExerciseType.STRENGTH), + DurationTag.assemble(2400), + CaloriesTag.assemble(0), + StepsTag.assemble(0), + AvgHeartRateTag.assemble(0), + ).toDetectedWorkout() + + assertNull(workout!!.calories) + assertNull(workout.steps) + assertNull(workout.avgHeartRate) + } +} diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt new file mode 100644 index 0000000000..e5deae11a4 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service.workouts.health + +import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class TrainingLogTest { + private val noon = 1_800_000_000L + + private fun workout( + id: String, + origin: WorkoutOrigin, + startTimeEpochSeconds: Long, + exercise: ExerciseType = ExerciseType.RUNNING, + distanceMeters: Double? = null, + avgHeartRate: Int? = null, + ) = DetectedWorkout( + id = id, + exercise = exercise, + title = null, + startTimeEpochSeconds = startTimeEpochSeconds, + durationSeconds = 1800, + distanceMeters = distanceMeters, + calories = null, + avgHeartRate = avgHeartRate, + maxHeartRate = null, + steps = null, + elevationGainMeters = null, + source = "test", + origin = origin, + ) + + @Test + fun `with no health connect the log is just what the user published`() { + val published = listOf(workout("a", WorkoutOrigin.PUBLISHED, noon)) + + val merged = TrainingLog.merge(emptyList(), published) + + assertEquals(1, merged.size) + assertEquals("a", merged[0].id) + } + + @Test + fun `with no published workouts the log is just health connect`() { + val hc = listOf(workout("a", WorkoutOrigin.HEALTH_CONNECT, noon)) + + assertEquals(1, TrainingLog.merge(hc, emptyList()).size) + } + + /** The common case: the user shared a workout their watch recorded. It must not count twice. */ + @Test + fun `a published copy of a health connect workout is dropped`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon, avgHeartRate = 150)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon + 60)) + + val merged = TrainingLog.merge(hc, published) + + assertEquals(1, merged.size) + assertEquals("hc", merged[0].id) + // The Health Connect copy wins because it carries the metrics the post dropped. + assertEquals(150, merged[0].avgHeartRate) + } + + @Test + fun `a published workout just outside the tolerance is kept as its own`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon + TrainingLog.DEDUPE_TOLERANCE_SECONDS + 1)) + + assertEquals(2, TrainingLog.merge(hc, published).size) + } + + @Test + fun `the tolerance boundary itself counts as the same workout`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon + TrainingLog.DEDUPE_TOLERANCE_SECONDS)) + + assertEquals(1, TrainingLog.merge(hc, published).size) + } + + /** Deduping on time alone would swallow a genuine second session. */ + @Test + fun `a different activity at the same time is not a duplicate`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon, exercise = ExerciseType.RUNNING)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon, exercise = ExerciseType.STRENGTH)) + + assertEquals(2, TrainingLog.merge(hc, published).size) + } + + @Test + fun `dedupe also applies when the published copy is slightly earlier`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon - 300)) + + assertEquals(1, TrainingLog.merge(hc, published).size) + } + + @Test + fun `the merged log is ordered newest first`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon - 10_000)) + val published = + listOf( + workout("older", WorkoutOrigin.PUBLISHED, noon - 50_000), + workout("newest", WorkoutOrigin.PUBLISHED, noon), + ) + + val merged = TrainingLog.merge(hc, published) + + assertEquals(listOf("newest", "hc", "older"), merged.map { it.id }) + } + + @Test + fun `one health connect workout does not swallow several unrelated published ones`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) + val published = + listOf( + workout("dup", WorkoutOrigin.PUBLISHED, noon + 30), + workout("yesterday", WorkoutOrigin.PUBLISHED, noon - 86_400), + workout("tomorrow", WorkoutOrigin.PUBLISHED, noon + 86_400), + ) + + val merged = TrainingLog.merge(hc, published) + + assertEquals(3, merged.size) + assertTrue(merged.none { it.id == "dup" }) + } + + @Test + fun `an empty log merges to empty`() { + assertTrue(TrainingLog.merge(emptyList(), emptyList()).isEmpty()) + } +} diff --git a/commons/src/commonMain/composeResources/values/strings.xml b/commons/src/commonMain/composeResources/values/strings.xml index 019c73f205..5f22370237 100644 --- a/commons/src/commonMain/composeResources/values/strings.xml +++ b/commons/src/commonMain/composeResources/values/strings.xml @@ -708,10 +708,11 @@ Most steps Highest heart rate Recent workouts - No workouts recorded in the last 4 weeks. Once your watch or fitness app saves one, it shows up here. + Nothing logged in the last 4 weeks. Workouts you post, and any your watch records, show up here. Health Connect is not available on this device, so Amethyst has no workouts to summarise. Track your training Connect Health Connect to see your weekly totals, week-over-week trend, best efforts and training streak. Everything stays on your phone. + Connect Health Connect to add the workouts your watch records, with heart rate, steps and climb. Connect Share this workout bpm diff --git a/docs/health-connect-play-declaration.md b/docs/health-connect-play-declaration.md index 09fc252b6b..5f889834d5 100644 --- a/docs/health-connect-play-declaration.md +++ b/docs/health-connect-play-declaration.md @@ -24,9 +24,18 @@ publishes anything. Publishing a workout is now one optional action on a row of app, the test to apply is: every data type listed is rendered back to the user as their own statistic, on a screen that has no posting requirement. +**The dashboard does not depend on Health Connect.** It builds the training log from two sources: +Health Connect, and the user's own published kind 1301 workout events. A user who never grants a +health permission still gets the full summary of everything they have logged — Health Connect adds +the device detail (heart rate, steps, climb) and the workouts they never posted. This matters for +the declaration's honesty: Health Connect *enriches* a tracking feature that exists on its own +rather than *being* the feature. + Related code: - `service/workouts/health/WorkoutStats.kt` — all dashboard arithmetic; pure, unit-tested. +- `service/workouts/health/TrainingLog.kt` — merges Health Connect with the user's own published + workouts, deduping the ones that appear in both. - `ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt` — the dashboard. - `service/workouts/health/HealthConnectManager.kt` — the only place the app touches Health Connect. - `ui/screen/loggedIn/workouts/health/HealthConnectRationaleActivity.kt` — the in-app rationale screen. @@ -39,14 +48,18 @@ Related code: > Amethyst is a social client for Nostr, an open decentralized social protocol. It also includes > a fitness feature, **My Fitness**, which is what uses Health Connect. > -> My Fitness is a personal training dashboard. It reads the workouts the user's watch or fitness -> app has already saved to Health Connect and turns them into a picture of how that person is +> My Fitness is a personal training dashboard. It builds a log of the user's own workouts — from +> the workouts their watch or fitness app has saved to Health Connect, and from the workout +> records they have logged in Amethyst itself — and turns it into a picture of how that person is > training: how much they did this week and whether that is up or down on last week, how their > time splits across running, cycling, walking, swimming and the gym, their best efforts, how > many days they trained, and their current streak of consecutive active days. > > The purpose is to help the user track, monitor, analyze and improve their own physical -> fitness. The numbers are shown to the person who recorded them. No part of the dashboard +> fitness. Health Connect is not a precondition for it: the dashboard works from the user's own +> logged workouts alone, and Health Connect is what lets it also count the sessions their watch +> recorded and show the device metrics — heart rate, steps and elevation — that a hand-entered +> workout does not carry. The numbers are shown to the person who recorded them. No part of the dashboard > requires posting anything, and nothing is transmitted anywhere to produce it — the summary is > computed on the device from Health Connect data and displayed. > @@ -62,7 +75,10 @@ Related code: > 2. Open the navigation drawer (hamburger, top-left), and under **Feeds** tap **Workouts**. > 3. In the top bar, tap the **chart icon** to open **My Fitness**. > 4. The screen explains what will be read and offers **What Amethyst reads** (the full rationale -> screen) and **Connect**. Tap Connect and grant the permissions. +> screen) and **Connect**. Tap Connect and grant the permissions. (If the account has already +> logged workouts in Amethyst, the dashboard is already populated from those and the Health +> Connect offer appears as a banner above it instead — the feature does not gate on the +> permission.) > 5. The dashboard appears: "This week" totals with the change against last week; the streak, > active-days and workout-count tiles; the four-week weekly average; the per-activity > breakdown; best efforts; and the recent-workout list. From c3cecf1e9f336cba2ddefe20ec752e6a11c0f7ae Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 19:23:22 +0000 Subject: [PATCH 007/127] fix(qr): correct the bunker message, and pin what the scanner sheet has to explain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While looking into routing more scanned payloads I found the reverse: the scanner's explanation sheet told users to add a bunker:// address "from the NIP-46 signer screen", which cannot accept one. Amethyst *publishes* bunker addresses — it is the remote signer — and the signer screen pairs nostrconnect:// offers only; remote-signer login exists on Desktop and even there it generates a nostrconnect:// rather than consuming a bunker URI. Split the string so bunker and nostrconnect each say something true. The investigation also showed raw nostr+walletconnect:// URIs already route, via the fallback at the end of uriToRoute rather than the dlnwc?value= branch above it. Both behaviours are now pinned by tests, because what uriToRoute returns null for is exactly what that sheet has to explain. Also set LANG/LC_ALL to C.UTF-8 for agent sessions. Without a UTF-8 locale sun.jnu.encoding is ASCII, and `./gradlew test` fails while *writing its HTML report* — after every test has passed — because eleven test names across four modules contain an em dash. It cost a full 12-minute run this session and would cost one in every future session. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX --- .claude/settings.json | 4 ++ .../qrcode/scanner/ScanOutcomeSheet.kt | 7 ++- .../amethyst/ui/UriToRouteTest.kt | 44 +++++++++++++++++++ .../composeResources/values/strings.xml | 3 +- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index c0238b52b5..e7a86ad229 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,8 @@ { + "env": { + "LANG": "C.UTF-8", + "LC_ALL": "C.UTF-8" + }, "hooks": { "PreToolUse": [ { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt index 1352a805b6..e6e06d6ea2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/ScanOutcomeSheet.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.qr_scanner_copy +import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_bunker import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_cashu import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_lightning import com.vitorpamplona.amethyst.commons.resources.qr_scanner_kind_nostr_unsupported @@ -137,7 +138,11 @@ private fun explain(payload: ScannedPayload): String = } is ScannedPayload.WalletConnect -> stringRes(Res.string.qr_scanner_kind_wallet) - is ScannedPayload.Bunker, is ScannedPayload.NostrConnect -> stringRes(Res.string.qr_scanner_kind_signer) + // Split deliberately: Amethyst PUBLISHES bunker:// addresses (it is the signer) and has no + // screen that consumes one, so pointing the user at the signer screen -- as this used to -- + // sends them somewhere that cannot accept it. + is ScannedPayload.Bunker -> stringRes(Res.string.qr_scanner_kind_bunker) + is ScannedPayload.NostrConnect -> stringRes(Res.string.qr_scanner_kind_signer) is ScannedPayload.Lightning -> stringRes(Res.string.qr_scanner_kind_lightning) is ScannedPayload.Cashu -> stringRes(Res.string.qr_scanner_kind_cashu) is ScannedPayload.Web -> stringRes(Res.string.qr_scanner_kind_web) diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt index 309e8f338f..bef8fe833e 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt @@ -58,6 +58,50 @@ class UriToRouteTest { assertEquals(Route.Hashtag("foo"), uriToRoute("nostr:hashtag?id=foo", account)) } + // The QR scanner shows a "can't open this" sheet for anything uriToRoute returns null for, so + // what does and does not route here decides what that sheet ever has to explain. + + @Test + fun rawWalletConnectUrisRouteWithoutTheDlnwcWrapper() { + // Not just the `dlnwc?value=` deep-link form: a wallet's QR code holds the bare URI, and + // the fallback at the end of uriToRoute is what catches it. + assertEquals( + Route.WalletAddNwc(NWC_URI), + uriToRoute(NWC_URI, account), + ) + } + + @Test + fun everyWalletConnectSchemeSpellingRoutes() { + assertEquals( + Route.WalletAddNwc(NWC_URI_NO_PLUS), + uriToRoute(NWC_URI_NO_PLUS, account), + ) + } + + @Test + fun walletConnectDeepLinksStillUnwrapTheValueParameter() { + assertEquals( + Route.WalletAddNwc(NWC_URI), + uriToRoute("dlnwc?value=$NWC_URI", account), + ) + } + + @Test + fun bunkerUrisDoNotRouteAnywhere() { + // Amethyst *publishes* bunker:// addresses (it is the remote signer); it has no screen that + // consumes one. The NIP-46 signer screen pairs nostrconnect:// offers only. Until that + // changes, a scanned bunker:// belongs in the scanner's explanation sheet, and the sheet + // must not tell the user to take it somewhere that cannot accept it. + assertNull(uriToRoute("bunker://$PUBKEY_HEX?relay=wss%3A%2F%2Frelay.example&secret=abc", account)) + } + + companion object { + private const val PUBKEY_HEX = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c" + private const val NWC_URI = "nostr+walletconnect://$PUBKEY_HEX?relay=wss%3A%2F%2Frelay.example&secret=$PUBKEY_HEX" + private const val NWC_URI_NO_PLUS = "nostrwalletconnect://$PUBKEY_HEX?relay=wss%3A%2F%2Frelay.example&secret=$PUBKEY_HEX" + } + @Test fun nostrConnectOfferRoutesToTheSignerScreenCarryingTheUri() { val offer = "nostrconnect://" + "b".repeat(64) + "?relay=wss%3A%2F%2Frelay.example.com&secret=abc123" diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml index 259a1a8869..4b80850cb4 100644 --- a/commonsUI/src/commonMain/composeResources/values/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values/strings.xml @@ -218,7 +218,8 @@ This code carries a private key or a pairing secret, so its contents are not shown here. Use the screen that expects it. This is a private key. Paste it on the login screen instead. This is a wallet connection. Add it from Wallet settings. - This is a remote signer. Add it from the NIP-46 signer screen. + This is a remote-signer address. Amethyst hands these out so other apps can ask it to sign — it cannot sign in with one. + This is an app asking to connect to your signer. Open it from the NIP-46 signer screen. This is a Lightning invoice. This is a Cashu token. This is a web link. From 57121fdf34bbdf2c6c69271d4207e57737444c13 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 19:26:09 +0000 Subject: [PATCH 008/127] test(qr): add the phase-0 decode corpus and its device-free baseline The plan's phase 0 was meant to gate the decoder swap on a measurement rather than an argument, and it had not been done: there is no camera or emulator in this environment, so the corpus was never built. Most of it does not need one. QrCorpus generates the fixtures deterministically in pure java.awt from the same ZXing encoder QrCodeDrawer uses, degrading each payload the way the real world does -- out of focus, tilted, off-axis, dim, inverted, glared, noisy, photographed off another screen, and too far away. QrCorpusBaselineTest then measures ZXing-Java, the decoder the old scanner used, over that corpus on the JVM, and records the result per category. The "too far away" fixtures are sized by pixels-per-module rather than absolute pixels, because that ratio is what decides whether a symbol is resolvable at all -- and it is why a long nprofile is harder to scan than a short npub at the same physical size, which is the open ECC-level question in the plan. The instrumented half, QrDecodeCorpusTest, runs zxing-cpp over the exported images and fails if it reads fewer than ZXing-Java in any category. That is the gate; it needs a device, so it is written and ready rather than run. The baseline is deliberately generous to the old decoder -- full frame, with an inverted retry, where the shipped scanner cropped to a viewfinder rect and alternated inversion across frames. A win measured here is a floor on the real-world difference, not the whole of it. Export is opt-in (-Dqr.corpus.export=true) so an ordinary run never dirties the working tree. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX --- .../qrcode/scanner/QrDecodeCorpusTest.kt | 128 ++++++ .../loggedIn/qrcode/scanner/QrCorpus.kt | 367 ++++++++++++++++++ .../qrcode/scanner/QrCorpusBaselineTest.kt | 187 +++++++++ 3 files changed, 682 insertions(+) create mode 100644 amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrDecodeCorpusTest.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrDecodeCorpusTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrDecodeCorpusTest.kt new file mode 100644 index 0000000000..d50330e03f --- /dev/null +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrDecodeCorpusTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Runs zxing-cpp over the committed [QrCorpus] images and asserts it reads at least as many as + * ZXing-Java did, category by category. + * + * This is the gate the plan's phase 0 asks for: the justification for replacing the decoder is + * supposed to be a measurement, not an argument. The baseline it compares against was produced on + * the JVM by `QrCorpusBaselineTest` from the same images. + * + * Regenerate both with: + * ``` + * ./gradlew :amethyst:testFdroidDebugUnitTest --tests '*QrCorpusBaselineTest*' -Dqr.corpus.export=true + * ``` + */ +@RunWith(AndroidJUnit4::class) +class QrDecodeCorpusTest { + private val assets = InstrumentationRegistry.getInstrumentation().context.assets + private val decoder = ZxingCppBarcodeDecoder() + + @Test + fun readsEveryCleanCode() { + fixtures().filter { it.category == "clean" }.forEach { + assertTrue("clean fixture ${it.file} must decode", decode(it)) + } + } + + @Test + fun beatsTheOldDecoderInEveryCategory() { + val baseline = baseline() + assertTrue("baseline.tsv missing - regenerate the corpus", baseline.isNotEmpty()) + + val results = fixtures().groupBy { it.category }.mapValues { (_, list) -> list.count { decode(it) } } + val regressions = mutableListOf() + + val report = + buildString { + appendLine() + appendLine("category zxing-cpp ZXing-Java") + baseline.keys.sorted().forEach { category -> + val old = baseline.getValue(category) + val new = results[category] ?: 0 + appendLine(" %-20s %d/%d %d/%d".format(category, new, old.total, old.passed, old.total)) + if (new < old.passed) regressions += "$category: $new < ${old.passed}" + } + } + println(report) + + if (regressions.isNotEmpty()) { + fail("zxing-cpp read fewer codes than ZXing-Java in: ${regressions.joinToString("; ")}$report") + } + } + + private data class Fixture( + val file: String, + val category: String, + val expected: String, + ) + + private data class Baseline( + val passed: Int, + val total: Int, + ) + + private fun decode(fixture: Fixture): Boolean { + val bitmap = + assets.open("$ASSET_DIR/${fixture.file}").use { + BitmapFactory.decodeStream(it, null, BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 }) + } ?: return false + + return try { + decoder.decode(bitmap, DecodeEffort.Thorough).any { it.text == fixture.expected } + } finally { + bitmap.recycle() + } + } + + private fun fixtures(): List = + assets.open("$ASSET_DIR/expected.tsv").bufferedReader().useLines { lines -> + lines + .filter { it.isNotBlank() } + .map { line -> + val (file, category, expected) = line.split('\t', limit = 3) + Fixture(file, category, expected) + }.toList() + } + + private fun baseline(): Map = + assets.open("$ASSET_DIR/baseline.tsv").bufferedReader().useLines { lines -> + lines.filter { it.isNotBlank() }.associate { line -> + val (category, passed, total) = line.split('\t', limit = 3) + category to Baseline(passed.toInt(), total.toInt()) + } + } + + companion object { + private const val ASSET_DIR = "qr" + } +} diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt new file mode 100644 index 0000000000..e442abe215 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import com.google.zxing.EncodeHintType +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.google.zxing.qrcode.encoder.Encoder +import java.awt.Color +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.awt.image.ConvolveOp +import java.awt.image.Kernel +import java.awt.image.RescaleOp +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.hypot +import kotlin.math.ln +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.math.sin +import kotlin.math.sqrt +import kotlin.random.Random + +/** + * Deterministic corpus of degraded QR codes, for measuring how well a decoder actually reads the + * codes people point phones at. + * + * Why it lives in the JVM test source set: it is pure `java.awt`, no Android, so the corpus can be + * generated and the *old* decoder (ZXing-Java) measured on any machine, with no device or + * emulator. The new decoder is native and Android-only, so it is measured by the instrumented + * `QrDecodeCorpusTest` against the exact same images, exported from here. + * + * Everything is seeded, so two runs produce byte-identical images and the two measurements are + * comparable. + */ +object QrCorpus { + /** A throwaway pubkey. Never a real key — these images get committed. */ + private const val PUBKEY = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c" + + /** + * The payload lengths that matter, shortest to longest. Length drives the QR version, which + * drives module size at a fixed physical size — and small modules, far more than error + * correction, are what defeats a camera at arm's length. + */ + private val PAYLOADS = + listOf( + "npub" to "nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq", + "nprofile" to "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq", + "nevent" to "nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq", + "njump" to "https://njump.to/npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq", + ) + + /** One generated image plus what it should decode to and which hazard it represents. */ + data class Fixture( + val name: String, + val category: String, + val expected: String, + val image: BufferedImage, + ) + + /** + * Every fixture, in a stable order. + * + * Categories are the *reasons* scans fail in the field, not arbitrary transforms: a code seen + * out of focus, off-axis, on a dim or glossy surface, photographed off another screen, or + * simply too far away to resolve. + */ + fun all(): List { + val fixtures = mutableListOf() + + PAYLOADS.forEach { (label, payload) -> + val (base, modules) = renderWithModuleCount(payload, moduleSize = 6) + + fun add( + category: String, + image: BufferedImage, + ) = fixtures.add(Fixture("$category-$label", category, payload, image)) + + add("clean", base) + add("blur", blur(base, radius = 3)) + add("blur-heavy", blur(base, radius = 6)) + add("tilt15", rotate(base, degrees = 15.0)) + add("tilt30", rotate(base, degrees = 30.0)) + add("tilt45", rotate(base, degrees = 45.0)) + add("perspective", perspective(base, strength = 0.28)) + add("low-contrast", contrast(base, scale = 0.30f)) + add("very-low-contrast", contrast(base, scale = 0.12f)) + add("inverted", invert(base)) + add("glare", glare(base)) + add("noise", noise(base, sigma = 46.0)) + add("moire", moire(base)) + // Sized by pixels-per-module, not absolute pixels: that ratio is what decides + // whether a code is resolvable at all, and it is the whole reason a long payload is + // harder to scan than a short one at the same physical size. + add("far-3px", shrinkInFrame(base, modules, pxPerModule = 3.0)) + add("far-2px", shrinkInFrame(base, modules, pxPerModule = 2.0)) + add("far-1.5px", shrinkInFrame(base, modules, pxPerModule = 1.5)) + } + + return fixtures + } + + /** Renders [payload] exactly the way `QrCodeDrawer` does: ECC level Q, 4-module quiet zone. */ + fun render( + payload: String, + moduleSize: Int, + ): BufferedImage = renderWithModuleCount(payload, moduleSize).first + + /** As [render], plus the symbol's module count — what pixels-per-module is measured against. */ + fun renderWithModuleCount( + payload: String, + moduleSize: Int, + ): Pair { + val code = + Encoder.encode( + payload, + ErrorCorrectionLevel.Q, + mapOf( + EncodeHintType.CHARACTER_SET to "UTF-8", + EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.Q, + ), + ) + val matrix = code.matrix!! + val quiet = 4 + val side = (matrix.width + quiet * 2) * moduleSize + + val image = BufferedImage(side, side, BufferedImage.TYPE_INT_RGB) + val g = image.createGraphics() + g.color = Color.WHITE + g.fillRect(0, 0, side, side) + g.color = Color.BLACK + for (y in 0 until matrix.height) { + for (x in 0 until matrix.width) { + if (matrix[x, y] == 1.toByte()) { + g.fillRect((x + quiet) * moduleSize, (y + quiet) * moduleSize, moduleSize, moduleSize) + } + } + } + g.dispose() + return image to matrix.width + } + + // ------------------------------------------------------------------ + // degradations + // ------------------------------------------------------------------ + + /** Out of focus — the single most common reason a frame fails to decode. */ + private fun blur( + source: BufferedImage, + radius: Int, + ): BufferedImage { + val size = radius * 2 + 1 + val weight = 1f / (size * size) + val kernel = Kernel(size, size, FloatArray(size * size) { weight }) + val padded = pad(source, radius) + val out = BufferedImage(padded.width, padded.height, BufferedImage.TYPE_INT_RGB) + ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null).filter(padded, out) + return out + } + + /** Held at an angle. */ + private fun rotate( + source: BufferedImage, + degrees: Double, + ): BufferedImage { + val radians = Math.toRadians(degrees) + val cos = abs(cos(radians)) + val sin = abs(sin(radians)) + val w = (source.width * cos + source.height * sin).roundToInt() + val h = (source.width * sin + source.height * cos).roundToInt() + + val out = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB) + val g = out.createGraphics() + g.color = Color.WHITE + g.fillRect(0, 0, w, h) + g.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR, + ) + g.translate(w / 2.0, h / 2.0) + g.rotate(radians) + g.translate(-source.width / 2.0, -source.height / 2.0) + g.drawImage(source, 0, 0, null) + g.dispose() + return out + } + + /** + * Seen off-axis — a code on a wall photographed from the side. A true projective warp, which + * [java.awt.geom.AffineTransform] cannot express, so it is mapped by hand. + */ + private fun perspective( + source: BufferedImage, + strength: Double, + ): BufferedImage { + val w = source.width + val h = source.height + val out = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB) + + for (y in 0 until h) { + // Rows further "away" sample a narrower slice, which is what makes modules shrink + // toward one edge exactly as a real off-axis photo does. + val t = y.toDouble() / (h - 1) + val squeeze = 1.0 - strength * (1.0 - t) + for (x in 0 until w) { + val centered = x - w / 2.0 + val sourceX = (centered / squeeze + w / 2.0).roundToInt() + val rgb = + if (sourceX in 0 until w) source.getRGB(sourceX, y) else WHITE_RGB + out.setRGB(x, y, rgb) + } + } + return out + } + + /** A dim screen, or e-ink, or a washed-out print: black and white move toward each other. */ + private fun contrast( + source: BufferedImage, + scale: Float, + ): BufferedImage { + val offset = 255f * (1f - scale) / 2f + val out = BufferedImage(source.width, source.height, BufferedImage.TYPE_INT_RGB) + RescaleOp(scale, offset, null).filter(source, out) + return out + } + + /** Light-on-dark, as a dark-mode client or an inverted print produces. */ + private fun invert(source: BufferedImage): BufferedImage { + val out = BufferedImage(source.width, source.height, BufferedImage.TYPE_INT_RGB) + for (y in 0 until source.height) { + for (x in 0 until source.width) { + out.setRGB(x, y, source.getRGB(x, y).inv() and 0xFFFFFF) + } + } + return out + } + + /** A highlight burning out one corner — glass, gloss, or a ceiling light. */ + private fun glare(source: BufferedImage): BufferedImage { + val out = BufferedImage(source.width, source.height, BufferedImage.TYPE_INT_RGB) + val cx = source.width * 0.68 + val cy = source.height * 0.30 + val radius = min(source.width, source.height) * 0.30 + + for (y in 0 until source.height) { + for (x in 0 until source.width) { + val distance = hypot(x - cx, y - cy) + val lift = if (distance >= radius) 0.0 else (1.0 - distance / radius) * 235.0 + out.setRGB(x, y, liftPixel(source.getRGB(x, y), lift)) + } + } + return out + } + + /** Sensor noise in poor light. */ + private fun noise( + source: BufferedImage, + sigma: Double, + ): BufferedImage { + val random = Random(SEED) + val out = BufferedImage(source.width, source.height, BufferedImage.TYPE_INT_RGB) + for (y in 0 until source.height) { + for (x in 0 until source.width) { + val delta = gaussian(random) * sigma + out.setRGB(x, y, liftPixel(source.getRGB(x, y), delta)) + } + } + return out + } + + /** Photographed off another screen: a faint scanline beat over the modules. */ + private fun moire(source: BufferedImage): BufferedImage { + val out = BufferedImage(source.width, source.height, BufferedImage.TYPE_INT_RGB) + for (y in 0 until source.height) { + val band = sin(y * 0.9) * 34.0 + for (x in 0 until source.width) { + out.setRGB(x, y, liftPixel(source.getRGB(x, y), band)) + } + } + return out + } + + /** + * Too far away: the symbol is scaled until each module is [pxPerModule] pixels across, + * then centred in a full-size frame. + * + * This is the category the old scanner could do nothing about, because it had no zoom — and + * the one auto-zoom exists for. + */ + private fun shrinkInFrame( + source: BufferedImage, + modules: Int, + pxPerModule: Double, + ): BufferedImage { + val targetPx = (modules * pxPerModule).roundToInt() + val frame = BufferedImage(source.width, source.height, BufferedImage.TYPE_INT_RGB) + val g = frame.createGraphics() + g.color = Color.WHITE + g.fillRect(0, 0, frame.width, frame.height) + g.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR, + ) + val x = (frame.width - targetPx) / 2 + val y = (frame.height - targetPx) / 2 + g.drawImage(source, x, y, targetPx, targetPx, null) + g.dispose() + return frame + } + + // ------------------------------------------------------------------ + // helpers + // ------------------------------------------------------------------ + + private const val SEED = 20260915L + private const val WHITE_RGB = 0xFFFFFF + + private fun pad( + source: BufferedImage, + margin: Int, + ): BufferedImage { + val out = BufferedImage(source.width + margin * 2, source.height + margin * 2, BufferedImage.TYPE_INT_RGB) + val g = out.createGraphics() + g.color = Color.WHITE + g.fillRect(0, 0, out.width, out.height) + g.drawImage(source, margin, margin, null) + g.dispose() + return out + } + + private fun liftPixel( + rgb: Int, + delta: Double, + ): Int { + fun channel(shift: Int): Int { + val value = (rgb shr shift) and 0xFF + return max(0, min(255, (value + delta).roundToInt())) + } + return (channel(16) shl 16) or (channel(8) shl 8) or channel(0) + } + + /** Box-Muller, so the noise is normally distributed rather than uniform. */ + private fun gaussian(random: Random): Double { + val u1 = random.nextDouble().coerceAtLeast(1e-12) + val u2 = random.nextDouble() + return sqrt(-2.0 * ln(u1)) * cos(2.0 * Math.PI * u2) + } +} diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt new file mode 100644 index 0000000000..9ccadfd116 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner + +import com.google.zxing.BarcodeFormat +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.LuminanceSource +import com.google.zxing.MultiFormatReader +import com.google.zxing.common.HybridBinarizer +import org.junit.Assert.assertEquals +import org.junit.Assume.assumeTrue +import org.junit.Test +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO + +/** + * Measures ZXing-Java — the decoder the old zxing-android-embedded scanner used — against + * [QrCorpus], and records the result as the baseline the new decoder has to beat. + * + * This is the half of the plan's phase 0 that needs no device. The other half + * (`QrDecodeCorpusTest`, instrumented) runs zxing-cpp over the exported images and asserts it + * does at least as well, category by category. + * + * **What this is and is not.** It measures *decoder quality on a still image*, and it is + * deliberately generous to the old decoder: it decodes the full uncropped image and retries + * inverted, where the shipped scanner cropped to a viewfinder rect and alternated inversion + * across frames. So a win here is a floor on the real-world improvement, not the whole of it. + */ +class QrCorpusBaselineTest { + @Test + fun cleanCodesAllDecode() { + // The one hard assertion. If a pristine, generously-sized code fails, the corpus itself + // is broken and every other number in this file is meaningless. + val clean = QrCorpus.all().filter { it.category == "clean" } + assertEquals("corpus should contain one clean fixture per payload", 4, clean.size) + clean.forEach { + assertEquals("clean fixture ${it.name} must decode", it.expected, decode(it.image)) + } + } + + @Test + fun reportsTheBaselinePerCategory() { + val byCategory = QrCorpus.all().groupBy { it.category } + + val rows = + byCategory.map { (category, fixtures) -> + val passed = fixtures.count { decode(it.image) == it.expected } + Row(category, passed, fixtures.size) + } + + println(render(rows)) + + // Recorded, not asserted: these numbers are the yardstick, and pinning them here would + // just mean a ZXing bump breaks the build instead of informing it. + if (System.getProperty(EXPORT_PROPERTY) == "true") { + writeBaseline(rows) + } + } + + /** + * Writes the corpus and its baseline into the instrumented test's assets. + * + * Opt-in via `-D$EXPORT_PROPERTY=true` so an ordinary test run never dirties the working + * tree. The generator is deterministic, so re-exporting an unchanged corpus is a no-op. + */ + @Test + fun exportsTheCorpusForTheInstrumentedTest() { + assumeTrue("set -D$EXPORT_PROPERTY=true to regenerate the corpus", System.getProperty(EXPORT_PROPERTY) == "true") + + val dir = File(ASSET_DIR) + dir.mkdirs() + + QrCorpus.all().forEach { fixture -> + ImageIO.write(fixture.image, "png", File(dir, "${fixture.name}.png")) + } + + File(dir, "expected.tsv").writeText( + QrCorpus.all().joinToString("\n", postfix = "\n") { "${it.name}.png\t${it.category}\t${it.expected}" }, + ) + } + + private data class Row( + val category: String, + val passed: Int, + val total: Int, + ) + + private fun render(rows: List): String = + buildString { + appendLine() + appendLine("ZXing-Java (the old scanner's decoder) over QrCorpus:") + rows.sortedBy { it.category }.forEach { + appendLine(" %-20s %d/%d".format(it.category, it.passed, it.total)) + } + val passed = rows.sumOf { it.passed } + val total = rows.sumOf { it.total } + appendLine(" %-20s %d/%d".format("TOTAL", passed, total)) + } + + private fun writeBaseline(rows: List) { + File(ASSET_DIR).mkdirs() + File(ASSET_DIR, BASELINE_FILE).writeText( + rows.sortedBy { it.category }.joinToString("\n", postfix = "\n") { "${it.category}\t${it.passed}\t${it.total}" }, + ) + } + + /** ZXing-Java, full frame, with an inverted retry — a deliberately generous baseline. */ + private fun decode(image: BufferedImage): String? { + val source = GrayLuminanceSource(image) + return tryDecode(source) ?: tryDecode(source.invert()) + } + + private fun tryDecode(source: LuminanceSource): String? = + try { + MultiFormatReader() + .apply { setHints(mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE))) } + .decodeWithState(BinaryBitmap(HybridBinarizer(source))) + .text + } catch (e: Exception) { + null + } + + /** + * A [LuminanceSource] over a [BufferedImage]. + * + * Hand-rolled rather than pulling in `com.google.zxing:javase` for twenty lines — a new + * dependency, even a permissive one, is not worth it here. + */ + private class GrayLuminanceSource( + image: BufferedImage, + ) : LuminanceSource(image.width, image.height) { + private val luminances = + ByteArray(image.width * image.height).also { out -> + for (y in 0 until image.height) { + for (x in 0 until image.width) { + val rgb = image.getRGB(x, y) + val r = (rgb shr 16) and 0xFF + val g = (rgb shr 8) and 0xFF + val b = rgb and 0xFF + // ITU-R BT.601 in integer arithmetic, matching ZXing's own conversion. + out[y * image.width + x] = ((r * 306 + g * 601 + b * 117) shr 10).toByte() + } + } + } + + override fun getRow( + y: Int, + row: ByteArray?, + ): ByteArray { + val out = if (row != null && row.size >= width) row else ByteArray(width) + System.arraycopy(luminances, y * width, out, 0, width) + return out + } + + override fun getMatrix(): ByteArray = luminances + + override fun isRotateSupported(): Boolean = false + + override fun isCropSupported(): Boolean = false + } + + companion object { + private const val EXPORT_PROPERTY = "qr.corpus.export" + private const val ASSET_DIR = "src/androidTest/assets/qr" + private const val BASELINE_FILE = "baseline.tsv" + } +} From db28e4ec4bd7bd3843a6d78569efd332a915d28c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 19:33:16 +0000 Subject: [PATCH 009/127] test(qr): measure the phase-0 baseline and commit the corpus Runs the corpus and records what the OLD decoder actually manages, which is the measurement the plan's phase 0 asked for and the decoder swap was resting on reasoning instead of. ZXing-Java over the corpus: 31/48. The informative part is where it scores zero. Perspective is a total loss -- an off-axis code, one of the most ordinary framings there is, was simply unreadable. So are half-a-module blur, very low contrast, and anything under two pixels per module. Tilt sits at 2/3 across 15, 30 and 45 degrees. Two things make 31/48 flattering to the old scanner, both noted in the plan: the baseline decodes the full image and retries inverted, where the shipped scanner cropped to a viewfinder rect and alternated inversion across frames; and it measures a decoder on a still, so it says nothing about focus, zoom or torch. A win measured here is a floor on the real difference, not the whole of it. Calibration matters for the gate, so the corpus is tuned to discriminate rather than to be hard: every degradation is a fraction of a module (changing the render scale cannot re-tune difficulty), and blur/distance each straddle the old decoder's limit. A category it fails outright can only ever show an improvement, never catch a regression. The export is wired through a Gradle property rather than -D, because the test runs in a forked JVM that does not inherit the Gradle JVM's system properties -- which is why the first export attempt silently skipped. Images are written as 8-bit grey, and render at 4px/module with three payloads, which brought the committed corpus from 2.6 MB to 776 KB. It lives in androidTest assets, so it never ships in the app. Also fixes the deep-link test from the previous commit: it asserted an un-encoded value round-trips, but a raw `&secret=` inside `dlnwc?value=` reads as a parameter of the outer uri and truncates it. Real deep links percent-encode the value, and so does the test now. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX --- amethyst/build.gradle.kts | 6 ++ .../plans/2026-09-15-qr-reader-overhaul.md | 58 +++++++++++++++++- .../src/androidTest/assets/qr/baseline.tsv | 16 +++++ .../assets/qr/blur-heavy-nevent.png | Bin 0 -> 11469 bytes .../assets/qr/blur-heavy-nprofile.png | Bin 0 -> 17282 bytes .../androidTest/assets/qr/blur-heavy-npub.png | Bin 0 -> 4921 bytes .../src/androidTest/assets/qr/blur-nevent.png | Bin 0 -> 7841 bytes .../androidTest/assets/qr/blur-nprofile.png | Bin 0 -> 12150 bytes .../src/androidTest/assets/qr/blur-npub.png | Bin 0 -> 3227 bytes .../androidTest/assets/qr/clean-nevent.png | Bin 0 -> 6230 bytes .../androidTest/assets/qr/clean-nprofile.png | Bin 0 -> 9212 bytes .../src/androidTest/assets/qr/clean-npub.png | Bin 0 -> 2761 bytes .../src/androidTest/assets/qr/expected.tsv | 48 +++++++++++++++ .../assets/qr/far-1.5px-nevent.png | Bin 0 -> 4166 bytes .../assets/qr/far-1.5px-nprofile.png | Bin 0 -> 6212 bytes .../androidTest/assets/qr/far-1.5px-npub.png | Bin 0 -> 1698 bytes .../androidTest/assets/qr/far-2px-nevent.png | Bin 0 -> 5207 bytes .../assets/qr/far-2px-nprofile.png | Bin 0 -> 7847 bytes .../androidTest/assets/qr/far-2px-npub.png | Bin 0 -> 1916 bytes .../androidTest/assets/qr/far-3px-nevent.png | Bin 0 -> 7993 bytes .../assets/qr/far-3px-nprofile.png | Bin 0 -> 12449 bytes .../androidTest/assets/qr/far-3px-npub.png | Bin 0 -> 3068 bytes .../androidTest/assets/qr/glare-nevent.png | Bin 0 -> 15416 bytes .../androidTest/assets/qr/glare-nprofile.png | Bin 0 -> 21051 bytes .../src/androidTest/assets/qr/glare-npub.png | Bin 0 -> 7015 bytes .../androidTest/assets/qr/inverted-nevent.png | Bin 0 -> 5791 bytes .../assets/qr/inverted-nprofile.png | Bin 0 -> 8699 bytes .../androidTest/assets/qr/inverted-npub.png | Bin 0 -> 2458 bytes .../assets/qr/low-contrast-nevent.png | Bin 0 -> 2650 bytes .../assets/qr/low-contrast-nprofile.png | Bin 0 -> 4827 bytes .../assets/qr/low-contrast-npub.png | Bin 0 -> 1337 bytes .../androidTest/assets/qr/moire-nevent.png | Bin 0 -> 6955 bytes .../androidTest/assets/qr/moire-nprofile.png | Bin 0 -> 10062 bytes .../src/androidTest/assets/qr/moire-npub.png | Bin 0 -> 3138 bytes .../androidTest/assets/qr/noise-nevent.png | Bin 0 -> 60717 bytes .../androidTest/assets/qr/noise-nprofile.png | Bin 0 -> 90236 bytes .../src/androidTest/assets/qr/noise-npub.png | Bin 0 -> 27278 bytes .../assets/qr/perspective-nevent.png | Bin 0 -> 6956 bytes .../assets/qr/perspective-nprofile.png | Bin 0 -> 10809 bytes .../assets/qr/perspective-npub.png | Bin 0 -> 2735 bytes .../androidTest/assets/qr/tilt15-nevent.png | Bin 0 -> 30101 bytes .../androidTest/assets/qr/tilt15-nprofile.png | Bin 0 -> 45364 bytes .../src/androidTest/assets/qr/tilt15-npub.png | Bin 0 -> 12684 bytes .../androidTest/assets/qr/tilt30-nevent.png | Bin 0 -> 28738 bytes .../androidTest/assets/qr/tilt30-nprofile.png | Bin 0 -> 43485 bytes .../src/androidTest/assets/qr/tilt30-npub.png | Bin 0 -> 12469 bytes .../androidTest/assets/qr/tilt45-nevent.png | Bin 0 -> 24891 bytes .../androidTest/assets/qr/tilt45-nprofile.png | Bin 0 -> 38272 bytes .../src/androidTest/assets/qr/tilt45-npub.png | Bin 0 -> 10219 bytes .../assets/qr/very-low-contrast-nevent.png | Bin 0 -> 2650 bytes .../assets/qr/very-low-contrast-nprofile.png | Bin 0 -> 4827 bytes .../assets/qr/very-low-contrast-npub.png | Bin 0 -> 1337 bytes .../qrcode/scanner/QrDecodeCorpusTest.kt | 3 +- .../amethyst/ui/UriToRouteTest.kt | 7 ++- .../loggedIn/qrcode/scanner/QrCorpus.kt | 19 ++++-- .../qrcode/scanner/QrCorpusBaselineTest.kt | 24 ++++++-- 56 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 amethyst/src/androidTest/assets/qr/baseline.tsv create mode 100644 amethyst/src/androidTest/assets/qr/blur-heavy-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/blur-heavy-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/blur-heavy-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/blur-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/blur-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/blur-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/clean-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/clean-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/clean-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/expected.tsv create mode 100644 amethyst/src/androidTest/assets/qr/far-1.5px-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/far-1.5px-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/far-1.5px-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/far-2px-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/far-2px-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/far-2px-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/far-3px-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/far-3px-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/far-3px-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/glare-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/glare-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/glare-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/inverted-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/inverted-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/inverted-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/low-contrast-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/low-contrast-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/low-contrast-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/moire-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/moire-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/moire-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/noise-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/noise-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/noise-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/perspective-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/perspective-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/perspective-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt15-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt15-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt15-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt30-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt30-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt30-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt45-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt45-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/tilt45-npub.png create mode 100644 amethyst/src/androidTest/assets/qr/very-low-contrast-nevent.png create mode 100644 amethyst/src/androidTest/assets/qr/very-low-contrast-nprofile.png create mode 100644 amethyst/src/androidTest/assets/qr/very-low-contrast-npub.png diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index 9e234da0b4..2277f7391d 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -317,6 +317,12 @@ android { project .findProperty("amethyst.arti.integration") ?.let { test.systemProperty("amethyst.arti.integration", it.toString()) } + // Opts QrCorpusBaselineTest into rewriting the QR decode corpus under + // src/androidTest/assets/qr. Off by default so an ordinary run never dirties + // the working tree; Gradle forks the test JVM, so -D alone would not reach it. + project + .findProperty("amethyst.qr.corpus.export") + ?.let { test.systemProperty("amethyst.qr.corpus.export", it.toString()) } } } } diff --git a/amethyst/plans/2026-09-15-qr-reader-overhaul.md b/amethyst/plans/2026-09-15-qr-reader-overhaul.md index 9f4c49b50d..96c78d9aab 100644 --- a/amethyst/plans/2026-09-15-qr-reader-overhaul.md +++ b/amethyst/plans/2026-09-15-qr-reader-overhaul.md @@ -310,7 +310,7 @@ Implemented in this branch: | Phase | State | | --- | --- | -| 0 — measurement corpus | **Not done.** No device or emulator in this environment, so the fixture corpus and `QrDecodeCorpusTest` could not be built *or run*, and none of the before/after decode-rate numbers that §1 and §2.1 call for exist yet. The gate it was supposed to provide — "drop zxing-cpp if it doesn't beat ZXing-Java" — has therefore not been exercised. | +| 0 — measurement corpus | **Half done.** The corpus and the ZXing-Java baseline are built and measured (see §8); the zxing-cpp half needs a device and is written but unrun, so the gate is armed rather than passed. | | 1 — in-app CameraX scanner | Done. | | 2 — per-frame decode quality | Done. | | 3 — explicit outcomes | Done. | @@ -347,3 +347,59 @@ Implemented in this branch: reasoned-through only. 3. **F-Droid packaging sign-off** on the new prebuilt `.so`, per §5. 4. **The ECC level question** in §5 — still open, and still wants the corpus to answer it. + +--- + +## 8. Phase 0: the baseline + +`QrCorpus` renders three payloads — an `npub`, an `nprofile` with relay hints, and an `nevent` +(69, ~330 and ~140 characters, so three different symbol versions) — at 4 pixels per module, then +degrades each one sixteen ways. Every degradation is expressed as a fraction of a *module*, so +changing the render scale cannot quietly re-tune the corpus's difficulty. + +`QrCorpusBaselineTest` measures **ZXing-Java**, the decoder the old zxing-android-embedded scanner +used, over that corpus on the JVM. Measured 2026-09-15: + +| category | ZXing-Java | what it stands for | +| --- | --- | --- | +| clean | 3/3 | sanity — if this ever fails the corpus is broken | +| blur | 3/3 | a quarter-module out of focus | +| blur-heavy | **0/3** | half a module out of focus | +| tilt15 / tilt30 / tilt45 | 2/3 each | held at an angle | +| perspective | **0/3** | seen off-axis — a code on a wall or table, photographed from the side | +| low-contrast | 3/3 | a dim screen | +| very-low-contrast | **0/3** | a very dim screen, or worn print | +| inverted | 3/3 | light-on-dark | +| glare | 3/3 | a highlight burning out one corner | +| moire | 3/3 | photographed off another screen | +| noise | 2/3 | sensor noise in poor light | +| far-3px | 3/3 | three pixels per module | +| far-2px | 2/3 | two pixels per module | +| far-1.5px | **0/3** | one and a half pixels per module | +| **TOTAL** | **31/48** | | + +Read it as a map of where the old reader gave up. **Perspective is a total loss** — an off-axis +code, one of the most ordinary framings there is, was simply unreadable. So are heavy blur, very +low contrast, and anything under two pixels per module. + +Two caveats on the number, both of which make 31/48 *flattering* to the old scanner: + +- It decodes the **full image** and retries **inverted**. The shipped scanner cropped to a + viewfinder rect and alternated inversion across frames, so it had strictly fewer chances. +- It measures a decoder on a still. It says nothing about focus, zoom or torch, which is where + most of this branch's other work went. + +So a win measured here is a floor on the real-world difference, not the whole of it. + +**The gate is not yet passed.** `QrDecodeCorpusTest` runs zxing-cpp over the identical committed +images and fails if it reads fewer in any category. It needs a device. Until someone runs it, the +decoder swap rests on the reasoning in §2.1 — this section just means the measurement is now one +command away instead of unbuilt. + +The corpus costs 776 KB in `amethyst/src/androidTest/assets/qr/` (test APK only, never shipped). +Regenerate it with: + +```bash +./gradlew :amethyst:testFdroidDebugUnitTest --tests '*QrCorpusBaselineTest*' \ + -Pamethyst.qr.corpus.export=true +``` diff --git a/amethyst/src/androidTest/assets/qr/baseline.tsv b/amethyst/src/androidTest/assets/qr/baseline.tsv new file mode 100644 index 0000000000..258e4f2397 --- /dev/null +++ b/amethyst/src/androidTest/assets/qr/baseline.tsv @@ -0,0 +1,16 @@ +blur 3 3 +blur-heavy 0 3 +clean 3 3 +far-1.5px 0 3 +far-2px 2 3 +far-3px 3 3 +glare 3 3 +inverted 3 3 +low-contrast 3 3 +moire 3 3 +noise 2 3 +perspective 0 3 +tilt15 2 3 +tilt30 2 3 +tilt45 2 3 +very-low-contrast 0 3 diff --git a/amethyst/src/androidTest/assets/qr/blur-heavy-nevent.png b/amethyst/src/androidTest/assets/qr/blur-heavy-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..e97301b636e77dcb41c954a6819890d666c5267c GIT binary patch literal 11469 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 X)2c73nvlnR%D1)kZhpLB74d%nqp7b& literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/blur-heavy-nprofile.png b/amethyst/src/androidTest/assets/qr/blur-heavy-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..493418b1943b396cbdcc5f73334a0179f214fb7d GIT binary patch literal 17282 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/blur-heavy-npub.png b/amethyst/src/androidTest/assets/qr/blur-heavy-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..93f957f537daddb1124b2a77a16f3ac9e6758a05 GIT binary patch literal 4921 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/blur-nevent.png b/amethyst/src/androidTest/assets/qr/blur-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..740559b130eac6333c6cc4e1aa7be763f602b3b7 GIT binary patch literal 7841 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 H9i#s@!sqf& literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/blur-nprofile.png b/amethyst/src/androidTest/assets/qr/blur-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..4c75240deee4415452ea6389ef7d638d0042ab52 GIT binary patch literal 12150 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/blur-npub.png b/amethyst/src/androidTest/assets/qr/blur-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..a948a0d920eadd5ece9f3bbf56661039b289feae GIT binary patch literal 3227 zcmZ8kc{mj68pm{OHRvd17=t;5EQPT(wrmlyWa7qu(s23u3g!?9J~}pLLwiUaR0om+u z>^`U_KB13k#-E&HF%9xJ+W@Bhl%b;awpGyBo(#wgi$QbeS%VcOoYKC)bCIwrSYtzqkV0VzFd4sE`og6t9;1$)M>jj5QT^?9+XZrlOn980GyPZP{?)|qv zp-=X*@w#c!bnhLPEPDX!RLpUu00iZ|Vfr}L7Ejk?+bnDTXxH2ju1NFlxgGeaMW+8a z0;Cp?EL!&ph|q|xG~+yN9B(q|(88ZRCk}mdT2UrG{)_5>40Nsmb)ctr6z2Ij#4)P~ z0CQ|!5OpW4$IAzcGgIf2EB)Vj(R@U0aSHG+agskq^#A?ydfVN4y>6#MC)1c%>Eok*w`%#2 zPB;b(svb=_@#*TZiFiVUD{|A14<8<8)D8y~UcLQ|?W&9DaaeQ+-D%*1``F0|eI~Vz zWhvbpucwbbwk+}G-79W&FWh2Fv`+ySVlA!R04t2l8NZp+8Z#%e6GsiM9R(TD*utv0 z!fK{n_biBz7~%Bp#oN~9BS$P$GbrUim_{C^v3N>%^D;6wF~HD#nGzRgt-G20F|wyE zT2i4@dvhO)6U}%9LlQ3~`b9_W5a_N27ygS|+8LHn1uAAOqi;vib)HA`X0W=xEx(3g z{1(6$I9eZY8p!~IhRtchL#LYl4*}zb-4)gu%_Wi#E5XH-CDZB1_0M<5ZNIge$+A_l1G50GLfg z#rg6RLwJ`fc#J$pMhhyx2Mda=Lt1AFoTnQOk6 zoM7V{-rRgeX`z{>S8T-o5zySqeM2Qr79^Yx=^lL7L=I&)I1qbEb3keMe7U)Y&Tbz0 z>!1N7nST_WwQL*xMX0ilZ=Z%oIL_AzTRKd>uuF1Wu_`UjeurV-WmauJYo3 zNqW^braW(waMzH9aet4|fZ2`+dZMvWyQV=urORlILo+8Tj|i{|IIo{)B=nVleWeq! zl*#Y?);lxaP*G)hRSZQbA<_t}IpD_AIoZ#i?4&wNRkatorQo_@lp5ta+h6{%zj=;c z?f?KQ^rdmb#C``V12RRWsxz;l2hZ*aqVvwvCnf0V>Z2tbmb0eL^vKTms82&otmICJ z_u{Yvv9Tix<@_+l4HQpmlqe40U{`sVbtUb!)J%@lR&f;g5DypsnHYm5BCjD3>}x7# z_Y%Lzc>IPM#P`*#+z)061tM6PN8ecgA;`_gW0rhmI8GWcMrQ>5Z!;L?=46<;YR6K^ z#h{Dp%X}G4k@a(X2Vr9y7h%lApI}fr#0X-kT=~}7*DwaHIm1HLlF?kB$) zRlKYr0WrR(DLRAU7G~*TcNI(GQX4o>CPHQphycBS|31?0Z)g&?P71df+vQraGu4MT zqZgt+o%xcei8pqG5d2{)RzX8U?5Tf&-79A8nPqd%M!OpIT7uokL|KS4$W3D2FgBeqz7`|wNr zbJ}s89r((fhB7KD>5HZQlSJ0&w7yr)ayz!cBRCZFS8(gg~hVawFunJ(QH0v>b`UHHl8iDI;t8y<@-v&&1lfNhBDbHiYqS4(JWX=^VlV(w$C(Em>ej zLthwgZ2T-w&vt}i=`rF{UN#W+Y89w#Lm9#jK2ld`=aral2I$kgS zX}Ac#MJyO~F{fVhxBqgrTqL63a1FCgTcV!jwatEmg$;@d1q+5g?Yc<&Dd;=E)b|tg zJa`i8_W?jVcN(?{{Whcu0uztyH{4Sb(Ne;xTr(cAYLJ$)RU+J==JcPwA9Hxm`o93g z3Hd?>N&pG^-cXnFXKyPp;BMW3+22L>S3}$g}SGfDX%@+n)Bx-{3@% t8`Wh%e8>N-`3>;yJ^#}ZG>hW-g0Bar3Z9gtgATh6JL{`f)fPUF{sUyWd4B)^ literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/clean-nevent.png b/amethyst/src/androidTest/assets/qr/clean-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f8e182219ff6272070cb3713916d6217ffe62d GIT binary patch literal 6230 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/clean-nprofile.png b/amethyst/src/androidTest/assets/qr/clean-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..e001e8123d96c90339d7107fd64505d86723a306 GIT binary patch literal 9212 zcmcI~dt6%g{eHJJO|#6{%q?jy$Wk-;W>a$^0Tq$8X=7Xz+GXpOT5D1jYP}$0I3gFD zOJki0Hj+jO7r$9DX|`yh7!gtMl7tWtl8~v$0iq(r2nQ7f5zrs`>-YNpxxTtTc)iYx z=RD`~Ip_1dpXdEKH6P;BB7U~*XRp2XT15Jh_y73XYd^_*^?Nh?$7}mbql2%#7BiUs z{=0vw_{{Zfe|?Os{+@lcs&e@IZSHfwk0k!nRV+Mr^l%^cduZ8V-LvWr=FUx3>3wy< z4b0Vt%Dx}A;Z0j{eC2WW6blc}w3Ah2Vr4xCx%`%!L*m|QJB6n7(P@@W{Pj~t=_DG7 zO;>GYeFLZ@>BFPl{KV%e7;={QNw?E9$LEP|OkPO6C!aL#Dl9T*?kE)rr7eapvc0aQ zp$x)P%3=6~d9JAbU?&v&hk|#Tcy&ZCw6ZW%1aA=T%@z684M4vP6CXL`{`=+43&EXw zi@8a`b4|8K5w%F1e?B1*5Kmq6XJ3ALMuf8&l%SG~fk0Il#^@H8Q7X38u2e4>dMQ})z?-g z8%;y&^*q8-FJZgWDB&v6{te@YZbd)5s-)0Du+GLJmis6>$9q_SLa zgVrqcCPI5iZ4W8AFY~35uGn3<)mE;g4k_CQt+hjV#tvVD8o2Jx#wu zW+DB3Daxo=2gcjOkAEJMWb=^)K%g&WRI1=HPl&ARxgHq9GPGky^t|?VAi{c34}4OW zlx?>+W1(`>{B+?zKb`%^E%KjIj?3+S{LDVF1_V1)!ji01R_1pbQeT>pN(jvyNwF4S z?d3c^-`iy<)`)N4EbC9}W!OhT=iOHwm=={Ffl4(*1Kq{`g@gQ}x_x}_u`9yF-0H2K zf=D(ZakAln^ndkyh$yX+Wj_B2T<~9mJD!f8Oont-76W_2*{_~#0mFJ8L!r5ZEM{7>qswDI7pIOJAT^JouxR`_V z80i1Z^3#4KAPxu)3g#m~8c*e_SWbeDMwE z$AnMJs!s_T^>#w7X)H4SS8QpKNwG@QX@!~ z=`V?Gv;EUq@UN*5-?&-Ju^vx%T4={`E%^T0U>xF=_^7_?}ry~c9Hgoa`Z-V@G@_AtuHGI zvL%RQBuRRM{|I$g5@Rk{sY{&_Ycr1bSg)2MfH^IepV_TVyBec5);dP0-rcx-;-iVs z@HvdEmIN7#_UA`e=qxGJiIfbXb49eHH(kF=KGUg9kQ>iTZ*p)+ctQ0|uGz}Hj-KY_ z0p=^le00`c>;!XOTjSY#sy)CcTZqMSovo(*awl;^bDDr4N}@zJ<7rg{j^zfa-h3*H z*kCStmvJstBB!(*y}E(PPjLwp(80#LMk&A;wf*+I9Vy>F(tShLCsiAC(=(r)Jtu)&&|TvJvdX+Qo2`Ncb_2t9nklF$p1_Sk^yTgmqM-){kjniq zz$pfg^G(H`uvX6YFL%<%#CuLQaTuQtVkhx(M-g6_xUAK_*N{AahdDy@lk{QI&e8(#!VXZTH3I*Y`i+Y=Io%>lgxOB*c|$$4kXY}F>;y@k5A z9|yYm6Szh?6k$aLqkAzsEiwQ=0BF-)ppo!O>X%9t#U)Ff^>eyjIYuTow)PG( z$8vO^_g8z0se{r8WttoR4FH+VYV#1y1+yDg$~JFE6_EMc6~#-~O5nX7svE*XC7p%t zy-nT*$(t_K%$k8-d%Lt~UhB!1=9lXx-*h{jStxJZPc=;2m!EE>+?F}x`6(Wzv3RS{ zlQ@9E1g>1f*7og(%eMXHFj$zt(8TIfa%fqH9VwhyolcRTtAFQ?Hm&u+cbj}yHI{ZY zQr}>{@+7G)rbi+n8%IsA|6wEwBMw%&V7_mtkjNkHh;n8~L8R#)rrFHVpI!;QnKU(0 z=ioV$%sy_ms$(U_MVC*nG#C_HeiIGlmK~A&`?4D*x*kL%dH*n?^CCI={-t zy@&TNj$xop4iM|X>JX#65*exUsC1nF;hJcFW57H)&h@2uI>&Fh*tHmWxpSLO99Lem z24054W~9>fNb+{w7$ohI996Yasvf=9kT|>3XBf2oC!~ScY^`n&hEO({s#JKiL9&u} zNWeOQVi~7a%J^?{mhv6xC7@#j5b;)BXzDkTNNw{TIha!tHMmLXOi^D91ebM4k1|Bu zNb#OFF61`^G{v7MPBaJ+GD=g9l}=odTD3cMsbQ^lmQ^Lyu7%HT^4XwsSL7P7*N{4n zr?E|0#dV(G4c~CvUj>{l1c2xOv+a=3yB=uwm@Hp11P(;6lyiTg{2o?@xi$su2izTm zF0Rn({n#{_f}8F=6}MR&1F6mHZRG&yg8e}+EIdgIfb!|xbD}Ogogk8u>1cSz8VfFfeOH zQtbzrziCRE<2c8=$7_I<8a?K;&fM4Iq6M!e%)O~x2+MopKemf198F6-Q7^yo35JK~_w*Hy}@ zJO?$XD<C;kY zo=DB+SN@keYrRzGKS2cQ@9s%TVh=Jr>xiPPI)w`o30Q)yXFu53l+tlxPlxV+yovP^ z%ac$=^Y#=HS;n~bs4o`>{+Q5F?9 zltTmyRVAJ7N*jwnmd(>@7q|gaR`r@}#VZn7Kw#W4HI)lB<7_&jFFupJPE^8}j5 zQ(>gJb$j(X^2}b}NYVZ}|F_lxk)_MCYb&Nltxd7WV2pEg8au06rX+Id7uK(8>8d2`ViF)?|v$`#||*nW8NuM^)Y# zf<}`H6GEjg>A1y<9?-OX_3(^p-|gPa7yT%CuH%+$VeqCS^bD<|lF7mCEpSzjH|`9) zF}ffL@OG`E-PVMpFN74rZORDW((Xt-mHyoZI2F+U7f^NVF_e0A4op5L3ly`@$Yl z!hSKY1;}>a*)q{&gC1PU>{K~l$gHf3yqdj#YBdI=$uQ}Vd3eV9rJ$eA<^ z*O4qohAT%FH!^mvq(db;rrY1r-ax`B%8g?e6AxbaRLj)cP1^|Ub8$+rIkXDtAD$NeK`{J zueVZ~6cIF#(Jp29UNriLVap#*4b;t_#^A;=kU`qdVGz7DcQg!X?LGuYO}_&tCm`E= z*)cLJ0?H?@CV23;q2^vf{pxh_d;g#vY2b|pS}%d_DBpfbRRdkzL^N0s>eU6*wLfBR zXfm9vkt2|=hQTu`H9ZD`q`=L!RaNpoya{`nAL~^V?(zlh^=h5ge9N@h*x^f`$Avvy zJA9uQqjLskWU}49EK0m5q9Bw=;x+Bv?0>izB{aZD-q0clUJkT$UGbmqU<)K#!rQe> z+j5O=nu83iRF;%H3Vr;khI!}{obI?Cap8m1ve5gEauttNg+-&M*uQJdSL|$oJK{W1 zofFe-PJ6fFi{o;2hBX+yjKnzvXiL1guvrD<6&g|tf2McZ^KF3G|u z$-x9V4MuMK^LH`!)I~g|S8{m1ysK%48!NpnWSOR&>bK)6QdsHBH~d|Oa6GS;TY23< zi>#pYI)eXe!}VbOLb`_{zI_tf2q=-K{Jm5|Q5pgDZgZOdH`84EBtB;lgh6c5%Xx;Q z*~k$Uoyn-1Kl&6gq!KF5%P?R__iW0i%ap*d#(SSve#J@DWVk_|qqDjv%4uHIiKv4m z-YY81E9V)>{AHRTzZrnERMR2LdM{K-(#uYw8JbC@wF*_v;Lx zSKY6~pc8@>lh@|SX0fqS-@ zg(XFT#)2i?Ue5@u5_mBh9-jNt-@9lxdI$O5MFF;yBp$~MYIA@W=4-jDQ*qnaMg3YN zMweyhz+|6wt=8EKRS8s}aqcMiFIeqi&U&QYnMmegXtv7nkkq+!H@88C(JuW4`Zwo; z7}Yr*i6)S0VVJVs*_A9>aI8%O!id3N_`X#A{Zj70|37fWYCMMR8qJ3`BH1;JySZ&A zjMbK=Z?{jxQ3GRi9aJ)ib}qM`a%FhFVJD{vt z3!gjtpVKyc*KSfh!A*;nq0-2fCf|{n`J+pCA_R5bCbg$;G39E<>IB_l1qM7cIgjl@JS7Hp*K67{O%nKKt8Kwx)W?qO703wHqCl(2B^9MH1#kP&$uHw31)@6G{FP4Ecl;wG@EC~A!s^f^UX zE9Fu@Y6F!d@&bl*S{VPUEid$^@rqy!=EHcJ=#C(_E^EZgPzkYii`hD0PPBYD`ul zY?P$O{nJJ33DKmjx&z$I&UI@rFtB@v-Cio07{f6A>rHOqHW8cF}Ko@jEXL+#~Ex{_a1Xg{? z9Zd-tsH`h`ry~aXAw=g-KVY+%j~ci8vi9hV<%NX$OBFOQ)4tJR-fN=qhNLp*L8T?FZEJ&@*rA8J zb4x&SZz9Rif=xd>VWgmW7y&>0P;j<&&2dPjD1t08So3$n>`?om(IM#~ZfJEO+^>EF z1rK;;jI$_AD0}nMlTZTYoSB)mY6ueapZ8yP>IgrG`p(j0Ds4a~L>?RI*sgOL_vJ#o z)^P9pI@Eod=eZ)w6?k;yd*SW_)rV59$L5I^nmMF!-Iy#X%dKw%u7-Z2)!Nh_ zKfitv(R*{!7%n$me6TUkDI`{{MzDJLb2%Btqid-XF!`FDq5R5 zR`HadEDG0|I=T8{`U6XWw_z`mi^%G)o8P2A%_uWeas$6yIbUE=^3188*U7GjvB$z! z{$n9W8m7LNRaAvrY0-@m^bn0M?k}NLLR_CV6&z(+U7{YOLo0BMZgeN>$^%+Zi6h=s}SkFs3*V&z!;aFL4 zhc6TbY*^|leNmY3XS*)#VyHlmp$JTPC#zwPbrxKv7=?G`H;$ErWk2O>L-49!njC+xQ!_A>Iwem(7?%_)uh6MbU&W@wJk{(E?% zKkVW`(_O73J0>X*zv0iKF7}sIL2lgRb;PhyPw>&=Jw+o&bZpIg-yvF?#^9^FHY}Xx zDMfruZ?-1Ibg*7xZNH6a0+!piI0dTv1nYy%wrhvy4MQCUf{sD# zUhl)_c+UtuT}#uZ(K9!}Xh~N1X@UmB*VE1UE8%2(QZIMEa95Irnc?;Ny37RJ=YlDb zC?Ny=N|}A@85UMr)d*vzD7@CsEGqMFnvC-;l%MP_;3=Il!l(zrEXyrfq^N&%JkRB7 z>fR>F#NrKx%vS=<8V~{2C)Pzva<~P1HBy}7e#23Fb5eE*46P4OQk1-j`3|dH{Y%y; zGjWQJPRz-G=`Y$K!mN(mVGP-#rn#BTF9z*{yY=#Z|K~3&N_GKNX8`u~AqdfQq^W5C z2XGV|Pq#$qwj5JYV`PD-9?$k-8qWXzavpd0@*Ng#NK9v84Svo&PoaaySiW{Di#)Cy z5Z({ZypsX;Rf1=^ve^Txag6AYzJ)_Got6RN*O=N@C-sI7G}#vyGaQ&-=x+;2&~){1 zaqrid4Zvo8#*=DSpdfW`oQPh=3EQ4 zP_tLx(#Ii@6PA0M?R|A-I;mgU;<(^89vs~OW;wc7c8ZP@iT8SI;$N!I(Dh{yD>WI5?LV9H#2V7qw2 zfnBml+!Jp~ZaOx?jJ5#MeTRD070b+>KYYg%&2$?9$~ogRYBlFi{;4X>5BrAZ|FDAa ZJIZI`8CpTrtIvAUKfu4=`re5%{|}vL{=NVJ literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/clean-npub.png b/amethyst/src/androidTest/assets/qr/clean-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..44f757dba1375eb865ef9b63f0c9775bc5b04f41 GIT binary patch literal 2761 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 O^^A9b*W=Hlv;PA(NYa!5 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/expected.tsv b/amethyst/src/androidTest/assets/qr/expected.tsv new file mode 100644 index 0000000000..733a533f41 --- /dev/null +++ b/amethyst/src/androidTest/assets/qr/expected.tsv @@ -0,0 +1,48 @@ +clean-npub.png clean nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +blur-npub.png blur nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +blur-heavy-npub.png blur-heavy nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +tilt15-npub.png tilt15 nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +tilt30-npub.png tilt30 nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +tilt45-npub.png tilt45 nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +perspective-npub.png perspective nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +low-contrast-npub.png low-contrast nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +very-low-contrast-npub.png very-low-contrast nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +inverted-npub.png inverted nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +glare-npub.png glare nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +noise-npub.png noise nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +moire-npub.png moire nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +far-3px-npub.png far-3px nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +far-2px-npub.png far-2px nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +far-1.5px-npub.png far-1.5px nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq +clean-nprofile.png clean nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +blur-nprofile.png blur nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +blur-heavy-nprofile.png blur-heavy nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +tilt15-nprofile.png tilt15 nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +tilt30-nprofile.png tilt30 nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +tilt45-nprofile.png tilt45 nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +perspective-nprofile.png perspective nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +low-contrast-nprofile.png low-contrast nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +very-low-contrast-nprofile.png very-low-contrast nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +inverted-nprofile.png inverted nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +glare-nprofile.png glare nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +noise-nprofile.png noise nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +moire-nprofile.png moire nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +far-3px-nprofile.png far-3px nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +far-2px-nprofile.png far-2px nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +far-1.5px-nprofile.png far-1.5px nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq +clean-nevent.png clean nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +blur-nevent.png blur nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +blur-heavy-nevent.png blur-heavy nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +tilt15-nevent.png tilt15 nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +tilt30-nevent.png tilt30 nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +tilt45-nevent.png tilt45 nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +perspective-nevent.png perspective nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +low-contrast-nevent.png low-contrast nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +very-low-contrast-nevent.png very-low-contrast nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +inverted-nevent.png inverted nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +glare-nevent.png glare nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +noise-nevent.png noise nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +moire-nevent.png moire nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +far-3px-nevent.png far-3px nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +far-2px-nevent.png far-2px nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq +far-1.5px-nevent.png far-1.5px nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq diff --git a/amethyst/src/androidTest/assets/qr/far-1.5px-nevent.png b/amethyst/src/androidTest/assets/qr/far-1.5px-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..9c5cb21fae6ce75da4f6ae922ebe282679dc7419 GIT binary patch literal 4166 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/far-1.5px-npub.png b/amethyst/src/androidTest/assets/qr/far-1.5px-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..9c3a9529b04cec23eb7aa450a45683778664b6ec GIT binary patch literal 1698 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/far-3px-nprofile.png b/amethyst/src/androidTest/assets/qr/far-3px-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..94e109453cfe9a7b7aa0593a11dcadeda5cbde7e GIT binary patch literal 12449 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 F{67q1n*;y= literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/far-3px-npub.png b/amethyst/src/androidTest/assets/qr/far-3px-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..9d6327047bdc3b2722bc8ac32abe40ea9b13e0fa GIT binary patch literal 3068 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/glare-nevent.png b/amethyst/src/androidTest/assets/qr/glare-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..9acda03a4b80840729d32cd617ae97649f86810f GIT binary patch literal 15416 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 FKL9yk6$k(T literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/glare-nprofile.png b/amethyst/src/androidTest/assets/qr/glare-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..1b7a565b29be162511613cc4c9947f9baefb9c4d GIT binary patch literal 21051 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 bcZ?eCIkV#Jnp~ekLR4Ht^1Gboa=n9&-a|` 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 Ke>t3d@qYkg2BAFw literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/inverted-nprofile.png b/amethyst/src/androidTest/assets/qr/inverted-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5ef50660a3f3c0f639903bee83db71248bdb5d GIT binary patch literal 8699 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 Bv-ki2 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/inverted-npub.png b/amethyst/src/androidTest/assets/qr/inverted-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed3a1012dfc84e729bbe2fddcacbf00f479f0b1 GIT binary patch literal 2458 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/low-contrast-nprofile.png b/amethyst/src/androidTest/assets/qr/low-contrast-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..1d46beaac4dd04e557126ef23910cc0cd82ffd95 GIT binary patch literal 4827 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 NB0QQ}_tlx){{i$9%D4ak literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/moire-nevent.png b/amethyst/src/androidTest/assets/qr/moire-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..f69f39c267ce4076e23efdd5cb7b92df3236410a GIT binary patch literal 6955 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/moire-npub.png b/amethyst/src/androidTest/assets/qr/moire-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd8553f4f3858ca1288c787accbed8e2af7a10b GIT binary patch literal 3138 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

    hp*2&h1F!TtTGVJmC2(E`8CScdjC!7nY5qY(zw* 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/tilt30-nprofile.png b/amethyst/src/androidTest/assets/qr/tilt30-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..d650c7a38288bbf5fa664a45793e9349557ce55a GIT binary patch literal 43485 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

    ITs}_*u6bHtK98Hyc}+jksH(4vjK^by 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/tilt30-npub.png b/amethyst/src/androidTest/assets/qr/tilt30-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..8bad92980abc5e4bf3f3458ad0e9e461c85ac55d GIT binary patch literal 12469 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 z zAf&yK-pRXk13@@IT-th=?ATy%b0CbH;4Rhv5!CdeebWqzT3m^GuZ22+jCn{fH{}BuhgY7RNoTx&A>!+wpOFTu6a&E!=Asr$H-okA6rV8m+SH+JgtHk}slwlu_erc?!AoQPpU+_=Qyt@f zY9{#Gf4;=6MF|>~bC2E(9N20xi}SbddUj-YrmhQ#5;wNmAIztm>^$xBn=rYxun7&W z9FzejnT6M&^RbhQYT^G^+j;*}{fB+L?7deU$0+k_m3450R5lr9%ShsgV;>~jAvsdE zL$;K?cN}p_$;^m2hpen~$cV=McHe)){e#~)kI(0v&v{?h^}3#~7gd0UQ?InlV2QJQkpR{uVr0R^yyXUKF;-)W6X%-U*?X$pNL;dgzGG1T&b@Kw)H!BJav>;I9 z9PmHS;%ZiW!xE4-a?kk~n-859&jCgFVHST@!;CnpOQlkT#?1{VT>M^|7`4euw zLSH}wx!_v(LHVTwOBhgm|J}bd!~Z46>){oLjr|IMZ{H-ZynT1R?;`R#cir;7-YtZQ zgmV}p(cwws+JA~9s=u{+g5G@-#lDWQ=A=4d;gdj0%n9nN>j{(QFTXPhS#fRR>3HF? ztZ{C-3}}MaVtFI=oOr{TDX9`p=_~@pvS8RG9NRQmUZ;S5(2g; z?&1Km$dj57+l60Mv)-a7w8^GE1M}BZ#vK$*|0ICjZ~<>-na4VlHW_vZml?cFi1h<1 zv3T?{IO8tFX{|y)H_UpUqeCIijl6}(vC)pH>~rh%HRSzYSWgBLRIq&hWm|~r zh6}D$)Y`(-|E3vf^O$m#*h{W;QGagmz?$jbzztCuiubFYQ7X69UH%pz@=L!W1)tEw+m?sb&pk=^;FD;*h@lyj5%J^2dy23L`65b1EHY z$Jm3QsydaJs1&OhTBAr`xOg(}#kTQhG>$egWx{MQTa@up%EQta-CLlOFK$|w1WpG_ z%)Vg&EWg8)nZ5%FUDGSIEnU*^8g4PHF8^h?%ZP3KMX)}89-6&VPA`T+4bJyj z@8kWE#ff2otZ%=~jr8klN}x)IY`l<$X&9D?1U?U9&Q5T}#)pT4vC2g)mUudf+HBcq zV1V#`7-uRN{UTnEk;3E0`heww(&7%)Q^S%p>5@gaf>3|t+6{rLkoJa+9f2kh2FNay zp^UcT=f?K|N^3g+wuBcsx%SlzE{DOFOJr|Rc{lT#$ zO#Q>Su zFTc*Gi)*O*ZNw|3lBNwCIIW$ArvhlgyFKQRlpe{QW$nG815&DTc6UNQ1I$e1{Uz&` zl;#C~2;vR(g>Uoai+9>hn(Vua(Y4Pc+1C~ z5Hs4#{sw4sV`w;9iC)g@K+Hu(}F%f^qBYp6JGQ=*1 z)A9g1J>q2`zuk9HLO#Uzv&OKu6P^7>qRM9gQM@ZAIjc!#pt(84kqRUgscM}x44ookwsWFR@S+^>> zGM!TLR?B8MOY>T}tgm*v39h+fr;+ji(NC9kSM5~%X01|0XTy^#pf*t<=P91&z_@2S zqp%#%e}{%ok7@>z{Hb4zvW+|`^-t!|AmcK`LB4>v4nH_X*B5$|fe3~^_9{C8*shZ9=0REkw&U>!|;3#_cb{ zI$j!W{C)576i*;fp{OvekK9_rK?2K{VPR~^Z`luYg^z_uyr(9;U0e=6{F)yu*yvFO((>iwQ>_XM7_LO&S+%%B*R`^RR>#Odou|+3G3N0=#aKn6F zf4N7Fx!?E(Hb%hVkp>zygkLe?Ppg??^n&v`(Y10KFn0r5)F+E0I7&2xbNc#%+t6gO z>qUnLUE%jHQUK!A1VSN8x24(mrZ&NU6Umoi1(s0$t^K#Zj3U6O1P`P$u5D_s_7=Tg zfRnW|RUCS5`rERTRmTZ_$kHhSoz^WRYg1E!5DAm~gY%GNK=3E@XDkGWIRTYSr#2ec z8mN$a%Tn?%cO$))Yqp zPe|i*lpk+~0I}+2v^k0Nt|`IvE=^6J2zz+oiTKhdyQ9-=!w#Rh;_DzwMu~;)mlXLNrjo7-_hLY#Fhg9U0fY=qI$)ri0*({EJM(A7hJ>-}#oO7IHp#lZ z;^_X&4VkfDRfzP^bhUge>i#D6rIu-JLZNXA9hnX?QZENQ1NV79hl9|IDt#0p zk?9PM07&arUP$ejNf)@g!}@}}t)86itvsGi5xOC2v)KuHQeE1uOu>c>1K_9~t>=L& zB^=P)q)S0JU0D|yMfKqOow&*=c0DVS{~Tms?Vf^+*~7X|7%!wAqg07@NS0Vkxh3Z|f|&F3B`jdk8gCX5Mi zx=0IjMWh1He38wPL?yZH&bgGe|FRWppF?Bh3}E7ouWl-w}iRnCA`KYev zePuFR5yJq`g%Ur0R3AdKko6b&FSbRpkoa`YxeS{X|B|!qqJwwWMNuVJh^Ej^LpXgt zmN@SU)L&I3iyMRuL15XFnS=_4ZCxwv(^0kk2gydyH~M4 zxXc$C`W_>2%(di+qugU}*kmx!gS>r03~jMBF5Ma;c-{?^wbI+L559`|HLXx>;B*x# zcaa!=QvXCk)ojz?V&^La&lWV@w+K?w1zg(-S~GH5OrF#t(4^9}gc;=3^f|8X-_o2i z=lu}Zl-qI<2ZC#w@D{%90gm#rttbG3IelVPF_g=a^Z=Pv`};U49%$3e+xGDwl^}ov z)?xJX><;)JYF@@7g*NvR{mc8^?{RSYJB8p2nJ35xF1cdD45?M#$%!8K#wVrx9gAm?i4_i8 z0`fz7m_ADDFnh{Bwli(-z>`}z<@8`vTKi+#Un)QVq|Fz}I{mppi;i^8|8|1if2o4x zVn87d95q7VRFP(#b;2T1{mr%o<(njY$IV zL_L1^XN3dkGMhrJ!$i^v^R?<#!$W>jlSObM3mnQWU{K5W*m#A44UzN=}t!Ri~C3AIPsvRY#L_=#~kP%YSKNoyWl#w-k z4mlN``9$mq-+=!-gZ1uMj_mg5?-U#<=r1NqWJpYeY<^v4##Q-te=O#@T1{68#ReDS z2r_dyvtw9VuO8Bh%UG`3z6AkeV%E_ESl`tt4sLKHY`jy7q-N!PHQ*2>f?D`~zJT&B zilRixvA%EF_RyLf`zDx?Rq=6j30ezNnTq~eBC)o-W!~NWF*DAXsm5=)OJ1WNn2%6 zvS?Si-g(*FL2kQEB9WoAgm{KM?bKD9Gk_i8`GR*lZPQ)ht}qr5L?S2bC&0PV`+^_F zt?!``iJTg_+SfGs>MpnHWe;9>AmS11$oXrr%3$yz_gFhzp#J2}i*X~;Z~1ber8U`) zEJ+gK(~?WSE-yw>B)siOAj?0x8Q)i_t!`|@OBI4u^;?0qs@DjFF+YL@zp+;0wzI_B z6)Rkg@O0kPq(B2`4GR4bu+zKuCVzO|w2PY-yl;~5_W=z6_)!E$zs#9i#m#Lbpb>}< zYXEeSJlnM4djSh$SHe`l5NBBwxTFOJoQ0kFkf$iEF$~*H!nmSWMcUZM9Gc-7o%idk`~M^wZ$={wZb%F1ixHqPAa7^eX{--P z;mYGdDU$Hj7UvfH$)MXUpBKW+Rt1e6VXp3T3_ceX8 z{eqR1=&jA6{v3%SzP;ZLEGU749M6~lhvf+5LLe-d!h(o~wdXBb({SS`#qxyza z^b3CVC{D)@-wqo{fTmylx=XZ4jTf_vP_EUiioB0?>wZlM42KUbIT}!}c~0gtVR-e| z$qrc`Sq_#egWC>2@lF>mT@(wX!uVC_0^m-hfcJ;UNDhc^GB8s7Tz@A<7$LMdQ|)c( zxxw+aFv^Ki$W&23_Sep#Ava{BP#0!Ghn9uKtf^-ATz9*1XNVqz*MT(HIlKK(E5wCQ zuZI)tfZuI0P5BoO2DdK^O!-t$32bEH%$=A2Le)v7v6!Y9=RYRwW#>=lq{@QWFoYaB zmx!MuC{6JAdAsA9w(V?l;NvpjRFAMC|38nFvL0#FYr+=_QVh}0A z@lBV=z;{#YQ>rcyU6{8q%=8hjZnfa@e$9%Sd@RwshNiNg>Oc{-7k=al?+xLB%goS2 z$(UV8tVmL+B)ny9I<#uiF(st7f_SjN1)2w9nSvQnr5pv?Xz}$Sua;g3?#{R@U zF3jvtmP5s7!6eyK3+|1Qvt*Gk09-&0KEx${TmsbJ3&vM3V^#1%T<@fu9W23~Iba5__3M`0`!@!ycI zzz2@&(i?oB)j6PQgti-_98)uV#vpcEtX%X zd6B3_SsU%=J^GjPkLTk0YhIm#tu#aPA&1s|wW990#ydtWt&9b{m$~5{C1?U)>q{Q^ zkxLj#^6vW;aVtdS;0ssv&CNoHDtqY2m;ey{)dULc|92{DvDC{HWPon;kRh95KC0JY zRMI1Ue$c`>(4qnSd0c~^xWoSsWCo2fq(H1D&9EQ*eu6Gs zD*lC&dU#Id{L><8e2oSD0BKg9cqzrGl2GZ@*K^`5s4@%J%kM-@f;z zher;t(;M_<1%bun;ERv-v!}{IqJ4@qn1jw~ZGM@fqe;J^VOcC|r|&3*)}qfr?q_{@ z!B|?aVM4Sp9Z7h^HU=7#d0I#H{T&K7^=dt#q|VnOMPdLzUyqlxJmjP;a#20nA5Zol z(Wr`_q=)Zat~jK zmX-UIQ=+Y@&Ar6V?1-|W`EhAFzRGepQXG;CI{#5v2%5>1DFACRn(Q^-byNLw2W7!5 zu>v-Vv^xd_!n-%nzHD07)L;+J-GOKR;rz}+!J7~G#>WRQ7{4*MG3t|RPV-mqoggZ} zRfSBO-~DK-THndM8?Wkj+p}^g;L0&>@rQDCTCh6V%+`BT2xlPY)Fcuq>Odiia$+RB zYWiuZe@*eY;T);705iWYdGQG5TxX`KS`eny8X`Ovz9Ntvl5+q%VKqFdp*KE*6-`r+Y z??0@>Ec|WZWLlD9gHh{(fU?$NC6s@!!TbTQCY2hAivf!(;;a>gs1FHzHtenZOI02z z&R!!H8gtmFgi`8jLU*RET(;jeh~Q!o9OTodY`n~VO_lrW+>m%KgWGh3WumzaNW@-U zi&`dGNyFlAto@^l32OPO;YSEH-t?o!5?m1IUbUAIKGe(=b?X58YBb;b!$n2BIyIPa zvsTBYUrIq;4G`YHP!1moS9&PWmJb3ONL#0JtQzo>-2Ma2BORN~1GUQSC+g;0{P|P` z5wh*I(fw}JIw3Qv(!2G^6%6cVePi?_Zyna?)HPPq>L7IM;WftAhU#-ZB@~|npv-CrV{mxJUxCWEr42(sd?3igW43R zgwQ;Yw{r{ejgj9pdZHd++dR|!LDXM;mR?#`|A9TyiN2vTK_(o#3{A!h#T!?Q_k+-Q zjrU6dd~&3dayaa5$OP^wMR|Wy6P@PN!Cn7u8YuP`args+0z(c47wc*tDKn!CCY_{k zJhu~qh_s8dl$7DqowMlsyd9Kg4nf3?HN^@mZCL_s9~mD?&Qj7p3k78pG1Wb5Q! zbqmrz_VSyA1EU9zqL7=L_G8t7U4ftPNJM;*%)i4%sN;8xXq~=0*W2kjE4-ujo#cmj z+ng9N+Ngu`{II96;^|c~|A-LQ z^C~%i*sgZ^2~q7eq;9$fjrWucf$2G8MAdjB(^nO-d6FicisbyS^Dwi2hkTuPLI7=q zkJuwdY3x^8>9C6ij||Y$*LFapnWyUKpWfm<`Htw3pMgd^M_eR+=C0fRV?EG7^tAYn zR_*j-qFPeKJ%So{RyjNa@0fJv+t&jMV#a44u6g&Dtss_prAW+P*x#3zIYXQEYme&dYD}EevSUxrOS@r1VXTA$Bsz@G^_-3hj!$ZxGv)+lP zZc}d{Dt>dcB~(b-#V=gYsKWthdYkH=EIwwVEfHTKN(!quPrf^pe!;#Sq-QhwWj279 zKyAi?v8I5A`IE?#zh?SB_b0Y)SlMpWvtW6|WhwHQYtzn(xUw3~@j_$;D#ZF_h=_3>Ak+j{dH)hDDC@WjS zRMyG6t9$L!LHOpA{k8MWxufo~EnURzcBSe~_x z1&V96n2;5*hHV8rkdQm0Z$2;wzuKrSt%<&}On&d##I*4l974C?Ko=Tcgs<$!%-21= zoppC4K?pB(CBv-UvRkt&-+!a%%txP-0DfofQ0GYuc_%a?S8RAxG~~W3Z77FjyA>#n zygS^i$-BM1Bk?)GLTM+S^$FbOy5A+jSy7X!12!Sd_jd3PgffpgajwnRWOjMu#-MtpwAAc z5+Z~NBiZN)s-G3{v8rN+myFr$p$gcIuniw~HQW0SOo3Tjd<)+xWj`xV7iXu4kC#o(aywWJ9u2W>9o(?)&2pb-z{y zE?;mUr>G*E@WL<+yq*uYW62ABW@UjEUU`GJhQ3)v`4|*O$7`q-v1pXZoQXIU7m+p( z*OThOFbclO_B2PsDd$O&)1Uelen>%SPWgjaLQ^}#V-c*lM93A`!)uG-#>@NL(tYei zxZQ_~&hbBa;w;-=bN-el7p@?eH!Lr1@PeK=_pYgZZnU7=ks-p*LZ9I;*}FQT1qiws zyS@#9jRSH^lZQ=M8H}X~VJbdC<>d`l2NS}DCiIB4qqp}G5iMkcYWN^atw-s-NP!eT zqO9VJoZesiM$VS9D8bPaM46rG?d}@~@%P;40^dmq z+Zx|Al2)PAF0SB56^lDfL{7}=*rK}s>drE0`d!MGJMXQksF{a(eyW!_%;_%A);oN)suF~W$)%^-tgk{Z5Z7)Ui;9pTbU%Jk>QFJ5{X9T?d_ zeBvMHR6h+&l(LUeW?Y5%7(P*NHy_;Q(e9f8PD1vV`>IB({dAKRy49d?RyvfY-hj0W zQ&aJyJw0G4owAZ6Smd6(R~o-Zn*iM&&zQm#$-X08!Gjcyb#ESAM&7MdL$Yp<@f{Zp zJ1Pcs6%`}Dszt6Tzx3jK^{1_LK8bSv$ECQB)S0HACIch6*L44Dk}AFux?eDhHuw&W zLI=F^0(Bv_1oaY{h@*F2W$yZBUBd_zCJ~;%_+{ocNyRJe= zQu~nX&{-Ah-$#0@c0q1#C4 zyi|9oM0inKSJV?=-tG!JPD9W7IfA#R>|^WYKuRf@y~W}>$wk?w+>M_Hqu8Gxv6+QY zo=N?7XY8MCdqg8g77@2i(A2dKyB2OGlEKSVTX6jtfxW`Wm2;2wZkNy!?mz)Qr5an6 z&^GVjrn`|!3VkKj#kR4t{@E(-(ORo#-i~YEc7!dlpSU%z@5Z!Xfw3XZV5>))u^u(w zU{A|dfLn2Mq9ZHKuDBFq1)yi67PWsqfA;Sd2A_-)hnUHbiF+cXCU@#nX_y|Eic(iE zm_-)_?U8fWQ2$}-G;jisqN#H-w=1zUlz1kQ zk!Z@h!HUm}8tmVb$z%OFrQ7>>TBM3|$L06kT9Y~#4n}352cdW7{`)bd@$y-o#o%ZD z5EkL$4jP2rb*AX+;1iY>{gi7hcrZxW;BKWFx4L_VBe+PL-9H&l!b9q4cP63I>QJwPXpcaTJE z!rFX(b&;iQx{#k?7Re_fP3)iEs7kj`Megy?Yf|gk@qtc<2fkv4XkAp<2i+W7{E@tV zltlB*Ss%at+@BSJo*P~6B{ui>W-h<<;LzzKI-pqY?)=vcH zrW*D^U>fE8nLg%wrr$6PCf{N`<(}8`1NF;yN!zd37T$60W|DN*XuvKz z2F2;nE>fG8$Tqvz5BrVT?AWK-J)r#-XbBwx>m}VL;Kit`@X9qs{C8o(t)l|CTgRX zmDu&ICswIPk0*jLvhw|Bh3uLuIFqA>k{0y2z_&g4*+a~p>CzP98@>+7Q!YVv#Z|jg zWM8W!CWAuP#~Nx|It3LpN_ahwB!r!|8y$B2WNhFU1U{&~#K)KOvXosZerC{GJWWK5 zW(d49GjK-)4!ZBNVC%tu*W@F9_mL$)3j5=>%%c(y0r{tdOuO|jCUn0?xL-o+xrw8ejgKbuH+bYU2UF|T zGt@IeNEm3FfZ~d9Bu6xJ-J<+(5uM&ZYMdP(COvQZW1r=5TP4a1OuKGIp`+bQoS((&ao>wGdaIJVrtIMg||# zX$D{U*#P~AkBnK>7Mq36a>v2%VXx^mf6u)$B3Sgw>K)NXh~TB^bal^_XciQLgtv8$ z`A#Xj#RlBF#a>iZ$UR=~x2DqVVI^9yMyzdgC4jG|mRaaAYd;p4f24VX7_&slh>oVr zABi^{ZgId%KRvt*y=kUPJ@)-J^=X(p>lqhSOba&WI=rgvc$o5hzWd!X;8TY0?tjN% z$KU4}Bn!R!n_nga&7ivUbvc?L2c1%SFQ};5RF8u-L$Z7|UJ#z7X1<$ZbKT5gz*7=o zl+fd{&ojX{zVzneuU>f^JD(r=YW8O_TtBTXN2{>v6sH#Y{0D8v(W)2`h}R~G^blYU z!X7BBP9&bR>VQM-O?cxHfqZ%_Za&UFf{{_V_CxtT96^c89+H2ln*-i)ldc4qcz%W_ zl#P_dS|~ZW5SwO3zmn>E)&dOfQQxRoJDzRw4fdvN;tqky;Z>IBl_!{_pXMJ)$zvcf zrg1Cl#W9IJVxZ$A<8tthxUo*gV9W_Dkg?P~m6 z9~g&hrbG4Ze5Q-Z}^~o zkLiT9N&G}zCPqE`zOM8sYO~nGBBjcSz^Yw$A<-io&z7spNM1j0hruct9e@&or5IWX zG9&!pmlY%5`C6206(}c3J`a5MC|tPX@1|hUPZ|Z)Vk^Ily#CFy@9C=&aU9y7t65TU zKGJ#jCuZ*`v!Dvv&KxtSPI)U!ryV6{U->EVfAd~1e#042pL}~&>KSTH1(5VWa6I7U(iVZDlOBX5{vZ_Z= zqwD;w=i{sMvu)Z~nJfGRiWux~za3dI((s_pKn?nPwi4QYQl_3)oACn+* zny65*vQV!I!c`Kp4=vNu6Bu)Qt9Ge|EI)~MWZ5TC}F09I+V1>%WqZ5TYhR~ zd)m%;5lZY+RJTJ{v7rjg(}3DZmmdEu11=m{mHD92>5lZPBBojC zMZvKzdVFNZ%`+4-P!S&x^22to7da!#8L~auj~62lw>a4rT=4Q_FM_Z(sX{J*<~x7n zb0K_7x{hy-cQk=wSNpgk(OiX)lML~k*C+O!b7cF=X|l)io~0Fc#Io&dE<%SKuTe&q zu#GF}i^;U{IuY&^-632gOt7D{@xAQ7Pgxw8%GY1y%-Rkwn&K-hZ5+W~+!v1?Vk7ue z-NLyzmeQFKDh4$YEqybR{PBo)0dAD7wSQ7*5z(;r;b6hezZgs#Q=P2jGfD}aJ&YVyYa6$jWAQuU__fFU8pnu3h^t- z1wUj<)T;F)DhIPHC`A1Ls<>&qLU4=N3qBuv+SC7?;X%&VDug_4*UdI5(_CFBE1ljK z75hV-4llL8VD@FC_=X(bj-jG>=hLiwuM_3D>!vUgR6D;7h*H=IJ#nMdokXVQgr%Tkc&Ns;rMihy&blgzW3sSbeic z_Kog`-zYN|(c8YEha-&Q3QWWXQ(b6jF6-J9u%_V?15CctNcXqM7Lm3*72pBWK95@8 z_00U&x5rYUWZ|NUd_o_Z=6L$f=2QtmNOfBD#1 z&(Va4@}bQgugqmhT1$D?dhT>BHLO296j~%w-b-<>_=@a2{c1w8&Rer-1Ikuocf$$+ z_*FR8V$5N}nLm;<3=9lXs*r+|wlqTv30HGu&>3ILnEu;@z@3__+?pvg-&e{h^Pb~r zAl^f9UF(W1o2^leGM_&myr1jCI#kY-ea^IbOkh^?!s+(Hz)7ZQEF||PFXpaf_ZP@H zSx`tx9MVgD_EJW@L;GS7^Edssh6ieVom9YDGq#n#2MiNk)VWIEj5qv(bD1=8xdW$B zau>HV)u$N3U~h6XahV56yStVTygf`WsRQyay$=Bn2xc5&zl431#&5QwJXX~d60(Io zim#-Id@QvXAE@nprRi^tJR&*{8pVI=jse5W$o%=f1~y@E8kH|q7hevt`YK?*8ZHD_ z>QWPAi?}SJn9?6?YLoTP%*kUPVrWB7uyD8Yf1eD488C12cVa6Q$yfSGp~ji6Q?Oy1#TumPNOZm3Un1JH$O0 zk&3ER@l(BTGRPtb^AFtJbA5*rg%Q7{39cXj+w4(N zL|db8=$0%AqLg&_68PXnY9Ya4)aZ@I{=-e2_fsj}9%+0iu?PoebOVuJe`#%RL$s9( z9rEPf=bmgsYILc;(of^;rEtlc^H~MxJYPrfWS#U3HlCo`EKL)=J<1LrN z+o}|vsM1oO9+Cf2FTOm;cuaBL(gBy0op&Z3_sYny?u>rJ>sIYtMf<&~BvE$G5-bMMc#SEt-b>L>o1oul z4i4JD7(d8~-L0kZ@8EEI6fv`NM5&>tvW`lu3S5{v!&w>y%kpoieJoQhm zuwx8}kwAd{IxRwuA=X`LF2Bm}pqG+^JF`wg*I(Bn_}vHDWE4w>f8S}G?~Qu7wG$Jn zN3rW=6QFcdA?Hl1Xe@NWJFqt|PpbKlG;i(p$ZApMaF(KEZ>~tEF669#M++tz%8$wn z@!Uvb`D4*6N@~I5xa-+_+k>J+rsb8cUR?MDZ@MEB{-?UI`IDee_Y<^g2PyAqq|Vns zul}^2q2Zpvs}64m(wTP2;|HapC&XF!<3M1iX{e6&aY)8taU>W3iIFa-F56a<;+o1nY#r z+?J~#I$@v~$t^bnV&=`lPA}QN5ic*Vz)?Yzh3@b>OFf!s2&VK##^ug*wLddDKybONbUA zFRo@{XBy~u3;tU^WR?#3zSodH5UJgL*$S5$d|PzTgIY-rTC!Agg?jOs;;hm{8q<0x z>o_|~W@?8aFZWEJRZ%3grqe^DrUeqBXnjHTr@eJju7afhJv~s{MB1gPv#<0KUzlLM ze4SD9BGX#UkScM}RMxfKXF;>vyd@hBl+q=&BiYxG`A7%*{4X91CGHtxE#UJPi=#a} zF!#J2XibXDHL4W+w!Rxcz!s-|OiAJL6xXJDnMtOK;N4s!cOty#DRdc)oR_C0rUZ;? zr_Sfo>h*sql6&l0XHf6Guh&v)t!!xZ zb+HDFE~Ul0XfVxy(uz+zRmXV(VxKXc$&F-`2<@w#C4!1#2FUyBZ#u|veh~3R36y~QBp1k%A zVUk@w+^e1V2i-qX(*0ZtydDC(d^WCsJ;XY-^s&AS5ch6q1u?42;rQb4{iiA($}nDm z8;IiL1gnpa?2r(3OcT(w7>W!h%vArV=Oq|F?YrqnCCxECKw zwhlMY< ze&T^JDqkT8cc8oE(7QG}o{#Ot1Prc5$P7g0tg8RKKOOqYO8)i%AJy@Rrh}7$_ubM2 z+q+-gj-swfMe!<3@yV1H8v9e%*gikY1VA_F&q-K9EHA;V?D&s&W_imK75mvUa*y<2k@L$UkG9Y;YTh!|B4*| z0kd}W?{tKR?sJgK#HZup)At=)JZ8LzcRN@|0lGE@NXVkoK)MA%*HCp=wZ@QD7uhy~ zUj$NjXWc1?W&7+&ecJf0X2Dj@M??Yp4^;LdH$U*CmhrN!c#hYjd~bGnD6lHzxLZbW;=fuA!k{wV)`v9B|p>gXOn<`e`Gj0-g0rw-IYWH;GC=jgDxe z8{(5HVBWm{wDquNFfxdz19HvzVOkO1&4@|%i}BlpXPY?Snhydup0tY?XVd8J&GEZ$ zU>u}Exh#%VQYZX2{TR8ubM-BIa^Ns8)JYT#qJs4sm^eCTG&^NAvS7s z-rYWY?Ou?uew%C8%qZ-=y`&JVaevl*oJT5fh6XM1zS16Vr1yWs-O)d9u)gZCPJYisTy@8!%eb zhw0~TA%+Fve<<)_zg5=e*68rcFojxo_3AUD&#CDs3FC+9^ZZUN`2J50%S}v3RwNCW zo@1=6@qPb)X_oS2#x>nv1f`|P2f32uWuxQNp1;JDF5dkTzzmf5?2tl_!@mQYMP-up z!?ENStI=l8{7(vK)T`AMN1G>oGAZ)X=_S zAHEA#`q;_@1eeSNjD%LL({|zFXqkYDAD~SCAAkQ?fz$#LZCc1O0M-DS!*H%*pY*E! z{42_u$PpZKE%^%hfXF03K$fwWQ?{w*?xgfsQwi5t1h7S0RuUN(a_DMLoLG5lD~d8E zj*=h!uo$j33Y>g-xc%_C5y3M|=$tuQgQvXzPJ#Hb4nHV{TJ3mc+s9AlyZ3nhXvf}0gEifV! zD^)ia&*=L0;ubP!p^EJe;$@2R&t%LzpZrJcF3oG`v!y+!l_=5M8r|<;&jLg8gIdL2 zU^^c}cs~Uttt@Ijd2gH#3AxwS=_SEU<}S?uAUDo~8&s zPRIB$$AiqKuonMV$tk}ttuNu55~tngiEIyRwBp$qLs^wp-9$7Qq7P{sS&`>Y@8c!N zotlBC6eDr6%#k$yVCoohFV8au7W~`SJczDL{~dZ^9*4Zh2cu)Lt)lWnex36o~|UHDXaF1i6eCRx!LqknEJb2z<~W!pH9V z&>*L_Jj%y3e(|?I?iXhENh!vz-*1pto$7zJB&f>_))`I}J%`mH(?e(kbHI@t=Zj)LU~KISpez%|P5zW5ABuQU*uxD_C{?w`}oq)bE`{jtB$()`1jR zo)D6=wQf?JN#^SAI_;LDjWqP*C75J0pK}u#?Ajklzgdu?1@P41-fV?a9~IHAE$hXM z78)~b9TQIN#W;5Z;s@WIcX?M@x3V&?tq&8KEyyambm z5PNf)9G~WaE?UZr)H#nwfezpVO(?+f)JW~bE!Fkq6bgF~F1bj{xXYiaxcKzrW_vPf z5YU;Qa66$wEb5=%i62X~i*Lc&1!K7HN+|^*Rh|B^xtiqs2HB9iXkT+#8!f{Aey0_9Jw^j& zZ$jvBo2W-0q(+x=e#uhfeGII|3?78EG6SNsc(AfC6B_cba81?w&or73kaB@Lv!vo)P5K6jEO`_Od|)MWTQw1Mbc~GtusE~-VLy0hI0v$Ge@HrXpZ3)E z%%G!97lX1uF?omYPj=GJC&~xgLKR4yPy95sSH`nr@~&jWx;-=f&P@CF5=8G{y<}Or z&84z-?$GlZL$~iADhyFt3Y98$i%FQ7SF2{xG$;-rV+zabK#(w^v`@Sf7uP<7y zhTcvC!!%u(9IQ;|qkr@QpHkGkzpDGIF`eDbdg2Ox?5_9M8Nkj)t368XN4-0Z;fSDL zs0vE{ulW)xJ(m|08W;@;=>z0!$_=0Q+B7mt@`N~{KlOn15JGt7yy0x}J=(|3glgea zQ1D2ZpBxt`vnfzv8da4^sk!GbMEW-5XolhGQ~*R>mX4lim6 zSgo;Z|E{iIILg$e#Hc)8hquEC$7%d8nxc%bpaqA#ct(e$xro0j7dvEr!cNiy<=57a ziDx)W?o}3DfrxuVK$xvu&F9kwylcRVBXoPn6VcUEnfO9 z=m(8* zubV~hJ`}c$#$_LfVsu-S7F>LY(tY#?+meNxOsMZwyay&9)9!2(LXg#Qh$^Kle-OAj z*VH=KGE+P~aPwhKgKmb>kZy=iBbb5=npqN*PniRqYj2jVD{AIbf8uj0DH+O#II;T; zM=)4u1(;y~vfGR{!nV+L{GT4F^ieu%Pl&pN4QVfFS9Uk2`Y|hUd_Ex7Y)GyDpDpM4 zd`%H&6$Npv@653|#z1Up$Qo~b`G98egic=n-H?_T%v6~uwk1PZeB=aDI`qEV%0PDM zfY*(liQgAjqU0ogNg$_Lu*Fr!T=M22JH_iVc4T!QSVk(gQ=ZRWcju@Qm$NQWPU5A* z8`8WEi~F7?eG^1FnAvAKr;4_vA|8@GjnBpr$|Cr+cox(M>YnYfANc(no#!+6)o8kW z$t_p7HD#X-$i--sPl8UhTY@px?1Q~kZedMHEkwJlbzC7gduX36m zm+(ws*OL7=Ulx@^$|;H2rxW6L`+vDJC-8wt0^}v_6asFuM~Qe{S;LY6x|tBfQ+W5HM5L z_3xm>sN7JtfG;Mgc4Z|leg<3&u;I%&vWORe-=Z6ulO6j=7`F(a@J+$%1X~b80AXS8 zYo(CD>sIzt_1fR;NebwT6g!?)ow2{m?Aml)@mJcE;FntMZ`5;3K_IbWWRr17l8Yd0 zy25JYA^3WVHR!?DXh;ZDWYxmgF1gCWB5p~8J;?QJzUNJKX z{XGK;^8e{^s8P_H^kKE<9uerd)~JrC1jyvb`&YgiCqr~R>vPo{+Mm6=WnByV&)KO9 zfBrsI**thz6cJoDB+HA|wLlT|Ltlvl2eX zX?#K6UvPj!h@3~E6r$ah@ON#o&w>8Di8SnSDE&qqPel-r2jp3phRdU@xKtEp#7+K_ zIjvds12fA4#7OmjV$M6s5*Fd_c*y8?tb~2P9JMxGuyEwD-^I&wzsxz@oP~l$ zJKc%?=HnS|=$ScYFUmS@rI+VG;0((~fduLKP|n8G))X@dBsygvn%+#);NRj2A{wju ze;A@2*)eB1bGhFKLLTk7+r#OCsk+?W;iB|D{&{!y7OLZI1BnqrAL(Zeyoddo+KuSS@{FY<@^6&8Vf__y1N_p>XleMF<+r^PjQoufymCtwm8)>sU0J8fFHc%Wk}*2p zmbPHyTOLss+k>?Q34qCBvhDY_=T09%h@cG-!7@Y_5}@0>=FEwK8$VGrc#B#l7t!cR zJnwD)4uTcpvB~sk$uI4NT`2_mC{270@R~3K_vQLGX=3Lqrj5BUeZ?LJhITqsdgS>u zTgj@CMSOTwu}iq^lR~YukKO&}ml&0Ug(KSh?9B*~sxv@$PDfMJufKh$`@^;^A2M{4 z1JW4)0H106U@1585{%6#=3T#VyssxfufLDG#=9xZTlC#KcF?XnScw5;L{RIp^SPVt zf9SQjPF1Xkb$7<|W10ZxegtXSdz#{8h~3SN6B2VW>El5dvmqDPB&){V2Cj*haFI zAl=>rOYpfSUn)+neLlGx-qfzeMYMiswTnK8V4t;9!7!xa>5>BIJlifoJ0m42i`nL5 z35DUW15a-fw~Uq+F4kt=tdv~$dF!L9GWh*GMM<;HSKIsgIS277%uM)wY~VI#bPv)W z_3}(Yfy|DiL+FHAB{`bLE0JcjwkM;)+wBiuQ+3$>610_U|Axps0yI@Xk~o?xD*Xo2 z?VI6?)FlX?Qpn9bteJpzqIb-)yW%`%jLz1L%CAaYp|VPYZ^B*33Y5gzDh=$YBZ;R2 zeGjxTTNJbt`~s45=U6Q!c8AS6;#8>1=)(};uKkxQXiQw~w9ISq1I5s3ADczJdI1_M z_<-euv88O@`hsyV_vvV4Kh4yltP`JEHL_>Rf-2$Rk3L=j24?4}`bAG{M?3}6{OByM z_Y^F(vSDhjKz)i%+SOn_FykQRl|eF(Q6)r`=sS6S)i}-2a+=FQG^cd#3ro@bC?B-y z#)Cm%Fdz(&s`wB%lKuy^66lHYG8w-1X5~Z#B89h7Ck@Xt3l08m)hS1wy(((JH!y=w z?#pNMcp^nBa0)eSn^cJME$z#LdjmYf6divLy-x-8#GmzjYz>0j$xkn+CCvdm*tMrk z_1XSQuU_#{>RhPZFY&pn#$tvRjh(`SM!`Q_aU!a^g%MxWnX(+Uj}V zzi9;rKwk~4DE>xG8%t9V58z>Gz`Om@>=IthXO2!pfb`6N4))%6<%mnvgbcyVm^7~e zb_Y>l$Cbgd?3|Cw;7-%uw}1R{L9Mw zx(nJr4Cm}WWlxJ1Vx+Du;y1Vi8NU}Ec_-R^0$<&h!5`CTsV3!J4rL0qZsT##i?g~a zbE1U^<(pUFoapLP8oQLn7-}A{8B1+yV0n;?;{xjH)TakrI$?hqD5L3h!p`;KMI}XB zzluMs9yv#f;k8%@N!!x@_Nr}GrQ^$!t{=XO@@hApEClpm5panM1^82tE?6%kRT46S z_$_WL&@%SQpHJL&=3QE@Q`OQ{@Z$F&wZ^!YUfyejP;SROg9N1mWB%y6Y-m7Q>l<5$ zlz)Unef3QW-9BN0*W10}c`f_4se1U?sh^T9*6@jyK7sNacA_%p4f#6nV3uViAS#)R zGP6>Lgy>#7_Kr78Zs5@_?5aB-XQx{5wdRUjR428vm%F9eeQN7+2#*9HejP0a=xx9S z3!{yM5?Fz3C4Nj=YNjj~1Gu}(P16iTWtxF~JcD(+B1aoU^;jKaNmY}CRtxU?xO~jf ze?+;rR4gc^i)?N1z#8Eqpu{W-mpOuYPpVHrpbgclyE1uHjO%kk*7Od#UG@sFAyJnT zadvvXa7DyR9XA^7%FCZv!r(0))_dU+2q(=!ugt-kB8_HhrOgB=`PliX7F)v_OS|Ha z%TCN5o(D{_F?7^1th;Ai?@3#5Ql+`K%CDbJUi&eE?8L!$pSmwm)f>`Q-;|-%-2D>w zN1^Ej#2qrA)Ak((*e0P)tNcSKQ;psIaN&z1`WJ#^7n-Ky7mhkTksVn2Zd!~&=~E;K zBeX)Q7TUA3e*(tX4d@m(BmE@qAO;+qsF71wQAYj{AdMWq4DDQO*6#C3$2;}`*y0Cn zsMsb>=ef&d&`ws}LL21lkyrFJN`JsGKs#6VnOQN1xlj#c28dgMn+`R9o{kRr-0aVo zWeqUfX~Z5*$C$DLxy!8$GIY~Me322R0R-(nM= z1R~}fZQ?W6_<^N6&kqA1OPG(#tSw(xqzkUeq$Af2haE~ckXyO?X2NizFh3|k1^{_< zP^Tj~kKpm(*C&a3Msgs*qQC4X^l5@4vdSQ{i4QZ+n^N*{j9F&s5!kamLHfG<(~+%ir3=N#P${JWw@1wt+Zu=?+LwXkLFI z#l!R#N|em~5ye5Us=Z;Ji-((x!V9u{`0gC9otZSL%vsK^psu#Z| zP2XECP7Us@OhRDer;a7kp95yvSZ#a6Zs+vteq+)+XrsfhXJ?w7rc6(>rPkUKS1z~M z5=|GL74S*@+1lRZ@F_v&dg5_*Ce_f|!20gE1is+@-Yc2h_>*v}8eOpapS(vCQq~fvu{l-CyfreGf-ncEYn&OW)1#2KDUeCkXP+u2l!l9kSH0<56n8I)+WPo zd5tD9QXX?K7mS>MS@Av5Zhy(K#iALQ@qKpt+pzv8 zPZzkl&x0j?!w$dR(FQqGbh#g8ttQRUZ?q}C-Ix&XUTU0I?N{&BzwY!~t zuU_r94DZnQrtSVug;<)?jmrApXx~$Y#rJf(50z&nYq1aaFHTQDBq5q^F~C3Mnh65% zM2pisU_4v~4t5d^hL_NR2HSu=W!l)ef|B-){(E_feLgEBipWdynZI10Zr&jRN=ZPedKP_{f~PrQg)f43;6)EVxwH0# z@B5CYP>kPA3%GU94R?^+(zQ`HKf{1|)9RBL> zKlDyts=B8y2_SL}`D?BqJtDeLsn3t8NP!$&a5s3T zhYxbDFAB&{iUv;T%(1ox7hM8%NxDw(4u30rb9|Woe8sXgKzN0Oe*8-?{E(+3b*^$9 zwZo*G5&9jIKgSXcX)Vu?o#_ji;kN_ny}@R#o{(8yRB}ehl=M){1EoltlCqu>Rq^Cn z&d0uwsVVZ$NHJH#^ZFHKn{klsy?KWf%eVMP_5SVD6VfjJip8Bpz5Dzi+ z_)epB-?1;5?7&;AGdXbJ^`n9O;fFH-nT6n$_Q_*$Q`tLHt?*oLu8f+jy@vHi=o zKYKpUm89`X8~ue7j6SzU7;YH<#|1o3fPRyw$Kk_?r#vq8^?~3Zoc$hKrG{3X0G=s# z@e{DQrU>Hn2yoN`j+t1y(H8;J%VL)#LjhO8a3g{8gb|%$<#{>AKT%;7&!k*t&OBLd zr>T8wIW%_HRq602XP!a5-BgeRWu8w>lym>Yn-wYo_OYOA5yV#O_feIGK&lv!!j(>L zwhCoc&p#H#t0t}J={0!5RhW1ON1ACfiJ#|Q7D4b5-}ZR@B~1B|053nbD6R;s8gCMAVeEA#7 z?iCdTwSnpaz?;h&rq7dyoYPTh@*s#p7~3TEVjZ46-n>L1v9^e~HA0y%`}%9ac&`q3 zdBMdutFkkOa(IzP{WTJOhr`Cj6_B8tpf&SUzT@@Xqk8nI%|e)=wip@wh?yM`hwsLB zK4Sdy5X%zU-P;K2R-$@c_luF7mvrXAeC@eX<;MwWXe3I;ov!{H9B*|lyq(wq-~H2+ zII*9QrB`;mPpQ3LD>Vc;6?}vpVeLx8CF{_U#|1;t_dG3A@{#9Ueo0ZAXyTUCK054U zo9ChO)13eb4|!7m;S$c}G<9~3gSEo}Otpa-*zUa4O{*ApCo%t9oZpV6Mxrxhgcl}w zm}`5#wBB;W)lrH}5y3tJchCm1;sSVQ5~I z8&eWs*4Po|E-xGlss4XUGwbrCw~zhx?I{7m$F}$<*=!w<6^n?&%NJ$QRgV9vKWJy3 zXLvvML0amZ3B#~_ehpm87iCa0#H0&sNzm=t0obz^T8xSpFcl_%GJ#D23l1&*kq*0p zwmbwE8hgo|hjzUXQ;M!wQ4;)UeE-1Om^X-Bk89zE#4Xu1&-?$6q%)6)>ihq=vF~g4 zElXsNEZIL1Le^wS$PmfCjj?apm#Kv838~0h*0D^sq((@_KC(YCGXi3rUm6YfLd4au0j{DQFFLa4DiOv)?7N>fLPg;TvB)&(u3(WQ(}AC+Kqj;Dx(k}dsS z;l81Wmxl_ET39TTMS)PnO^xk26s4xrTD&FPE$~sS-T#0O>&~QG<%MkC&51$8d>?!t+ z!@tIy7Z}7S%jm|NY#ek_dH_{Ht6-MDx5mYSTqv^1_D5x}Jl1sL4P#O(d7<$@K=u%~ z0{P;L9=n%wazp(;3m=GBy9cqy#>bXVk23=-Lw7JFmf2K=Hb%@FGEgI&#&XC}z)G>K z5ThtrLoif0pD*zwrCemgU#!&!CwDo288X@IP;fB76a*PxLeZ z2G!W2sVp@`A#?mOvn+P4g8Wnp2wy1@u|(+Ruo3o99sN==cA%sqyjpOj&?~$#pYwPs zfTdwne&r@#l#Ar_%#1pw$(%JH{Q=hvufU8Xjchd;=gy`z^RJ044gOtZge|C);!Cz; zj$;BX+PuEd<}R`2J)a>QK?;GO~Fmq3sgWj z199;{d{D?fb-uo2LcV0SzPoG`@FB*=#KTb|)DMg)b&R(>o_+_5e5Y0X!{-idC+k$2 zn^78JtpyLpp?>fAog89Fb=0~dbr3GJ$_E0Vw!Qw3 zC0+h&ywQ96?i06d$|(BCX_S9PBwr*m5pMCeBHEX_PWRWrf0Iof>2fD=qv)~9H}B>< zn&D1<6!401?aGU2(c+Cy(vDCTwQGMvcVZMA?Ty)8xruhh+*pe!VU;in9&|y}QPjNo zP0g@`6RXR*5ZG;-k7vL7Cx`oL)Ql)mHH-V|8*}Ou2N^H&N-k1pWZQ8cJs96Axx!iT z#Amrp&^Fbt0Og+;sY)uw?z$QQV?7KQrPT^1o5Fo+CsuM&-VD2B0N{duLnKjzqzt+v zAgN4-NciK2`KFf`_JC`bt)d^8?{3CSV>a}fzTGE#z}0s$Y{~Ef{ta>}`uYKA(ez*x zaty&O!Sy-Y5)a#PpGql???KNvgjZ6!yUFIy^!6t^ew#?&%1q)Bfp=U-ZvTAE_y5S zdW@|ca{B5orq}x~&@jCA-^TQyAbFNQNAJH>4V{Su*Z_yW0yDD2V-{Wd7>4dDYx+LB z>vf7)+^cVCU76V3pwTRC8n`0;wI@F9FmwKQwmiKCGS2OXR%G2*n}SgbDiGMACY`Ui z_$>A4&1*Dbq&;8xka}Ip>GqG+AvhMY;Gv^?Gnf%4wm607EDY1_oJRUgJu)BD0+rH? z3kmj?uD2D5>%yPhnzZ!3{utH|d|FGk41Zhv?QvjKURAH|O$hF;xYy6(O!ZTUZC%sj zom90d%rmTOctbu@lnwy|9aunG%VSE0=KORX3^EUMoxAlK9EbDLrVQ(QbHFm;<})OA zJMzM-i4e{&vi}r8G~2?OHK4=oQr2j1=x_!owNSuh6Df;zcc2vd7{VcB5fV_0+MP zp3I(}INaQ9F|&^!J{0@?U0&v_5j2D;(Qp{eb`<-@C7s%2Y%S7kjQ&8ke3;^thr}>; z66pBh-dll$*hGqBxk8XL7#28{D`bsiqjryue4>ghUR+}o;vl~2*^x^-N%AE-71P<~ ze%L*<|Jm3?gtwdo{oYS!)s;oaYaY|is~17vxqrM{WIDgm6K;^Vx>S11II8|vC1eZX z(##_4N#^O)A;Zx_oh^a&>R!vnZ0^v;xm?WMZ_vV&Jr167We|6Sp5Ym3uc50F%fGZ_ zgha#83Dv6*g)dGb{SLA8cPO1dwfC~3ol?IXv1)cjL^2nT;7UL8774R;#j#MI6B_m;=hU^3^!?+c?iJ2oqn=UE)}B_6 zrJEQLt@VuUmN@<~|56>yv#zs>u{;|})aS{(<2Vy&qfSF;AQ7~ikm?fJG_|ConQb^k@G)$ja%fV?z=tqj{fGf5!%hVY}LQ5;kV`982mF=J3vWCbKl-q za7Z%{7GkL`V{qq>62qz;GCgQYn71upHt4Q7@xiBVIl?*Z3ZTxILA06)yyuV+o5M95 z!5}h|xw%G$s6v#L612iqxX^Q_3XjegF{r@ zhMRK*6Jj0rH;X&K@`$Eu-y|W+GE@aXJG3PiFSI+Mamf@O|u0SjY4}=`JC?qQOCRX4~ zJcbL;@A#X*c6>UYa?SZPo$^>)6kCbPXFF;ExPbj7doB|1@GKYdz9&q+-<+XQ;Xhd2 zCN=kvWjz!agU%%{ z_l@%if?c8HBjP@M*p>=Al78adW&{9f;xwfGgmWNRwijN~2t)%%7wH{n{K`Oxi_^$j6X^qFc&6PCVT1N zKJtwdOe)y469f28-{mPSKG!KWQad;3|*-rgemGg?zoh-aD2ZP9J={ z8Xv@+kdjMxG!6YQ#8)_s6)e4nrsKVH|Y^)|0DsC z_;^GG8(uAZlIf>9u^C$|%xgCgC0#D36}~;K--!lp4yvQD?|(&M9feTxQx9la8U*5E zfy4^3%) zg_D;``_Oi0Vn%wk$;y(nH?)$4>?G_N!Q!RLZujNf$A3K8zb&1|ctxz>%5*3iOwYqu zj#Cl%yO46K$f;De$lv2*VChaUz>_;SaDxONw@(pr19wn>x_Y~arzjvhxN|yKmF2({ z&w&&$q+CV63F-bqV(Z^t8l+8#PWZaLKnP)w&YagSI_s_J8S+mW|LY%hho!p~Gx7-- z&j(AX7b%{}{RGC?-HX$oa(XajAhZkQk}bQQWlVW6DAD{ICTe=PV}G!E0xdJr4-{{k z5MW(nIA!a(#W$5j>U8MGZZX=9eysNzJ?j?%T!@p=-f^=9s=}=nBDp zI37G-Wr*N?M6d;UKA}U{C4MoGOIkZHen*)PCuZvOljF%3xb7&HVNssn(@4d8BbC8# zLi%!Rs&=tyPQ5Pc2p&#i{_PqC6vAJyplS~F<#AW$oZP|l^GMo;D$PON<^Ep2HrnHs zMjR#JS4eaz%wGnEF&yZUQHV_c+NRaw(im+7@E@AMmi)z+_}`rNzoU2urrGbggDM$t zbii7_45j<_eX`tv065GORNo-#qF#sr{FDFYWrplk5$dePB77gM~IM1HOTz|8C=o14gvl_CuW1-+xUV=TA4LVpDet0SP z&{R1HeUr?&MQnv3tu=;kaUm<}$@qqA$lPk{<$(v}LJ3b5rM@4Dxh$(I2Vb*Ftu`Zu zMN!N%Dlj*kvG73_>zQYmc@9`kJtp^WJSK5p71?N<{gO$hB^LMC#rOUwDu{K< z_X~P7BC185&s+nwbk=66vU{&%{=PE2TjbXP+0n0mzveE5{1Iwgywnj=Rw8nz1;gXY zc$%k&Uw&Gm`TK!8uA}kqI`hqzmY|>U-1wi=y*Vj%v79{=S8qRipV|z(M*(!4$bXGb zd~(18q0`9%EO~J%i<9Fbn%vNy;rcx7MFB+K&p2KQ&}|B)>-sZEKu8+H zgU#I3B9ZSs?dDt)whVgO1!H0!$j_WDyHPIwlYE#WoD5?uoKUzv;sR563qO+JBR`3v zoEbWeYxw|4rgOe+s`tdGOW?9y8O{dy8@^f21Bmbj7rJ3p5qMk3lN=|J!O5>fvo?&EW}9YVz~#x7 z>j^Bx5TOTLhrM9QzxM=iI?v8kuj-ztngPF$5rvR5$g2%2;>vzF7jN6HJwRIJ1JI1_ z#Xyy?A1kKEZnu9A@Aq$R?Dai~?*tv7INUoPic*YPpM}6+B9iL3b=#8mlr?ca{)`X* zWBnsj3&u%=dY#Oq=#X)a6u%fl7|wm-lix^Pdo~%>)4-LPLA`5;u5XVM#$B52ty!FL z)C{AJWXsj8yqcAz<)X&)g4OUV_ChoqN8`5Kxa7MTqI{Hx1D^eb>KtDvkowNESbu`C38$;*}v^=*kyF}CFMu;c$?H`rwx(^nKibPb@EvJoIU)svin~1aa&Igstk3PSbY~HiT250k z=WeARs#M3Z8ta5%mb-XGz+2&VIOk*40b@`}8YsbtU@8GEwVuyV&#9uZmnU7ryZ7SAAwjkUnrDEoCgt3~QEwi~`PepqBa;O1e(2nBtRd$^-3 zT0q>UiBXz?9^_NmuzUDOk~VSKkv`2my)__tz=EW>ZF48p*5tCwP2-l)k*-DcFb~}d z(Qqr41iniH*6KB)#4oPcZ)U3M-Gh zx{&X{kN4}mnn{Jtc^RZiEnMmIoMsEwe85MnU>)Me7j6GH*gYNv-&s=fN-pO+1}ETU z5o4O-YlCJ#PHs7`F=#shd_xPai~N_vGwavmtGmDQ(|SItcT^+EDN&Ln#&)wi>zRXF z&qY%77$D-TBsitB6SdVi8O{JzA+^q0QX#>MKcwuGX@-$zW;+eA0o3xCoVzuYAqwPH z0F>DC6GVIc1Avx#8Z@cWA4A?4tmH;nF9G}r98tV)bhPxHd*UT~sLB%?S&zs-qi+jC zeo6+-e}iPDATM=L1JyJBB1v#b$eypWP%_r_<~VWLlHuZXI@3P7B>2z8ssDJdMo#rt z*#TMY!Sha$mcJ%T2aVzj-x9FZ4Kg01h!n`NQ*D!(8#l3Dxno3kEKzs^U1CakoLU?? z{WFp;_*mT~)ap&K*>M_LD)M{2vjKm`kzVGJ^_`Ys?Ir3IveEYW!Og$xVZE^QNL5&* zch`^|5IOt0>_8%uozNnij!FR*z|{Ko+rU&N32HXJW2ZTsJilhInBOQQrnVN*`jevu z3aAg)G)o_FIkjZXmr0b&fR$*Ef|GN!McYGm!1P&juik7eSrWP*o9<6t7$vIqkWKaG zb%CITPgKWyim)pA4;VRk8XXjrW)=4!XSYK(7UEi%b6IApWfwTyAUhgg7*mLUqD-^w z+o+8eN=aW&Ri1d?*tzwlf*!6@_0}%)naiv2TvR#_s)tNXIXty=CANaDk!EI$K9l|+ zR*@r;Jf&d&1v-B@7W3p2Ag)rG(Uwl!vX?v)aB++Dl>~I`OT}WamD!T(O-x++bU^a8 z47PhL>=u|uO+;5^G}LT&f8mlY$p*%-BHv3k1be;d6{oVilC+P(lHIyS*NX>gHE5>W z^SqxhJRxsjI2jX%*5+{YCrvgj?a(Hi9p4Ho{9vDY645&E0JR;VhnH2hzdnTyZlVc! z8IaSRze;~<-cHhBTGisNwk-b-ERrP%E*lx*Zigl?00Y2mbofV<(8E2izL2Xwq5EFb z1@@XL34cI`{bpiW;<+SQ5L2_n6ZII#Dd!V^O7aar*oi6Y6qh{kwhYb(TIG3}29(Zw z%$TrZOz>*&KEsNK2}H93Sd!ApD0Lm|AgO5MP^ ze2?)lmb;woD6al*wDQNp=nKTFW6^ng97N)?$MS!L3%=)&t?J#Az^dmyo(=5!0uhJU z$ZWrL-TR%v02lfC=eH&!j%$~FY~3l>m-3V*is|*8(O&`<_?iRK<)8Eix1}x+^^JdD z#|w}IamuyRK8H$?1_v4Fzh zr905>zv-C8Yjb6KlY+VX^~!qR<|!lg0DbwXe@ZSUBQfgOp{OLd+DSuldf`KsS*9@K zml`o5TD~gS2p_A>jGotyaie@&T=G@?+a(YsWO1D z0ElIR{}`8h_W;OYQ-Brf-zcU@6A1f-hA#3F=z>?yDt}%UCU4HRQ_WrMo4#X7c%)e8 zJ#-Y9+;?g&wGcuv+@zf*Hu0rXy2AQ<-JKZH01tq)cpRU6afl?0zPlvg1J@TneTgp)a6}2 zOVT4TcPvpYU&rij0}Gu(N?c!?lL0o@6!|}x2jd=P)Gq{W&`9M+Q#G=jHrX6o+2(*M zJA{X6%WYX$LYTpw`HNF+RGbMX8>p+VwSJiab0*&IAAWB2iO*_m%*IXHYpQ;Ldax-# z3c?B{3KwE_9=Q=$7X29WHwa{!H5OS}KN9Oz4XNcRN5v<7A+WROGYAK~;(?eGwO^TM z3Glt|OX~9i-I1m@?CSsSL|+aIYFEyz5iH}~qUD^_t~r7*bt%KKuHt=g$BeDr8#_P9 zY8^89ntlh4;%f?1JVec;F0D36+!y}x#^a1ud$%V(t)%aPQB6FiY?P5DUKau$_WFh0 zC1I!bIpqWm@ib@~F5Otm?$ITAZ8yld4xJu zD(>|AQ(+QV;Ii|$(y*VCmg_^+`B^^G zlIob^|ErnEy`%$m%2CD$klFEBC!W`}mdtn)@ST^j2*Xx8KzM4!gkDprsq;H}5OAFq zXkb1jq@Az>{OosbB2tR>;A9-rV~YO~=0&X~`Mg(;$dbzdMgg-La?<<7a?0xrxs>Jl z`k$h2HaDFuIH9y~&?e8M3F-XqoB5Iy8Gi`gt!cK%2%%hSseJ$)) z(amv2RtU|sdh>e-$Yh_17R-|kt6KkQfPqFwv8xh0I-J-qB(w?QbI<&HY?DQ>l$c3p z;`)|pzO$>J#WTG}CLjQ&OIe(=;8XPl5~&X0+m6wUfV2*FWhorw$SMUGaeJn z2a5c@l^Vaw#&+-n@Qk}9m*GY66aPCMX`Rf}q8GK^Oo2ZW!p@skO$@6{wX=wU=@Dy3 zu7Ya|38RAqpcF!M`pxT4yZ=hrPR>$uSGiC(f&6#r{!gHy9w5KMF zx2oY9l%>bB_cSj{U8^@uA;1*$@sNXiN=JP?<5QUrnJJ*R`S z*@h>*SgG;KMD4F_u_qX53_mU>FQq4vKicUz{7+3dBdRmlR%p&^xv51tw}HN;O$Yfe zGxlchV&;SKgf0%#`jy+SzTxi79fWL3O-X+46`59NWstU)6n<{WC*vxTOougm^RW8e zLl%LulAHka+mJ5;WP>8aa-`>%MS#pv3$XZVV+L8YqD76lTOI1qe$^!}(Ol?p)A&&`YyB zO+mlV9U1!Vg9g22nQIRhTBn-6{vzLHc%)e>N0XhB|0)~nJ(sX{`?rJ_9%iPb+a#>4 zFN4W1cj!Z8Lhd7txUJAw$nszIX-A;>CVhqUh9z|MuNsXRK~& zTjpAal5d6QHe8m+W_TWc(V^g_!`f%am8HbFe35y<{QTyM-9tX5RkfSW@1kmp+a_w% z2IR?f1%OY}G4bN-qFoNv7U$N3F#N{ZRBKS_6ZN*-Ck5ZdYC>&j~Asl#h6KVE?R-vmy^Ro{h8#!v~ zH96f|lr(*q02EqJr{i|u<#1Gw)Hw|usdO8;KOzd>bFLckOqh>LvGc#v0RbGtP)mrc zTj||jDX*{iZKb`E9tK%5#831kMUp1S*T>D53H#UnSwy-=(jos%F^3vu<{|Eog#ku1 z-H5Ejd5+r=e+axzp1rtFek6ub8i1akjDCj))-}+L=+(mk*BoK^QcM8-YGEl(vV|3h z1et)BeFJkFYXvq;0KK}bWC@pU>`0krL(%-jnLs760|ofT1w03Ub)s0}GdhJgK8u5G zPI@v78}eVRp??f>9QXZx4~@1c3tqW%-{$V0TmBQ2o&8yq7Hb; z-2Wo_JS$N@W|22^Vc+FBk!G|4&k`g-7^6{F{Pq6vM z@k#13C`pi7%71+>a&behZfpl*h$h>dp=JBe(r9d z--(v2m#I~;;75RSQ z=cL+a9sKPEyUJY6ecC8$x$HC3)4^9XkXSR2uZWzv9NAL)+~HOu@QBrZeIl1ZEF2DQ zqv0o3cY5+@vh%hLg-2c>){;0mh|ywzNIW8|M+9=#BE>HsRd_`F?Tb^&o+RfyCkD8D zB{S-F3l|5mNg4T_#RUc7%O%h^?BIkFQrIGQ9N zhk;tDN)ALyH%bc`M#Hz0E(!Is-b3;*yXQ4`$o6@VC_gtDU??tKR`&81fdN)XR>>vn zMnySqA4>Yy^J_+5JaPBtz_v((L)(bSK@&bnbl!bg>R2m{Y%u4Xhf5SP4OI?IuXO!x z6TBNuWp5C`f||+(aW@FbzwxVH@73~s@3R-O65Ns0x(o-3UU=Zf7H-39p}zMJOwRDe zZ&nYL?~7x*?z#1vhSFrw_4QqS_<($^JH#lsaEVeZ2N02rS7>XfVTt;2EY^yH$6n*t|4g3aX=xiW zg2FgUIt$*kYi^%pe$vA|Hy(Sf2;x8ta6nM=74bv5f%ZULPrCET ztsir)<-u#-=a)#Zw+YbOzQqfoOMqzvfI{H=+}ckRv2#ew$*W<#V+v4N^(@wx)bQXr z2ec`5KfBeB7AD?z80c=x;Uyx<4|lW}Km4^T7P~H+g&lqH*3d}jcSDiEcb755DZ{JZ zH`wiKzp`@Rt^Z!v%zqCyzA$*$UHRfNru|36rHndVQCQvGx`Df`x`mQU^m(S1udEp_ zv%Fjv|M)_!sVaIgY(o}xyONMlW~BYvgG$SklPZeJwf+i0C@MwHI=gwB%!AD&e z@3fGYChx5KEwu0QXY|P@NFsn;3<7I(Zt`t!B`maa;8~K;g0j6j^h;Cuo_*l&vh}CO zx44JA!xBy3xJO5Q5%;TBiJ07q53Zi=w|82<#R?;n-3b_+KS3Ub z<9~b=t;aePI{&NRTd~cJvnT8CL-)@Efv^^3F4w6*HTu{5T~N@K!~mW^N`3(g7>WKd zl;9ueQKtJ40n8jYN*7m*%#+Tz`aZsgwrp=UzUR8+MH+urBzFKCj}*JbC&A}4t_}6+ zD>;5YOF$TtaZ`H#jkln8H>>qm!!B0D^j?y)KK_|ok81L2Y~9{02{qTxLfmNrvB59--v!ADLgjWII96V_YokrEyGYi{<#LVXmL( zV)%gMnS9-kyL|Na?fFF1uKyJ?)mN0Gu}NQxH>F)F+l59Cv}H$x=C5_kE#KAd^F}1R8B6zal|Hpm z3x&9F?@#Vq^^R4&AbdA(j>DN3H(UO(sm1w@mGYRkdwT>;Wr&(>Ui=p-9oJ<&C#>W< zu9Vb7(KBL24KN*ohw0 z8w( zR0}Z)Q^#ti<$e%(;ZBB)yI)k`SGE3G@e;nShnl3+p6YSQg?2d!J*Ayutg_bNO;Tdw zY{S3^&vBOSf&ABU$Cr2!OvPDE^0T=DF_or@mPxklqTi17WxlN*Y)ZE?aCLjVx1+&1 zT@cM#WL-DpgVf*Az}2{{;H@W0Nt6PV9_X2;22XsxKk;aGm>4g@k&sO?L9{e`ebDdeYbe7--%7mg%K zXvj-3L)LZAnFLU0Ps$SHy0Tlv4(a`Yd~)(j1*X>AziLDW*U@F(ZZ2E9ddeoN&|FnV zUx19uXPTon(fZ&ZpvzEm90fn)&K`L3!JS)=0ko>Tua zU@wqQkARbrX60PYh98Q_*Yube7Iq}lJEfy^7VxulLL$;7%r-bxtD2tvk#$osYCx!w zu4;DZGXC>qk^`xDN2I0|J>AzYF|{piFrUET>1M!(B8WTYj*GN!3?$dXSRJAbtThZT zwTpoO`1VyHh`&t0)w0l_ENWp;1w9P$bEP7*g3o@<5o-D7iDAwi{}$;x$+!4QRN!h+ z%H$vsgSCq9%&JIMxFKLK8*A$~}j<}k{j-48ct=@X< z?v3_W=tL~yU)!0Qgt5E}ezfSEvY+s5p&fI$eGD@~(6SR<`_&!U?{tb_l>>wq%}1TY z<*V*Z_;x>&M8m9_eXS2PMYnXmqAc`^A-2xSivvcU)&f4S-m z29?K-reA*t^)p|APDreryj>ryTA-Z#Mq5cIGqZs9&p7{;sh1jRHairwZCWEy%)A6ZRd&EP8_V=z9F{GVDvU+CRq#e@M!&_H-e&>IM>SH?^Z`YjcsA5*T zYZ=e~?+vQ1cUc?TznL=R2U1$nfTq6UtF3fMtbg(~Iu;NrW~H;bcIK$Ginv3isAtuM zk4oO4yUxaVNrO`_XKt7y+GfuZ${Q^sup6!|bC>RzCDd2o{D$CrxH|@Om(~thGIJH) zkTzMa7dTvY)+m%~pNJ$oItZXDU8O-+*v7Ir&!d}U&p0yseBF{)7OV$vx0vD0^5jfaWe$v z@{(V=1z{xN@}Pb1-4V%dG^ipuYlH+r_d3MSo%G&QW2^*SYNil%X5LBnoG zgUh+c1pin019V6yd24(&!(Ml^9(DMMyfo-r%@hwl=zB+mHMXgAz4!f62c8~9yg%kZ9}AO6rJvWq2>vgP*$x* z-QZ{P<1g|Lb;3RNf|H4vL)7xm(|A0QmEbTFV*+a+L)D@so8ldmvmDw%UALQQvi;pW z{8h%=^zz#pEtYcnp=e>twfL=|-Sta_PFwe=qIUWQQUeePMpWpy{YDPtAfe2dqrZVd zi?qp9SMfT_xkDqhnL0`eMx(P<75N?X4peP$Plj8cqrh&qmL(ZrtEJB4M0(b5bzy## zjR@8-rc%VRJeTw6YM3Y(iap#ljEYb7_)r$ai|IxB$m7}C$2S~ zgSdETcchr61I1OWUP|8x+vOE;DQiG7&80f= zNm{6|6oJx$Vb#KX2xe}g7>H+2aGIK>Lf(qgu&&KOXgVgY$T#_i`Hr(79Ngqt(0woh zWJ)jezI=0pA;YsySJEbX$(x}_op7tIOp}x{F`Zd6hRM(UHv{dkDlq(VtT(=g0sf*? z6g%~@TvP(6ORSzMaEz?{OORJFVkw3!l8X&4HTI<{%EG|by+#*~Qv%>&h1u0Xhni@~jzR$Pfnw%Z*ijQRXhqfv|_ie*c_+&MAvb^`YoMiprA z*Dua0C!Bq&2D6pabgAKFoA_y}j6sng0?LWcU$SnpH2!lS*o3`fGL>|8mzWT(p3@l_@^p}55iQh4{o-AJ#3ebK&<2O+&mU0Ws`P`V z)@LeLQ>~W4h9?UC~m=ke|*@S1-<#_8?kG>rc}u1MIL)65uqzA2rO= z$vzJ&`qK9ZCgQk3@A>D2@IQmTAV3M(>JU+eV|BFPp3V03UaHqGqPWp>rR+v(}bFT~2ACIZYwtGgSB`iYQgDHp51n%e~udIym!pJ{-8n>PK1bdWq<3ydJwc){l?v^NOF^=Hnxl^**4fbFUl_(zt54 zGo5{tGVHO*pGu*vcx0P~YxO%9B-R$m~oLqk8I}QP)Yw#q$ zgNZ%3%;rheS>O@Ka=T*{)7leomlDi>Z&BvE;%0Zmvb7cG!fn59Q7=I4uuq3aPq}Td zcV7(W$HfB5%X@=b<*`HTp-c}p#<|a9d!0PBnMDW^$+OTWc(h~w5lQr9!_#Xm3d@@b zktzf7#S*d^W7Kzj6Q-(lI=J3l1(Ldn4KC!Wjcogb)eUEzQ!AZiy3(YnG=$#8cWw@O zCz~`n$+g`G=3*|b<&P#fN&#Gsos^(LsJl#@VqcGcXP%w`bLVI~K zf?#7Z<&s5zud_(cfjNpzKl6@>r&CKpv?P8q^g;mT5tv7!KRAE9*xvlB79xxSrHK}F zLbf9(QJZ(pMU=FEpO;$8Y4FJ2lar{%gQq^Gc{04;O#jmljP2SeI=Jfnk*|v%&nJOI zZAO+cr>Gy}I*AOHOE0}5b8J~Fwj+52c;8|6(uiwLu$B`=X>-^!weRJTY4H-i?ChYhF7agXds=a%+>b5uNFr3ox`n zXpe7>Dz4X6bIR?#U3}cuGlv%T24|J?0s#4M2Br6e_53RIa$m+`*gGIwZq;2K7dk0= zZp8PsBGhfU$x|m8R2`Z zo(y!T(hG!fHC1>SsNNj3BU?d1F!B{XNZy^T4I@9Lmeql?k@36-{RmIoQD@6 ze|Op|t9-&*CEtevT9QFBI8?th?o3H{u6SCj8oJ2#&w%1mZ=F3 zRIDetsMMS(1y-$7)ype6owDg)L>SN5mnY*0x6DPt^G;hHMP{_V?HQRx z+Jtlo#iD#7n~%Q1AJ;NU|3SVNfu7P;iMYB%Nzka!X!U&O*&=XwQ$CC;rrA439-x{w z=A~>{JyR$UUR-?VkmznsLq;1cI-DuUY|IQ?mf3GN7%93hX!drVAE0#ZBk@W2_N&tK z$*?3{R6=_S0<^Sz56qi{xQgHRjK%Y6#nVl<#V?Omvi9kR0&(m}k=e9E=j zbyF$qv2N0i5N|;0!)*jxt5Ie$-iI59=PMzxX=U;loILXGy1Ri!)a&aTD3f&?)Y_5x z+i29wX8aD({~AJ5>HEe~s^rHqh5OFub&hA_itK1ms#vr*cQw1)GHtZF1;|i~hB1NG zIRX20@@iGhOxDnK3Q6dP4X$@8JUaAZ$(~*7OG)>InYEfCa%hRi2R96UwN>m{C5EYH z#1|@IQfOR+j;{GkL=$hFDZ`;HexHlFJ%z}jc!(*CDO7l>}~?+XEY@qAdr*0oWgpcbqM3* zJF`EOZftb-+a2stX(eLHa(hZCTy{nMS&uaveprtg|7aF`dW}Hlf8)J#;)&?Z79CQA zTD|k9=TI%;BjGK#uxew+KIIP)MROux=yA4*{+B(=^(TF)Tl+7V7Yn-Ub-urremuN> z$D~y~9>eij8#(S(&_&%jBdB`;B}93(puh`kofm5#McmiK-y$f(JzvOEotf2Y`9tSU z`sKGM`_c9FD2}6xD}2JoA&K*GIG3Q{od(ILk;YdhN_V4WroX@lnRWpm&9D3ixIa^o zgJNsCeuC=Dt+UZlnDq7HgB2>|oj*1vL!0*^t>h*VVl^)Pp2#AI%kKDFmETrp1bcEXRZWGk~p4L9%83%G}53Rg$&Rh z>>(j$I{0cTJI0CUCb$-;lNWgX#=0pC=(U%erin@ir1Ivvu9ri{y4>nrZ*!v4`WaqsuP|&j*3tTtbUI^ypCtrZ!VbZ z{9bKzkV2mUMOS5p7P5IhT03DGpjFwsCAwZ z#U)KAHBNS{q;~w7%j-Mma>oOPwT+7}cT_&5HhR9(TF&94!`1b=xj%)}H&P%6nIl!C z14@do%%4jK3WeOGLST7x^kjHcn-wMWrzGbJHvjN;y(~OrfQLx68!A3iK5VB;S7h)C zC0P=AAo>y1@p5_7#SCXD$m{JM3^Zb6Y}?e1T%B(|UO1#TX3+%jLE_q96V{bItpxSZ*bV4>9aYJ zzxEV2TfDzZytnQUsu<=jiEAdB;g_dgmFrc7FuiG=<&dU~q=P?)UYxpMW|ukLI=f`# zR~w)00UIIdwbuyVur2%XJp2D@yYhc1yZ3J!O5~9c22bO$GsW1)5?Lz3L)o%rEqhEx zMn<-*p+SiuMTD|U7>#Z05-Qop7~+{|jG>HS#P{y=KYZ`k>;CP2opWCI>zs34*ZY0F zuPaZC&VL}*tn(+)Z$m4yX#53NYjROs4lxV_7LeVjPt}{Z?L}|{7hyY5hC95W7JN2i zArVm0hJG1Zx#5jF^9*x~_L_2dz0kjYpeggkhsAC9eJIpFZ8)fa1o90huEsGHt)aFI z&8TSYCu<RGcqPJHHS-;@R-N;4q3X?1)$aM1CQK<~Rsjx`Z+7kt< z$?%1vs9!rfzK4i4))}9|95dF=i=(fm5fnvFXK>g?gg+or?g73e=zc z8?%XSW#=CsT0x@uWrKecPcrXogl`#D^hTW-I7@SW_%<+EMVUTFVOO9PT&GLt~1xE^s!kEOg@<#5s z(S#q^;AFpI2u*oOh8zT3WBI{viH9*1y||Y}h1SPn*2+V2e4KS-is2l?(>#XPXO8ZO zeYofBEy_Ur45i*MQoJtxKBH_qSj{UDKnVjMA$>C%s!~vf(KY}oz+Uk`&CgBInBrhZ zH|OL$lC%Ua!Iif`tp@@eahZ2#*Jr*nw~P$4ZXG{V;{Nd7r9ydTqC%VHdHQw`U*2RD z59&&r?;?5Wy$=M)fh#c+B|fU_6nw1y$4lzRIm2`eIIA9FwTz8B^;c&{5Yha;Im(`NoQ*BO&Yw_%-pbsybWY6vPh!}6W|Mv+RAu!G3ja{L2$ z3jcD5a!kw`b2MT(BuCpu6^Pt72g=Xl9ttprgkMUw?|e~aU`w53x#EBUATyug4{eM+ zD31NumnBli3Vto>aq99Mq$H;nrbXn|$8e!bFO5<|3?c%yM5{?4O}pI4Y$4D0g?r1p zO_=|Fx1`od3E9^wvQ!Gar1W-zuEecR zZn?bjNNRq4@`s+n7dGYq?Sr8W6k zJ7+y}Luyt=gM^30tj#?I5|EQYi-N;Wl?V$Gh-FFpHu29`Y@QSS;LQ{zFGTR8nX?V@ zH0m$4a)oo0z;dqEJP(cDgsLZR1&i8w?T))7Z94s6&JL|);^ARZeY2q7Ykj3o^l&ebmEFx9Lu9*pP7#Fe zF#ozkeEI%~?rz1Wh_(6wZy7ao!>`r{gocX`# z2Xm$|bcZw}ED4=$6Tj{ZW=@w6+a0B&QoPG@Mr?hkb^QOX6}H{h!fp1bP)sh;whesh zQ~yN#tF|}VSZj)=B@6qmWLwvY#{|6yKd#Gi;TPH_R6NeUTUHxsQoR-_F7q@qu-$2m zWS(W=({YNCCd04SM<;|3?lkZIbB$`CL`iF-Z;+#*{&}|asVH$hK$WhJzb(dpzz^0t zcBf@9XuAayAY2oLKTkm^QEmZLuR;Y&bMbEgRZbVj+*5$EZ&wL37W-F8BaJJ_<_bzV|MCZ2n7)onoXd)kfqIeTpFocfe1uY%7RM^QthTV<5%eMPtG02=qr z&~jjNo z@9}MwU9NF#yEGbsGyO4(|0b3)+scDsofBJF3xHC%~8?~;Cx zk5Fjr^v{7OjK)_1N{@!3?d5Qp-rI z0^3SN%fQ^~7`9eIX&U}yeMklgk`cwzm4F6rptr{rFIoUe{cH6)GjBR1v&STX6}fVk zsnhv3d-q|UUeqv?p52W}msGu^Tk%Bf+azk){j=ySC7fDS zp{?2Pu`#c2&EMEF+Voo-fW_F#3J- z8s!QA?rQ+@XsQNv#0>bfdEZw}J-tT#uL#NSwc;l#6Rhq+-BZ^|hb9(c^@`W^8JU$S z8Kw+r`gpU6?$P)=D=|EKQeWhM6i-8Wj~<}j@~*B1LVwmY z{Z&NqoMOeaqtSwA_e7#9Wij2KF211pDv%Fli6116==B)8 z)w^9l-_p6iqtoUHAej3KUTrvb45t^&+r0&#P%wl#b4$DxF_=cHxNYi`b05WH*f*w6@Z5`0{&AY_zO?|{-XZlftAyuuarun1iJw{vBSjId)6X2%AyO#ETRE* zd)U_o-W+~+3C15NC=Dlt9fk0dGjO32J*Dz=uERIQca+1jCL(+VPSnbeOB#1 z2bDtQDH@@gPY2czr|L$AKifN>2kb*f+?h)ZjlV&2=iU=s)3WaHv3N_(1whFiJp0XYL}Pe7qcHnsOWW z3E(IvD7aK36iJ@vz|1{^@Sk<{*j`{i2f?JzC5h^1$a(b5Wz+o?YTmGR<}MP^GaelS6{*Fy2#yojJ2O z9w+?dPYs8zGrBy6ufWN_pFVs>98yB^>j9x?X#Q(wp3O4ZoQ)Fo{+R?nRbN3(zZw-0 z*VVV(=X{*HK4TT62Z&Dq86DVr9J<>eHA2yCh%Yj??nY+^bLu|DTbJzLu9Fxny-A1K zD8J${e34)>C7_q&e~!83I#|j_f6L{}8^?*h0B|dtDG8W+G<_gk?jY;Sw)cmfOzQ+c z5o$crpUAsM|L<2?(=hho!aJ*9&&gwT#uIsW>GV&znv@Z5XUa|7kRhuss!LdDFvtH3D(hWZ|zw_Vy;5&;Gf zSsi~F)Jgn_n9^hLq^plILocQnGjBAYg~Qpii`{EOoXGjhFkjingnmy&L0l}6oSB+I z@*W2v1rK?$)YdEi2Y~lREP~pIr9yPBc+IALD6GX$Ov{ci5f78yqlDr1p-0aX9TWbb&$BlY`BZ_t;n<_7axycxZ@QivM!%|{vkf4<+2ICJ$KCLiU~f;oWa NvW2brTQl#({{vztRet~g literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/tilt45-npub.png b/amethyst/src/androidTest/assets/qr/tilt45-npub.png new file mode 100644 index 0000000000000000000000000000000000000000..73cca26568ef92e06ce636d6a574802ebb3533b1 GIT binary patch literal 10219 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/very-low-contrast-nevent.png b/amethyst/src/androidTest/assets/qr/very-low-contrast-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc8fa196d0a59cc63baf302bfa313f9e427e83f GIT binary patch literal 2650 zcmZ`*eNahh0TTx0>-m6C*$RY0)bf?0pbdxwlC%`9 zjEefeNB9bg52CXRibVM)i&Zow5O`r4K_FBhD<{=sWQ0rA7t{-+w z0k{6)`1Tc}_Xlc&bs;7v&%xh4QTf6gS;*b%FaW>t!&&6rh#(32wZ~3ma`;)8#_l)? zD=@n3>!l9g958IUJ4u1Z;PYq`(OFB8g)BzHd{^4f4k}B$BiMS9Pt%Ap7DZyDQ%0t+ zXFNSL$O>sd_M;BAxX){OhB{Cg;$uL8H+$KjKZkjs zzoHHeF)L6%i&U+pYsWliVJP=y6Hxk0)%%&R zzbGiaQaehMmPd4_zula`34#ko;7%OX=nBzNl*>=m#2XJBql*p zP=}u0VR%Pn+h~1HPU>Br1j0C2Gg+9KJ^)+gg1@JLf@$ez64cM2{tFJ;TI;2xFEJW) zqfIeN6}`uIZf+~_6!fscCku!YzCA+`1BX?yeGNnpGp03%UWkAiQzwI}I`bXVK`5^~ z7K5+O;drF91F_)7)5^6V!$DmG@Vc5t?AwzIhs$+Va4SW7^&FDX9|-w8Kk|{x&eETy zIKXkA;{npJQ{rvO(|h#vyX-9#?U2%yi8YGxFDEk^&jQ{}T<&+c0O(b@It%FliESiM zY%M&V82CndOD`eLnieWmFgozXL2A|%p{eDx6hb?)A8b#{mhU32)6E-Ao^9d1nWl9MO_oWv zSegRXw*AgDySebRk-k%(*e_Za+Vz9QSvy;YlzvS=3fnVCp1{FG7d9*h5rD)jiMrnmnJ5x3z zs=AX`rf5H*;0%+QgjWSfJ`dMj)sy|$wS6naVGb%%xIUC_%1>ta@NhaP$rRiFI1NPJ zc9kIBdU`Qq!Xf#G6tRvqPwn_#uB`0g|C@D`^Zbv~fqvn-28dG55Zrs|NW zCF6V7-8#H5w(5Up>WEJBOJ_8U^g8!z-S5L0Y$Fy$&3xt+4&@>3fxnrD2eG|&8%?re zrKqz|4UA}XRB0brl>*vvopBWzgQ@}A^9up41<%*ejRx~3POBlOS6A67w^i3RED zn#)K+n?>3LbiXlAl=^_Tx0g5C9IoJCaOc%kwyx)v1w!0k-G>cC zo%Ik_<82haSHEDw|jk^E)Jc)tAvJ+jY-+0 zL1AVXl?{H&U}ED%i05k}Ld=+aR6pmdZKX}smd5A?_XI046%i0lZURLn34lB(yAV%W zkizuzsmnxYXIhayrt4_yf;$`hzC?ol1TnE$T$bqRFkB=I3zVeM+(Q2Ia z3<>%znTd7T$AU)|*Qi3UqM+9zDX;Z2#7oX_1Y>@#24axb6cDL#W5S6y&qWh1!pOAC zqe@m%GlSaLnNzsrNu6o@3MYf9A_aUx!`X3CQ4kGJT%5Z$3HT*$DI&)?WqoPHXuEf^ z9FnvYCRTJ98N;#v?`9|R;Da5r?w^XBOtR~WTZ%rJbFSC&nMz?&6zxV>wjsm?h2ZjB=PB zOJl;lxI{SBz);1L{YReIEe}kR)IN7m<3$<$R3(oyYrC^lamv2; zlb+)_?xhXDS6gsMJWUS{Vy2th&2qlG6x}4PZ_&5ps|nL2!6JzX^Xw_IPW*N109V=F zH+R?%7M18om1A{odSh5F>*)$hjhD|vT)HKAtB!y55ZC3;+*XjYmp&)YSKBH=v!~B$ zLLlz!Ha2J{a;9jzIFqarbQx_44r_@#*>!&3s2a@D4Tz_4?%7jp#{!@{uemA+i#ndg znB9CiLm^;2o@%DEhwa<(r;JyofN$w>W}PB;!`rleRqZ{k-P^Jj=Y^&1ro&Z2QX<*+trdggZVR}sd{g7fXL#RTJqQIaA<=_3Fm1f=(u+* jm?-|gW7{95xvmu(haOGKwOw-S>d*)Vh6mK|J(Tw^$t`d8 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/very-low-contrast-nprofile.png b/amethyst/src/androidTest/assets/qr/very-low-contrast-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..ca492a1fb280aacd99972b72e3efc64c8bd9887d GIT binary patch literal 4827 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 @@ -81,9 +82,13 @@ class UriToRouteTest { @Test fun walletConnectDeepLinksStillUnwrapTheValueParameter() { + // The value has to be percent-encoded, as a real deep link's would be: left raw, its own + // `&secret=` reads as a parameter of the OUTER uri and the value comes back truncated. + val encoded = URLEncoder.encode(NWC_URI, Charsets.UTF_8.name()) + assertEquals( Route.WalletAddNwc(NWC_URI), - uriToRoute("dlnwc?value=$NWC_URI", account), + uriToRoute("dlnwc?value=$encoded", account), ) } diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt index e442abe215..a20f15de17 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpus.kt @@ -66,7 +66,6 @@ object QrCorpus { "npub" to "nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq", "nprofile" to "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhkummn9ekx7mqpz4mhxue69uhkummnw3ezummcw3ezuer9wchsz9thwden5te0wfjkccte9ehx7um5wghxyctwvshsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvshszxrhwden5te0wfjkccte9ehx7um5wghxyctwvshsqgxvxz9jkth8dgc6dyckt3jmg5kvthdjtcn6q9lc39ahq5dpjznuwq", "nevent" to "nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ezuamfdejsygzhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8psgqqqqqqspp4mhxue69uhkummn9ekx7mq", - "njump" to "https://njump.to/npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqdhpvhq", ) /** One generated image plus what it should decode to and which hazard it represents. */ @@ -88,7 +87,7 @@ object QrCorpus { val fixtures = mutableListOf() PAYLOADS.forEach { (label, payload) -> - val (base, modules) = renderWithModuleCount(payload, moduleSize = 6) + val (base, modules) = renderWithModuleCount(payload, moduleSize = MODULE_PX) fun add( category: String, @@ -96,8 +95,12 @@ object QrCorpus { ) = fixtures.add(Fixture("$category-$label", category, payload, image)) add("clean", base) - add("blur", blur(base, radius = 3)) - add("blur-heavy", blur(base, radius = 6)) + // A quarter-module and a half-module of blur. Both are expressed in modules so that + // changing MODULE_PX cannot quietly re-tune the corpus's difficulty -- and they are + // chosen to straddle the old decoder's limit, because a category it fails outright + // can only ever show an improvement, never catch a regression. + add("blur", blur(base, radius = MODULE_PX / 4)) + add("blur-heavy", blur(base, radius = MODULE_PX / 2)) add("tilt15", rotate(base, degrees = 15.0)) add("tilt30", rotate(base, degrees = 30.0)) add("tilt45", rotate(base, degrees = 45.0)) @@ -331,6 +334,14 @@ object QrCorpus { // helpers // ------------------------------------------------------------------ + /** + * Pixels per module in the source renders. + * + * Four is ample to decode from (the hard fixtures shrink from here) and keeps the corpus that + * has to live in the repository to a few hundred kilobytes rather than a few megabytes. + */ + private const val MODULE_PX = 4 + private const val SEED = 20260915L private const val WHITE_RGB = 0xFFFFFF diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt index 9ccadfd116..9b53c8db0d 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrCorpusBaselineTest.kt @@ -52,7 +52,7 @@ class QrCorpusBaselineTest { // The one hard assertion. If a pristine, generously-sized code fails, the corpus itself // is broken and every other number in this file is meaningless. val clean = QrCorpus.all().filter { it.category == "clean" } - assertEquals("corpus should contain one clean fixture per payload", 4, clean.size) + assertEquals("corpus should contain one clean fixture per payload", 3, clean.size) clean.forEach { assertEquals("clean fixture ${it.name} must decode", it.expected, decode(it.image)) } @@ -80,18 +80,22 @@ class QrCorpusBaselineTest { /** * Writes the corpus and its baseline into the instrumented test's assets. * - * Opt-in via `-D$EXPORT_PROPERTY=true` so an ordinary test run never dirties the working - * tree. The generator is deterministic, so re-exporting an unchanged corpus is a no-op. + * Opt-in via `-Pamethyst.qr.corpus.export=true` so an ordinary test run never dirties the + * working tree. The generator is deterministic, so re-exporting an unchanged corpus is a + * no-op. It is a Gradle property rather than a plain `-D` because the test runs in a forked + * JVM that does not inherit the Gradle JVM's system properties. */ @Test fun exportsTheCorpusForTheInstrumentedTest() { - assumeTrue("set -D$EXPORT_PROPERTY=true to regenerate the corpus", System.getProperty(EXPORT_PROPERTY) == "true") + assumeTrue("pass -Pamethyst.qr.corpus.export=true to regenerate the corpus", System.getProperty(EXPORT_PROPERTY) == "true") val dir = File(ASSET_DIR) dir.mkdirs() QrCorpus.all().forEach { fixture -> - ImageIO.write(fixture.image, "png", File(dir, "${fixture.name}.png")) + // 8-bit grey, not RGB: every fixture is greyscale in content, and storing three + // identical channels tripled the size of a corpus that has to live in the repo. + ImageIO.write(toGrayscale(fixture.image), "png", File(dir, "${fixture.name}.png")) } File(dir, "expected.tsv").writeText( @@ -99,6 +103,14 @@ class QrCorpusBaselineTest { ) } + private fun toGrayscale(source: BufferedImage): BufferedImage { + val out = BufferedImage(source.width, source.height, BufferedImage.TYPE_BYTE_GRAY) + val g = out.createGraphics() + g.drawImage(source, 0, 0, null) + g.dispose() + return out + } + private data class Row( val category: String, val passed: Int, @@ -180,7 +192,7 @@ class QrCorpusBaselineTest { } companion object { - private const val EXPORT_PROPERTY = "qr.corpus.export" + private const val EXPORT_PROPERTY = "amethyst.qr.corpus.export" private const val ASSET_DIR = "src/androidTest/assets/qr" private const val BASELINE_FILE = "baseline.tsv" } From e39d57e08f25373acff27c5747f5df01de618104 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 05:19:29 +0000 Subject: [PATCH 010/127] refactor: move the training-log domain to commons, open Workouts on Mine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes that make My Fitness a first-class part of the app rather than a screen hanging off an icon. **The domain moves to commons.** DetectedWorkout, WorkoutStats and TrainingLog are pure Kotlin over quartz types with no Android dependency, so per ARCHITECTURE.md they belonged in commons all along; amethyst keeps only the Health Connect adapters (HealthConnectManager, ExerciseTypeMapper, and WorkoutMerger, which stitches split sessions and is meaningful only for a watch). They land in jvmAndroid, not commonMain: the day bucketing behind streaks and active days is java.time, which will not compile for iOS, and the module's preference chain is commonMain -> jvmAndroid -> platform-specific. jvmAndroid is shared by Android and Desktop, which is what this needs — the desktop app has no Health Connect but does have the user's published workouts, so the same dashboard can run there. Their 30 tests move to commons/jvmTest, which is what proves that: they now run on the JVM target, not only under the Android app. **Workouts opens on Mine.** The section is about the user's own training, so that is the landing tab; other people's workouts are the second one. The dashboard body was extracted as MyFitnessContent so the tab and the standalone drawer destination render exactly the same thing. The chart icon in the top bar is gone with its string — the tab supersedes it, and two routes to the same content from the same screen is one too many. That also retires the extraActions slot added to UserDrawerSearchTopBar last commit, since nothing uses it now; an unused parameter on a shared component is debt. The follow-list spinner only filters the Following feed, so it now shows only on that tab. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019egdJyBHnrATZHjs86up8f --- .../workouts/health/HealthConnectManager.kt | 1 + .../service/workouts/health/WorkoutMerger.kt | 1 + .../topbars/UserDrawerSearchTopBar.kt | 5 - .../loggedIn/workouts/WorkoutsScreen.kt | 84 ++++++++++++++-- .../loggedIn/workouts/WorkoutsTopBar.kt | 26 +---- .../workouts/fitness/MyFitnessScreen.kt | 97 +++++++++++-------- .../workouts/fitness/MyFitnessViewModel.kt | 8 +- .../suggestion/DetectedWorkoutCarousel.kt | 2 +- .../suggestion/WorkoutSuggestionShared.kt | 2 +- .../workouts/health/WorkoutMergerTest.kt | 1 + .../workouts/NewWorkoutPrefillTest.kt | 2 +- .../composeResources/values/strings.xml | 3 +- .../commons/fitness}/DetectedWorkout.kt | 2 +- .../amethyst/commons/fitness}/TrainingLog.kt | 2 +- .../amethyst/commons/fitness}/WorkoutStats.kt | 2 +- .../fitness}/PublishedWorkoutMappingTest.kt | 2 +- .../commons/fitness}/TrainingLogTest.kt | 2 +- .../commons/fitness}/WorkoutStatsTest.kt | 2 +- 18 files changed, 151 insertions(+), 93 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health => commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness}/DetectedWorkout.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health => commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness}/TrainingLog.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health => commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness}/WorkoutStats.kt (99%) rename {amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health => commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness}/PublishedWorkoutMappingTest.kt (98%) rename {amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health => commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness}/TrainingLogTest.kt (99%) rename {amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health => commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness}/WorkoutStatsTest.kt (99%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt index 5c9c9befef..893a51c2c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/HealthConnectManager.kt @@ -34,6 +34,7 @@ import androidx.health.connect.client.records.TotalCaloriesBurnedRecord import androidx.health.connect.client.request.AggregateRequest import androidx.health.connect.client.request.ReadRecordsRequest import androidx.health.connect.client.time.TimeRangeFilter +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMerger.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMerger.kt index 90e10fa22a..12756018c8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMerger.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMerger.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.workouts.health +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType import kotlin.math.roundToInt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt index e834ee9e99..f74d25ca16 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/UserDrawerSearchTopBar.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.navigation.topbars import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton @@ -67,7 +66,6 @@ fun UserDrawerSearchTopBar( accountViewModel: AccountViewModel, nav: INav, baseFilter: SearchQuery?, - extraActions: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit, ) { ShorterTopAppBar( @@ -82,9 +80,6 @@ fun UserDrawerSearchTopBar( }, navigationIcon = { TopBarNavigationIcon(accountViewModel, nav) }, actions = { - // Screen-specific actions sit left of search, which stays the rightmost affordance - // everywhere it appears. - extraActions() IconButton(onClick = { nav.nav(searchRouteFor(baseFilter)) }) { SearchIcon(modifier = Size22Modifier, MaterialTheme.colorScheme.placeholderText) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt index c4d562485f..0b72b51d20 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt @@ -20,10 +20,25 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SecondaryTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.workouts_tab_following +import com.vitorpamplona.amethyst.commons.resources.workouts_tab_mine import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState @@ -37,6 +52,10 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness.MyFitnessContent +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.TabRowHeight +import kotlinx.coroutines.launch @Composable fun WorkoutsScreen( @@ -50,6 +69,7 @@ fun WorkoutsScreen( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun WorkoutsScreen( workoutsFeedContentState: FeedContentState, @@ -60,10 +80,20 @@ fun WorkoutsScreen( WatchAccountForWorkoutsScreen(workoutsFeedContentState = workoutsFeedContentState, accountViewModel = accountViewModel) WorkoutsFilterAssemblerSubscription(accountViewModel) + // Mine first. The section is about the user's own training; other people's workouts are the + // second tab, not the landing page. + val pagerState = rememberPagerState { 2 } + val onFollowingTab = pagerState.currentPage == FOLLOWING_TAB + DisappearingScaffold( isInvertedLayout = false, topBar = { - WorkoutsTopBar(accountViewModel, nav) + Column { + // The follow-list spinner filters the Following feed, so it only belongs on that + // tab; the Mine tab shows the plain title instead. + WorkoutsTopBar(accountViewModel, nav, showFeedFilter = onFollowingTab) + WorkoutsTabs(pagerState) + } }, bottomBar = { AppBottomBar(Route.Workouts, nav, accountViewModel) { route -> @@ -81,20 +111,54 @@ fun WorkoutsScreen( }, accountViewModel = accountViewModel, ) { - RefresheableBox(workoutsFeedContentState, true) { - SaveableFeedContentState(workoutsFeedContentState, scrollStateKey = ScrollStateKeys.WORKOUTS_SCREEN) { listState -> - RenderFeedContentState( - feedContentState = workoutsFeedContentState, - accountViewModel = accountViewModel, - listState = listState, - nav = nav, - routeForLastRead = "WorkoutsFeed", - ) + HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { page -> + when (page) { + MINE_TAB -> MyFitnessContent(accountViewModel, nav) + + else -> + RefresheableBox(workoutsFeedContentState, true) { + SaveableFeedContentState(workoutsFeedContentState, scrollStateKey = ScrollStateKeys.WORKOUTS_SCREEN) { listState -> + RenderFeedContentState( + feedContentState = workoutsFeedContentState, + accountViewModel = accountViewModel, + listState = listState, + nav = nav, + routeForLastRead = "WorkoutsFeed", + ) + } + } } } } } +private const val MINE_TAB = 0 +private const val FOLLOWING_TAB = 1 + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun WorkoutsTabs(pagerState: PagerState) { + val scope = rememberCoroutineScope() + + SecondaryTabRow( + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + selectedTabIndex = pagerState.currentPage, + modifier = TabRowHeight, + ) { + Tab( + selected = pagerState.currentPage == MINE_TAB, + onClick = { scope.launch { pagerState.animateScrollToPage(MINE_TAB) } }, + text = { Text(stringRes(Res.string.workouts_tab_mine)) }, + ) + Tab( + selected = pagerState.currentPage == FOLLOWING_TAB, + onClick = { scope.launch { pagerState.animateScrollToPage(FOLLOWING_TAB) } }, + text = { Text(stringRes(Res.string.workouts_tab_following)) }, + ) + } +} + @Composable fun WatchAccountForWorkoutsScreen( workoutsFeedContentState: FeedContentState, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt index 3b172486c6..e5f6d2dbe1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt @@ -20,36 +20,29 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.commons.icons.symbols.Icon -import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.my_fitness_open import com.vitorpamplona.amethyst.commons.resources.select_list_to_filter import com.vitorpamplona.amethyst.commons.search.SearchSeed import com.vitorpamplona.amethyst.commons.search.asSearchQuery import com.vitorpamplona.amethyst.ui.navigation.navs.INav -import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.topbars.FeedFilterSpinner import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar import com.vitorpamplona.amethyst.ui.screen.FeedDefinition import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Size22Modifier -import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent @Composable fun WorkoutsTopBar( accountViewModel: AccountViewModel, nav: INav, + showFeedFilter: Boolean, ) { val list by accountViewModel.account.settings.defaultWorkoutsFollowList .collectAsStateWithLifecycle() @@ -59,21 +52,8 @@ fun WorkoutsTopBar( val me = accountViewModel.userProfile().pubkeyHex val seed = remember(list, me) { SearchSeed.merge(SearchSeed.ofKinds(WorkoutRecordEvent.KIND), list.asSearchQuery(me)) } - UserDrawerSearchTopBar( - accountViewModel, - nav, - seed, - extraActions = { - IconButton(onClick = { nav.nav(Route.MyFitness) }) { - Icon( - symbol = MaterialSymbols.AutoMirrored.ShowChart, - contentDescription = stringRes(Res.string.my_fitness_open), - modifier = Size22Modifier, - tint = MaterialTheme.colorScheme.placeholderText, - ) - } - }, - ) { + UserDrawerSearchTopBar(accountViewModel, nav, seed) { + if (!showFeedFilter) return@UserDrawerSearchTopBar WorkoutsTopNavFilterBar( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt index 3af4c0422d..3cb580aa41 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt @@ -55,6 +55,8 @@ import androidx.health.connect.client.PermissionController import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout +import com.vitorpamplona.amethyst.commons.fitness.WorkoutStats import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.resources.Res @@ -91,9 +93,7 @@ import com.vitorpamplona.amethyst.commons.resources.my_fitness_window import com.vitorpamplona.amethyst.commons.resources.my_fitness_window_note import com.vitorpamplona.amethyst.commons.resources.my_fitness_workouts import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_connect_details -import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager -import com.vitorpamplona.amethyst.service.workouts.health.WorkoutStats import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -107,12 +107,15 @@ import com.vitorpamplona.amethyst.ui.stringRes * The user's own training, summarised: how much they did this week against last, what they * spent the time on, their best efforts, and how many days in a row they have shown up. * - * This is the reason Amethyst reads Health Connect at all — the numbers are for the person - * who recorded them. Publishing one as a note is an optional action from the workout list, - * never a precondition for seeing any of this. + * This is the reason Amethyst reads Health Connect at all — the numbers are for the person who + * recorded them. Publishing one as a note is an optional action from the workout list, never a + * precondition for seeing any of this. + * + * Carries no scaffolding of its own so it can be the "Mine" tab of the Workouts screen as well + * as its own destination from the drawer. */ @Composable -fun MyFitnessScreen( +fun MyFitnessContent( accountViewModel: AccountViewModel, nav: INav, ) { @@ -136,45 +139,57 @@ fun MyFitnessScreen( onPauseOrDispose {} } + val openRationale = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) } + val requestPermissions = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) } + + when (val current = state) { + MyFitnessViewModel.State.Loading -> CenteredBox { CircularProgressIndicator() } + + is MyFitnessViewModel.State.Ready -> + if (current.report.isEmpty) { + // Nothing logged yet. Offering Health Connect is the useful thing to do when it + // could fill the screen; otherwise just say the log is empty. + if (current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE) { + ConnectPrompt(onDetails = openRationale, onConnect = requestPermissions) + } else { + CenteredBox { + Text( + text = stringRes(Res.string.my_fitness_empty), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 32.dp), + ) + } + } + } else { + Dashboard( + report = current.report, + // Only offered when it would actually add something: a device with no + // provider gets no banner to act on. + showConnectBanner = current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE, + onDetails = openRationale, + onConnect = requestPermissions, + ) { workout, label -> + nav.nav(workout.toNewWorkoutRoute(label)) + } + } + } +} + +/** + * The standalone destination, reached from the drawer or a pinned bottom-bar slot. Inside the + * Workouts screen the same content is a tab instead — see [MyFitnessContent]. + */ +@Composable +fun MyFitnessScreen( + accountViewModel: AccountViewModel, + nav: INav, +) { Scaffold( topBar = { TopBarWithBackButton(stringRes(Res.string.my_fitness_title), nav) }, ) { padding -> Surface(modifier = Modifier.padding(padding)) { - when (val current = state) { - MyFitnessViewModel.State.Loading -> CenteredBox { CircularProgressIndicator() } - - is MyFitnessViewModel.State.Ready -> - if (current.report.isEmpty) { - // Nothing logged yet. Offering Health Connect is the useful thing to do - // when it could fill the screen; otherwise just say the log is empty. - if (current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE) { - ConnectPrompt( - onDetails = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) }, - onConnect = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) }, - ) - } else { - CenteredBox { - Text( - text = stringRes(Res.string.my_fitness_empty), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = 32.dp), - ) - } - } - } else { - Dashboard( - report = current.report, - // Only offered when it would actually add something: a device with no - // provider gets no banner to act on. - showConnectBanner = current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE, - onDetails = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) }, - onConnect = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) }, - ) { workout, label -> - nav.nav(workout.toNewWorkoutRoute(label)) - } - } - } + MyFitnessContent(accountViewModel, nav) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt index e2c38ea854..a893649434 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessViewModel.kt @@ -25,12 +25,12 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout +import com.vitorpamplona.amethyst.commons.fitness.TrainingLog +import com.vitorpamplona.amethyst.commons.fitness.WorkoutStats +import com.vitorpamplona.amethyst.commons.fitness.toDetectedWorkout import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager -import com.vitorpamplona.amethyst.service.workouts.health.TrainingLog -import com.vitorpamplona.amethyst.service.workouts.health.WorkoutStats -import com.vitorpamplona.amethyst.service.workouts.health.toDetectedWorkout import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt index 58528ea2d0..3258c2223f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/DetectedWorkoutCarousel.kt @@ -57,6 +57,7 @@ import androidx.health.connect.client.PermissionController import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.resources.Res @@ -67,7 +68,6 @@ import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_connect_m import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_connect_title import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_distance_km import com.vitorpamplona.amethyst.model.BooleanType -import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/WorkoutSuggestionShared.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/WorkoutSuggestionShared.kt index 5030b4b872..20f6d93972 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/WorkoutSuggestionShared.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/suggestion/WorkoutSuggestionShared.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.suggestion import android.text.format.DateUtils -import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.quartz.experimental.fitness.workout.tags.SourceTag diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMergerTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMergerTest.kt index f8ea473932..33d5732a1a 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMergerTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutMergerTest.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.workouts.health +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType import org.junit.Assert.assertEquals import org.junit.Assert.assertNull diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutPrefillTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutPrefillTest.kt index 8d9fcf289e..f3b35e345c 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutPrefillTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutPrefillTest.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts -import com.vitorpamplona.amethyst.service.workouts.health.DetectedWorkout +import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.suggestion.toNewWorkoutRoute import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType diff --git a/commons/src/commonMain/composeResources/values/strings.xml b/commons/src/commonMain/composeResources/values/strings.xml index 5f22370237..f9ed7017d6 100644 --- a/commons/src/commonMain/composeResources/values/strings.xml +++ b/commons/src/commonMain/composeResources/values/strings.xml @@ -683,8 +683,9 @@ Nothing leaves your phone until you tap a suggestion and publish the post yourself. Amethyst has no server: the post goes to the Nostr relays you configured. The whole feature is optional. Turn it off under Settings → Compose, or revoke the permissions in Health Connect at any time — the rest of Amethyst keeps working. Read the full privacy policy + Mine + Following My Fitness - My Fitness Last 4 weeks Health Connect serves the last 30 days, so Amethyst summarises four whole weeks. Nothing here leaves your phone. This week diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt rename to commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt index f92e7d141f..05c4aa8129 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/DetectedWorkout.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.service.workouts.health +package com.vitorpamplona.amethyst.commons.fitness import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt rename to commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt index 639ccee6c4..ad34dd5671 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLog.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.service.workouts.health +package com.vitorpamplona.amethyst.commons.fitness import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent import kotlin.math.abs diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/WorkoutStats.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt rename to commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/WorkoutStats.kt index eac9ea8ff0..88437973b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStats.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/WorkoutStats.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.service.workouts.health +package com.vitorpamplona.amethyst.commons.fitness import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt similarity index 98% rename from amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt rename to commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt index e8c3ee06a6..6931914b53 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/PublishedWorkoutMappingTest.kt +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.service.workouts.health +package com.vitorpamplona.amethyst.commons.fitness import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent import com.vitorpamplona.quartz.experimental.fitness.workout.tags.AvgHeartRateTag diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt similarity index 99% rename from amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt rename to commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt index e5deae11a4..51975cd716 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/TrainingLogTest.kt +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.service.workouts.health +package com.vitorpamplona.amethyst.commons.fitness import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType import org.junit.Assert.assertEquals diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/WorkoutStatsTest.kt similarity index 99% rename from amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt rename to commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/WorkoutStatsTest.kt index b8f728ebf3..25c8ec021c 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/workouts/health/WorkoutStatsTest.kt +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/WorkoutStatsTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.service.workouts.health +package com.vitorpamplona.amethyst.commons.fitness import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType import org.junit.Assert.assertEquals From c5526f3575995c041367243b89829514ae587f26 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 13:11:48 +0000 Subject: [PATCH 011/127] refactor: filter Workouts by "Mine" from the top nav instead of tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Mine/Following split was built as a SecondaryTabRow + HorizontalPager, which no other feed screen in the app does. Every other one narrows its feed through the top-nav FeedFilterSpinner, and TopFilter.Mine already exists for exactly this — music, playlists, communities, badges and git repositories all offer it, and FeedTopNavFilterState already maps it to MineFeedFlow against the signer's own pubkey. So Workouts now offers Mine in that spinner and the tabs are gone. It needed no new filtering machinery: liveWorkoutsFollowLists already runs through the same topNavFilterFlow, so both the feed scan and the relay subscription follow the selection. The only addition is a workoutRoutes catalog — the kind3 list plus Mine — mirroring the per-screen flows the file already keeps for music, git repositories, highlights and podcasts. That duplication is the established convention here rather than something to factor out. MyFitnessContent is inlined back into MyFitnessScreen. It was extracted only so the tab and the standalone destination could share a body; with the tab gone it had one caller, and a public composable with no second caller is API surface for nothing. Note the consequence: the Workouts screen no longer offers a route to the My Fitness dashboard, since the tab was it and the chart icon went with the previous commit. It stays reachable from the drawer and a pinned bottom-bar slot, which is how every other destination is reached. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019egdJyBHnrATZHjs86up8f --- .../amethyst/ui/screen/TopNavFilterState.kt | 23 +++++ .../loggedIn/workouts/WorkoutsScreen.kt | 84 +++---------------- .../loggedIn/workouts/WorkoutsTopBar.kt | 4 +- .../workouts/fitness/MyFitnessScreen.kt | 81 ++++++++---------- .../composeResources/values/strings.xml | 2 - 5 files changed, 68 insertions(+), 126 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt index ae384f50e3..a62ae6e088 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/TopNavFilterState.kt @@ -337,6 +337,24 @@ class TopNavFilterState( ) } + private val _workoutRoutes = + combineTransform( + livePeopleListsFlow, + liveInterestFlows, + ) { peopleLists, interests -> + checkNotInMainThread() + emit( + listOf( + // Workout records can be narrowed by author, hashtag and geohash, so this + // mirrors the kind3 catalog plus "Mine" — the user's own training. + listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow, mineFollow), + peopleLists, + interests, + listOf(muteListFollow), + ).flatten().toImmutableList(), + ) + } + private val _highlightsRoutes = combineTransform( livePeopleListsFlow, @@ -488,6 +506,11 @@ class TopNavFilterState( .flowOn(Dispatchers.IO) .stateIn(scope, SharingStarted.Eagerly, persistentListOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow, mineFollow, muteListFollow)) + val workoutRoutes = + _workoutRoutes + .flowOn(Dispatchers.IO) + .stateIn(scope, SharingStarted.Eagerly, persistentListOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow, mineFollow, muteListFollow)) + val highlightsRoutes = _highlightsRoutes .flowOn(Dispatchers.IO) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt index 0b72b51d20..c4d562485f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsScreen.kt @@ -20,25 +20,10 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.PagerState -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SecondaryTabRow -import androidx.compose.material3.Tab -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.workouts_tab_following -import com.vitorpamplona.amethyst.commons.resources.workouts_tab_mine import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState @@ -52,10 +37,6 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness.MyFitnessContent -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.TabRowHeight -import kotlinx.coroutines.launch @Composable fun WorkoutsScreen( @@ -69,7 +50,6 @@ fun WorkoutsScreen( ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun WorkoutsScreen( workoutsFeedContentState: FeedContentState, @@ -80,20 +60,10 @@ fun WorkoutsScreen( WatchAccountForWorkoutsScreen(workoutsFeedContentState = workoutsFeedContentState, accountViewModel = accountViewModel) WorkoutsFilterAssemblerSubscription(accountViewModel) - // Mine first. The section is about the user's own training; other people's workouts are the - // second tab, not the landing page. - val pagerState = rememberPagerState { 2 } - val onFollowingTab = pagerState.currentPage == FOLLOWING_TAB - DisappearingScaffold( isInvertedLayout = false, topBar = { - Column { - // The follow-list spinner filters the Following feed, so it only belongs on that - // tab; the Mine tab shows the plain title instead. - WorkoutsTopBar(accountViewModel, nav, showFeedFilter = onFollowingTab) - WorkoutsTabs(pagerState) - } + WorkoutsTopBar(accountViewModel, nav) }, bottomBar = { AppBottomBar(Route.Workouts, nav, accountViewModel) { route -> @@ -111,54 +81,20 @@ fun WorkoutsScreen( }, accountViewModel = accountViewModel, ) { - HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { page -> - when (page) { - MINE_TAB -> MyFitnessContent(accountViewModel, nav) - - else -> - RefresheableBox(workoutsFeedContentState, true) { - SaveableFeedContentState(workoutsFeedContentState, scrollStateKey = ScrollStateKeys.WORKOUTS_SCREEN) { listState -> - RenderFeedContentState( - feedContentState = workoutsFeedContentState, - accountViewModel = accountViewModel, - listState = listState, - nav = nav, - routeForLastRead = "WorkoutsFeed", - ) - } - } + RefresheableBox(workoutsFeedContentState, true) { + SaveableFeedContentState(workoutsFeedContentState, scrollStateKey = ScrollStateKeys.WORKOUTS_SCREEN) { listState -> + RenderFeedContentState( + feedContentState = workoutsFeedContentState, + accountViewModel = accountViewModel, + listState = listState, + nav = nav, + routeForLastRead = "WorkoutsFeed", + ) } } } } -private const val MINE_TAB = 0 -private const val FOLLOWING_TAB = 1 - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun WorkoutsTabs(pagerState: PagerState) { - val scope = rememberCoroutineScope() - - SecondaryTabRow( - containerColor = MaterialTheme.colorScheme.background, - contentColor = MaterialTheme.colorScheme.onBackground, - selectedTabIndex = pagerState.currentPage, - modifier = TabRowHeight, - ) { - Tab( - selected = pagerState.currentPage == MINE_TAB, - onClick = { scope.launch { pagerState.animateScrollToPage(MINE_TAB) } }, - text = { Text(stringRes(Res.string.workouts_tab_mine)) }, - ) - Tab( - selected = pagerState.currentPage == FOLLOWING_TAB, - onClick = { scope.launch { pagerState.animateScrollToPage(FOLLOWING_TAB) } }, - text = { Text(stringRes(Res.string.workouts_tab_following)) }, - ) - } -} - @Composable fun WatchAccountForWorkoutsScreen( workoutsFeedContentState: FeedContentState, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt index e5f6d2dbe1..4b0214055c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/WorkoutsTopBar.kt @@ -42,7 +42,6 @@ import com.vitorpamplona.quartz.experimental.fitness.workout.WorkoutRecordEvent fun WorkoutsTopBar( accountViewModel: AccountViewModel, nav: INav, - showFeedFilter: Boolean, ) { val list by accountViewModel.account.settings.defaultWorkoutsFollowList .collectAsStateWithLifecycle() @@ -53,7 +52,6 @@ fun WorkoutsTopBar( val seed = remember(list, me) { SearchSeed.merge(SearchSeed.ofKinds(WorkoutRecordEvent.KIND), list.asSearchQuery(me)) } UserDrawerSearchTopBar(accountViewModel, nav, seed) { - if (!showFeedFilter) return@UserDrawerSearchTopBar WorkoutsTopNavFilterBar( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, @@ -70,7 +68,7 @@ private fun WorkoutsTopNavFilterBar( accountViewModel: AccountViewModel, onChange: (FeedDefinition) -> Unit, ) { - val allLists by followListsModel.kind3GlobalPeopleRoutes.collectAsStateWithLifecycle() + val allLists by followListsModel.workoutRoutes.collectAsStateWithLifecycle() FeedFilterSpinner( placeholderCode = listName, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt index 3cb580aa41..125770000a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt @@ -111,11 +111,10 @@ import com.vitorpamplona.amethyst.ui.stringRes * recorded them. Publishing one as a note is an optional action from the workout list, never a * precondition for seeing any of this. * - * Carries no scaffolding of its own so it can be the "Mine" tab of the Workouts screen as well - * as its own destination from the drawer. + * Reached from the drawer, or from a bottom-bar slot the user pinned. */ @Composable -fun MyFitnessContent( +fun MyFitnessScreen( accountViewModel: AccountViewModel, nav: INav, ) { @@ -142,54 +141,42 @@ fun MyFitnessContent( val openRationale = { context.startActivity(Intent(context, HealthConnectRationaleActivity::class.java)) } val requestPermissions = { permissionLauncher.launch(HealthConnectManager.PERMISSIONS) } - when (val current = state) { - MyFitnessViewModel.State.Loading -> CenteredBox { CircularProgressIndicator() } - - is MyFitnessViewModel.State.Ready -> - if (current.report.isEmpty) { - // Nothing logged yet. Offering Health Connect is the useful thing to do when it - // could fill the screen; otherwise just say the log is empty. - if (current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE) { - ConnectPrompt(onDetails = openRationale, onConnect = requestPermissions) - } else { - CenteredBox { - Text( - text = stringRes(Res.string.my_fitness_empty), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = 32.dp), - ) - } - } - } else { - Dashboard( - report = current.report, - // Only offered when it would actually add something: a device with no - // provider gets no banner to act on. - showConnectBanner = current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE, - onDetails = openRationale, - onConnect = requestPermissions, - ) { workout, label -> - nav.nav(workout.toNewWorkoutRoute(label)) - } - } - } -} - -/** - * The standalone destination, reached from the drawer or a pinned bottom-bar slot. Inside the - * Workouts screen the same content is a tab instead — see [MyFitnessContent]. - */ -@Composable -fun MyFitnessScreen( - accountViewModel: AccountViewModel, - nav: INav, -) { Scaffold( topBar = { TopBarWithBackButton(stringRes(Res.string.my_fitness_title), nav) }, ) { padding -> Surface(modifier = Modifier.padding(padding)) { - MyFitnessContent(accountViewModel, nav) + when (val current = state) { + MyFitnessViewModel.State.Loading -> CenteredBox { CircularProgressIndicator() } + + is MyFitnessViewModel.State.Ready -> + if (current.report.isEmpty) { + // Nothing logged yet. Offering Health Connect is the useful thing to do + // when it could fill the screen; otherwise just say the log is empty. + if (current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE) { + ConnectPrompt(onDetails = openRationale, onConnect = requestPermissions) + } else { + CenteredBox { + Text( + text = stringRes(Res.string.my_fitness_empty), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 32.dp), + ) + } + } + } else { + Dashboard( + report = current.report, + // Only offered when it would actually add something: a device with no + // provider gets no banner to act on. + showConnectBanner = current.healthConnect == MyFitnessViewModel.HealthConnectStatus.AVAILABLE, + onDetails = openRationale, + onConnect = requestPermissions, + ) { workout, label -> + nav.nav(workout.toNewWorkoutRoute(label)) + } + } + } } } } diff --git a/commons/src/commonMain/composeResources/values/strings.xml b/commons/src/commonMain/composeResources/values/strings.xml index f9ed7017d6..7f46225d00 100644 --- a/commons/src/commonMain/composeResources/values/strings.xml +++ b/commons/src/commonMain/composeResources/values/strings.xml @@ -683,8 +683,6 @@ Nothing leaves your phone until you tap a suggestion and publish the post yourself. Amethyst has no server: the post goes to the Nostr relays you configured. The whole feature is optional. Turn it off under Settings → Compose, or revoke the permissions in Health Connect at any time — the rest of Amethyst keeps working. Read the full privacy policy - Mine - Following My Fitness Last 4 weeks Health Connect serves the last 30 days, so Amethyst summarises four whole weeks. Nothing here leaves your phone. From ec8a5fa573a3abf5bc83dcf6d7882b2201eea71d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 17 Sep 2026 09:19:50 -0400 Subject: [PATCH 012/127] fix: don't offer to re-share an already-published workout My Fitness builds its log from both Health Connect and the user's own kind 1301 events, but every row in Recent workouts carried a "Share this workout" button. On a relay-sourced entry that opened the composer prefilled and one tap from publishing a second kind 1301 for the same effort. WorkoutOrigin already recorded where each workout came from and nothing read it; the button is now gated on it. Also tidies the dashboard's copy and its empty cells: - This week showed "0 kcal" (and would show "0.0 mi" for a strength-only week) where the weekly-average card already omits metrics the window has none of. Distance and calories now appear only when this week or last week has one, so a drop to zero still renders its trend. - The footer note explained the four-week window as a Health Connect limit and claimed nothing leaves the phone. Neither holds once the log is fed by published events, which by definition already left. - Drops my_fitness_unavailable, unused since the log stopped depending on Health Connect, so it isn't sent out for translation. Co-Authored-By: Claude Opus 5 (1M context) --- .../workouts/fitness/MyFitnessScreen.kt | 100 +++++++++++------- .../composeResources/values/strings.xml | 3 +- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt index 125770000a..bf1886b371 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt @@ -56,6 +56,7 @@ import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout +import com.vitorpamplona.amethyst.commons.fitness.WorkoutOrigin import com.vitorpamplona.amethyst.commons.fitness.WorkoutStats import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -284,34 +285,50 @@ private fun ThisWeekCard( miles: Boolean, ) { SectionCard(stringRes(Res.string.my_fitness_this_week)) { - StatGrid( - listOf( - StatCell( - stringRes(Res.string.my_fitness_workouts), - report.thisWeek.workoutCount.toString(), - null, - WorkoutStats.percentChange(report.thisWeek.workoutCount.toDouble(), report.previousWeek.workoutCount.toDouble()), - ), - StatCell( - stringRes(Res.string.my_fitness_time), - formatDuration(report.thisWeek.durationSeconds), - null, - WorkoutStats.percentChange(report.thisWeek.durationSeconds.toDouble(), report.previousWeek.durationSeconds.toDouble()), - ), - StatCell( - stringRes(Res.string.my_fitness_distance), - formatDistanceValue(report.thisWeek.distanceMeters, miles), - distanceUnit(miles), - WorkoutStats.percentChange(report.thisWeek.distanceMeters, report.previousWeek.distanceMeters), - ), - StatCell( - stringRes(Res.string.my_fitness_calories), - report.thisWeek.calories.toString(), - stringRes(Res.string.my_fitness_unit_kcal), - WorkoutStats.percentChange(report.thisWeek.calories.toDouble(), report.previousWeek.calories.toDouble()), - ), - ), - ) + // Workouts and time are always worth a cell. Distance and calories are not: a week of + // strength work has neither, and a column of zeroes reads as missing data rather than + // as "this activity doesn't have that metric". Last week still counts, so a drop to + // zero keeps its cell and shows the fall. + val cells = + buildList { + add( + StatCell( + stringRes(Res.string.my_fitness_workouts), + report.thisWeek.workoutCount.toString(), + null, + WorkoutStats.percentChange(report.thisWeek.workoutCount.toDouble(), report.previousWeek.workoutCount.toDouble()), + ), + ) + add( + StatCell( + stringRes(Res.string.my_fitness_time), + formatDuration(report.thisWeek.durationSeconds), + null, + WorkoutStats.percentChange(report.thisWeek.durationSeconds.toDouble(), report.previousWeek.durationSeconds.toDouble()), + ), + ) + if (report.thisWeek.distanceMeters > 0 || report.previousWeek.distanceMeters > 0) { + add( + StatCell( + stringRes(Res.string.my_fitness_distance), + formatDistanceValue(report.thisWeek.distanceMeters, miles), + distanceUnit(miles), + WorkoutStats.percentChange(report.thisWeek.distanceMeters, report.previousWeek.distanceMeters), + ), + ) + } + if (report.thisWeek.calories > 0 || report.previousWeek.calories > 0) { + add( + StatCell( + stringRes(Res.string.my_fitness_calories), + report.thisWeek.calories.toString(), + stringRes(Res.string.my_fitness_unit_kcal), + WorkoutStats.percentChange(report.thisWeek.calories.toDouble(), report.previousWeek.calories.toDouble()), + ), + ) + } + } + StatGrid(cells) } } @@ -333,14 +350,16 @@ private fun WindowTotalsCard( val cells = buildList { add(StatCell(stringRes(Res.string.my_fitness_time), formatDuration(report.weeklyAverage.durationSeconds), null, null)) - add( - StatCell( - stringRes(Res.string.my_fitness_distance), - formatDistanceValue(report.weeklyAverage.distanceMeters, miles), - distanceUnit(miles), - null, - ), - ) + if (report.windowTotals.distanceMeters > 0) { + add( + StatCell( + stringRes(Res.string.my_fitness_distance), + formatDistanceValue(report.weeklyAverage.distanceMeters, miles), + distanceUnit(miles), + null, + ), + ) + } if (report.windowTotals.steps > 0) { add(StatCell(stringRes(Res.string.my_fitness_steps), report.weeklyAverage.steps.toString(), null, null)) } @@ -492,8 +511,13 @@ private fun RecentWorkouts( overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f), ) - TextButton(onClick = { onShare(workout, label) }) { - Text(stringRes(Res.string.my_fitness_share), style = MaterialTheme.typography.labelMedium) + // Only what is still unpublished can be shared. A workout that came + // back from the relays is already posted: offering to share it again + // would publish a second kind 1301 for the same effort. + if (workout.origin != WorkoutOrigin.PUBLISHED) { + TextButton(onClick = { onShare(workout, label) }) { + Text(stringRes(Res.string.my_fitness_share), style = MaterialTheme.typography.labelMedium) + } } } Text( diff --git a/commons/src/commonMain/composeResources/values/strings.xml b/commons/src/commonMain/composeResources/values/strings.xml index 7f46225d00..4550aca749 100644 --- a/commons/src/commonMain/composeResources/values/strings.xml +++ b/commons/src/commonMain/composeResources/values/strings.xml @@ -685,7 +685,7 @@ Read the full privacy policy My Fitness Last 4 weeks - Health Connect serves the last 30 days, so Amethyst summarises four whole weeks. Nothing here leaves your phone. + The last four whole weeks of your training: the workouts you have published, plus anything Health Connect records on this phone. Amethyst works the summary out on the device and publishes none of it. This week vs last week Weekly average @@ -708,7 +708,6 @@ Highest heart rate Recent workouts Nothing logged in the last 4 weeks. Workouts you post, and any your watch records, show up here. - Health Connect is not available on this device, so Amethyst has no workouts to summarise. Track your training Connect Health Connect to see your weekly totals, week-over-week trend, best efforts and training streak. Everything stays on your phone. Connect Health Connect to add the workouts your watch records, with heart rate, steps and climb. From 0debf8a0233be1bd06debb7da7dba8b4b6e6ea87 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 17 Sep 2026 10:48:07 -0400 Subject: [PATCH 013/127] fix: don't offer to re-share a workout shared from Health Connect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit gated the share button on WorkoutOrigin, which closes only half the case. When a user shares a workout their watch recorded, TrainingLog.merge keeps the Health Connect copy and drops the published one, so the survivor is still WorkoutOrigin.HEALTH_CONNECT even though a kind 1301 for it is already out there — and the dashboard went on offering to share it. That is the likelier route to a duplicate, since sharing from Health Connect is the feature's main flow. Origin answers "where did this data come from", which is the wrong question. DetectedWorkout now also carries alreadyPublished, the merge hands it to the surviving copy, and the published-event mapper sets it too, so the dashboard has one field to read. Confirmed on device: a Health Connect ride shared from My Fitness keeps its metrics, does not duplicate, and loses its share button, while the six unshared sessions keep theirs. Co-Authored-By: Claude Opus 5 (1M context) --- .../workouts/fitness/MyFitnessScreen.kt | 10 ++-- .../commons/fitness/DetectedWorkout.kt | 10 ++++ .../amethyst/commons/fitness/TrainingLog.kt | 18 ++++++- .../fitness/PublishedWorkoutMappingTest.kt | 3 ++ .../commons/fitness/TrainingLogTest.kt | 49 +++++++++++++++++++ 5 files changed, 84 insertions(+), 6 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt index bf1886b371..ef3405c8e9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/fitness/MyFitnessScreen.kt @@ -56,7 +56,6 @@ import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.fitness.DetectedWorkout -import com.vitorpamplona.amethyst.commons.fitness.WorkoutOrigin import com.vitorpamplona.amethyst.commons.fitness.WorkoutStats import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -511,10 +510,11 @@ private fun RecentWorkouts( overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f), ) - // Only what is still unpublished can be shared. A workout that came - // back from the relays is already posted: offering to share it again - // would publish a second kind 1301 for the same effort. - if (workout.origin != WorkoutOrigin.PUBLISHED) { + // Only what is still unpublished can be shared. Offering to share a + // workout that is already posted would publish a second kind 1301 for + // the same effort — whether it came back from a relay, or is the + // Health Connect copy of one the user shared earlier. + if (!workout.alreadyPublished) { TextButton(onClick = { onShare(workout, label) }) { Text(stringRes(Res.string.my_fitness_share), style = MaterialTheme.typography.labelMedium) } diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt index 05c4aa8129..a99e9bf58d 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/DetectedWorkout.kt @@ -64,6 +64,16 @@ data class DetectedWorkout( * originated and where every constructor but the published-event mapper still builds from. */ val origin: WorkoutOrigin = WorkoutOrigin.HEALTH_CONNECT, + /** + * Whether a kind 1301 for this workout already exists. + * + * Not the same question as [origin]: a workout read from Health Connect is published the + * moment the user shares it, and [TrainingLog.merge] keeps the richer Health Connect copy + * rather than the published one — so the survivor is still [WorkoutOrigin.HEALTH_CONNECT] + * while a kind 1301 for it is already out there. Anything offering to publish a workout + * must read this, not the origin, or it offers to post the same effort twice. + */ + val alreadyPublished: Boolean = false, /** * How many Health Connect sessions this workout represents. 1 for a raw * session; higher when [WorkoutMerger] combined several close-by same-type diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt index ad34dd5671..ef62be0f78 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLog.kt @@ -50,17 +50,31 @@ object TrainingLog { * from their watch — the Health Connect copy wins: it carries the metrics the published * event may have dropped (heart rate, steps, climb), and its start time is the recorded one * rather than a publish timestamp. + * + * The winner keeps the loser's one piece of information: that a kind 1301 for this workout + * exists. Dropping the published copy would otherwise lose that fact, and the survivor — + * still flagged as Health Connect data — would read as never posted. See + * [DetectedWorkout.alreadyPublished]. */ fun merge( healthConnect: List, published: List, ): List { + val flagged = + healthConnect.map { recorded -> + if (published.any { recorded.isProbablySameWorkoutAs(it) }) { + recorded.copy(alreadyPublished = true) + } else { + recorded + } + } + val deduped = published.filterNot { candidate -> healthConnect.any { it.isProbablySameWorkoutAs(candidate) } } - return (healthConnect + deduped).sortedByDescending { it.startTimeEpochSeconds } + return (flagged + deduped).sortedByDescending { it.startTimeEpochSeconds } } private fun DetectedWorkout.isProbablySameWorkoutAs(other: DetectedWorkout): Boolean = @@ -97,5 +111,7 @@ fun WorkoutRecordEvent.toDetectedWorkout(): DetectedWorkout? { elevationGainMeters = elevationGain()?.toMeters()?.takeIf { it > 0 }, source = workoutSource() ?: "", origin = WorkoutOrigin.PUBLISHED, + // It came back from a relay, so by definition it is already out there. + alreadyPublished = true, ) } diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt index 6931914b53..264e4735cf 100644 --- a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/PublishedWorkoutMappingTest.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.experimental.fitness.workout.tags.WorkoutStartTi import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test class PublishedWorkoutMappingTest { @@ -73,6 +74,8 @@ class PublishedWorkoutMappingTest { assertEquals(6000, workout.steps) assertEquals(createdAt - 3600, workout.startTimeEpochSeconds) assertEquals(WorkoutOrigin.PUBLISHED, workout.origin) + // Recovered from a relay, so the dashboard must not offer to publish it again. + assertTrue(workout.alreadyPublished) } /** Miles on the wire must land as metres in the log, or every total is wrong by 1.6x. */ diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt index 51975cd716..bdfb0f8e1c 100644 --- a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/fitness/TrainingLogTest.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.fitness import com.vitorpamplona.quartz.experimental.fitness.workout.tags.ExerciseType import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -49,6 +50,8 @@ class TrainingLogTest { elevationGainMeters = null, source = "test", origin = origin, + // Matches the real mappers: anything recovered from a relay is by definition published. + alreadyPublished = origin == WorkoutOrigin.PUBLISHED, ) @Test @@ -82,6 +85,52 @@ class TrainingLogTest { assertEquals(150, merged[0].avgHeartRate) } + /** + * The survivor is Health Connect data, so its origin cannot answer "has this been posted?". + * Without the flag the dashboard offers to share it again and the user posts a second + * kind 1301 for the same effort. + */ + @Test + fun `the surviving copy remembers that the workout was already published`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon + 60)) + + val merged = TrainingLog.merge(hc, published) + + assertEquals(1, merged.size) + assertEquals(WorkoutOrigin.HEALTH_CONNECT, merged[0].origin) + assertTrue(merged[0].alreadyPublished) + } + + @Test + fun `a health connect workout that was never shared is not marked published`() { + val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon + TrainingLog.DEDUPE_TOLERANCE_SECONDS + 1)) + + val merged = TrainingLog.merge(hc, published) + + assertEquals(2, merged.size) + assertFalse(merged.first { it.id == "hc" }.alreadyPublished) + assertTrue(merged.first { it.id == "pub" }.alreadyPublished) + } + + /** Only the matching session is flagged; an unrelated one in the same log is left alone. */ + @Test + fun `flagging one workout does not flag the rest of the log`() { + val hc = + listOf( + workout("shared", WorkoutOrigin.HEALTH_CONNECT, noon), + workout("private", WorkoutOrigin.HEALTH_CONNECT, noon - 86_400), + ) + val published = listOf(workout("pub", WorkoutOrigin.PUBLISHED, noon)) + + val merged = TrainingLog.merge(hc, published) + + assertEquals(2, merged.size) + assertTrue(merged.first { it.id == "shared" }.alreadyPublished) + assertFalse(merged.first { it.id == "private" }.alreadyPublished) + } + @Test fun `a published workout just outside the tolerance is kept as its own`() { val hc = listOf(workout("hc", WorkoutOrigin.HEALTH_CONNECT, noon)) From f1bba332a95793a008dd87a33658b99c14b503f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 15:06:22 +0000 Subject: [PATCH 014/127] docs: point the Health Connect walkthrough at the drawer entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reviewer walkthrough still routed through Workouts and a chart icon in its top bar. That icon is gone — the Mine/Following split moved to the top-nav filter — so following the walkthrough would have dead-ended, which is how the first rejection ("insufficient information to determine app functionality") started. The route is now the drawer, where My Fitness is the second entry under "You", the first section, directly below Profile. That is a shorter path than the old one and needs no scrolling. Two other things the walkthrough got wrong, now that the dashboard reads both sources: an empty Health Connect database no longer means an empty screen, and a reviewer can populate the dashboard with no health permission at all by logging a workout in the app. Both are said explicitly, because a reviewer who sees an empty screen and assumes the feature is broken is the failure mode these notes exist to prevent. The sharing step also said to tap Share on any row, which stopped being true one commit ago: the button is gated on alreadyPublished, so a workout already posted does not offer it. Said as written, a reviewer would look for a control that isn't there on some rows. PRIVACY.md and the store listing carried the same stale path and are fixed to match. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019egdJyBHnrATZHjs86up8f --- PRIVACY.md | 2 +- docs/health-connect-play-declaration.md | 33 ++++++++++++------- .../android/en-US/full_description.txt | 9 ++--- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 1363eaf653..384e0677af 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -32,7 +32,7 @@ Configuration, cached events, keys, drafts, and other operational data live in t ### Health and fitness data (Health Connect) -Amethyst's **My Fitness** screen (Workouts → the chart icon) summarises your own training for you: how much you did this week against last week, how your time splits across activities, your best efforts, how many days you trained, and your current streak. It builds that picture from the workouts your watch or fitness app has already saved to **Android Health Connect**. +Amethyst's **My Fitness** screen (drawer → You → My Fitness) summarises your own training for you: how much you did this week against last week, how your time splits across activities, your best efforts, how many days you trained, and your current streak. It builds that picture from the workouts your watch or fitness app has already saved to **Android Health Connect**. This is what the health permissions are for. The summary is computed on your phone and shown to you; nothing is sent anywhere to produce it, and you never have to post anything to use it. diff --git a/docs/health-connect-play-declaration.md b/docs/health-connect-play-declaration.md index 5f889834d5..a3add5e9e7 100644 --- a/docs/health-connect-play-declaration.md +++ b/docs/health-connect-play-declaration.md @@ -17,8 +17,8 @@ permission is not a permitted/valid use case."* The app changed, not just the wording. Amethyst now has **My Fitness**, a personal training dashboard that summarises the user's own Health Connect workouts for them: weekly totals, week-over-week movement, a per-activity breakdown, best efforts, active days and a training -streak. It is reached from the Workouts screen and works fully whether or not the user ever -publishes anything. Publishing a workout is now one optional action on a row of that dashboard. +streak. It is its own destination in the navigation drawer, under "You", and works fully whether +or not the user ever publishes anything. Publishing a workout is now one optional action on a row of that dashboard. **The permissions serve the dashboard.** If the declaration below is ever re-read against the app, the test to apply is: every data type listed is rendered back to the user as their own @@ -72,22 +72,33 @@ Related code: ## 2. Reviewer walkthrough > 1. Install and open Amethyst, and sign in (a key can be generated in-app). -> 2. Open the navigation drawer (hamburger, top-left), and under **Feeds** tap **Workouts**. -> 3. In the top bar, tap the **chart icon** to open **My Fitness**. -> 4. The screen explains what will be read and offers **What Amethyst reads** (the full rationale +> 2. Open the navigation drawer (hamburger, top-left). **My Fitness** is the second entry under +> **You**, the first section — directly below Profile. Tap it. +> 3. The screen explains what will be read and offers **What Amethyst reads** (the full rationale > screen) and **Connect**. Tap Connect and grant the permissions. (If the account has already > logged workouts in Amethyst, the dashboard is already populated from those and the Health > Connect offer appears as a banner above it instead — the feature does not gate on the > permission.) -> 5. The dashboard appears: "This week" totals with the change against last week; the streak, +> 4. The dashboard appears: "This week" totals with the change against last week; the streak, > active-days and workout-count tiles; the four-week weekly average; the per-activity > breakdown; best efforts; and the recent-workout list. -> 6. Everything above is the tracking feature. To see the optional sharing path, tap **Share this -> workout** on any row — it opens a pre-filled composer that the user must confirm. +> 5. Everything above is the tracking feature. To see the optional sharing path, tap **Share this +> workout** on a row in Recent workouts — it opens a pre-filled composer that the user must +> confirm. The button appears only on workouts that have not been published yet, so a workout +> is never offered for sharing twice. > -> Note for a fresh test device: the dashboard needs Health Connect to actually hold finished -> exercise sessions from the last four weeks, written by some fitness app or watch. With an empty -> Health Connect database it correctly reports that nothing has been recorded. +> My Fitness can also be pinned to the bottom bar, under Settings, like any other destination. +> +> Notes for testing: +> +> - The dashboard summarises two sources: workouts read from Health Connect, and workout records +> the account has published from Amethyst. On a fresh device with an empty Health Connect +> database and an account that has never logged a workout, it correctly reports that nothing has +> been recorded — there is no data to summarise, which is not a failure of the feature. +> - To see it populated, either grant Health Connect access on a device whose fitness app or watch +> has saved sessions in the last four weeks, or log one in the app: drawer → **Feeds** → +> **Workouts** → **+**. A workout logged that way appears in the dashboard without any health +> permission at all. ## 3. Per-permission justification diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index acab602da2..2b2f056798 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -16,10 +16,11 @@ classifieds, calendars and more.
  • Track your training and share a workout — see below.
  • My Fitness — track your training

    -

    Amethyst includes a personal training dashboard. Open the left drawer, tap Workouts, then -the chart icon in the top bar. With your permission it reads the workouts your watch or fitness app -has already saved to Android Health Connect (Samsung Health, Google Fit, Fitbit, Garmin Connect, -Strava and others all write there) and shows you:

    +

    Amethyst includes a personal training dashboard. Open the left drawer and tap My Fitness, +under You. It summarises the workouts you have logged in Amethyst, and — with your +permission — the ones your watch or fitness app has already saved to Android Health Connect +(Samsung Health, Google Fit, Fitbit, Garmin Connect, Strava and others all write there). It shows +you:

    &qm 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
    -Q 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 z
    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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/perspective-nevent.png b/amethyst/src/androidTest/assets/qr/perspective-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f16b570e08a086964059ca5b480d3fccc68458 GIT binary patch literal 6956 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 literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/perspective-nprofile.png b/amethyst/src/androidTest/assets/qr/perspective-nprofile.png new file mode 100644 index 0000000000000000000000000000000000000000..f9121f7aaa9a2f756c69c7856c2e180ddf41b82a GIT binary patch literal 10809 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 NML|QpLe?zs{{XjsbcX-{ literal 0 HcmV?d00001 diff --git a/amethyst/src/androidTest/assets/qr/tilt30-nevent.png b/amethyst/src/androidTest/assets/qr/tilt30-nevent.png new file mode 100644 index 0000000000000000000000000000000000000000..0b83d2cb94475887c90d8d83ec7dc5771e45fd0b GIT binary patch literal 28738 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