Skip to content

feat(android-app): android app - #1256

Draft
dorthrithil wants to merge 42 commits into
nextfrom
app
Draft

feat(android-app): android app#1256
dorthrithil wants to merge 42 commits into
nextfrom
app

Conversation

@dorthrithil

Copy link
Copy Markdown
Contributor

No description provided.

dorthrithil and others added 30 commits August 8, 2026 21:29
- Add @capacitor/core, android, app, splash-screen, status-bar (^8.x) as deps
- Add @capacitor/cli, @capacitor/assets as devDeps
- Add capacitor.config.ts: appId de.localcrag.app, appName LocalCrag,
  webDir dist/client/browser, launchAutoHide false, no server block

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add npm run verify:webdir asserting dist/client/browser/index.html exists
- Guards against RESEARCH Pitfall 1 (wrong webDir producing a blank WebView)
- Verified against a real ng build; cap:build/cap:sync deferred to plan 02

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add HardwareBackButtonService contract stub (register,
  handleBackPress, dismissTopOverlayIfAny) exporting OVERLAY_SELECTORS,
  EXIT_APP, BackPressOutcome; every method throws NOT_IMPLEMENTED
- Add 7 specs covering overlay-dismiss detection and canGoBack/exit
  branching (D-11, D-12), using an EXIT_APP token and mocked Location
  so no Capacitor plugin proxy is spied on
- All 7 specs fail RED for NOT_IMPLEMENTED only; rest of Karma suite
  (126 specs) and lint stay green

Co-authored-by: Cursor <cursoragent@cursor.com>
- environment.android.ts targets http://10.0.2.2:5000 (D-09, emulator alias for host loopback)
- angular.json android configuration replaces only the environment file, mirroring the e2e pattern
- package.json cap:build/cap:sync scripts to build and sync the Capacitor Android project

Co-authored-by: Cursor <cursoragent@cursor.com>
- npx cap add android generates client/android/, a Capacitor Gradle project bundling dist/client/browser into app/src/main/assets/public
- applicationId/namespace de.localcrag.app, display name LocalCrag (D-01, D-02, D-06), versionName synced to package.json's 1.51.0 (D-04)
- Manifest kept minimal with only the generated INTERNET permission (T-14-05)

Co-authored-by: Cursor <cursoragent@cursor.com>
- New debug source-set overlay (app/src/debug/) adds networkSecurityConfig allowing cleartext only to 10.0.2.2, denying it everywhere else via base-config (D-10, T-14-01)
- Nothing under app/src/main or app/src/release references cleartext or 10.0.2.2, so the allowance never reaches a release manifest (T-14-02)

Co-authored-by: Cursor <cursoragent@cursor.com>
- dismissTopOverlayIfAny() queries OVERLAY_SELECTORS and dispatches a
  synthetic Escape keydown that PrimeNG's own Dialog/Popover already
  consume to self-close
- handleBackPress() enforces overlay-dismiss-first, then Location.back()
  when canGoBack, else immediate exitApp() with no confirm dialog
- register() is native-only, idempotent, and wraps the listener callback
  in try/catch (T-14-06) so a thrown error can never leave the hardware
  back button permanently unresponsive

Co-authored-by: Cursor <cursoragent@cursor.com>
- Adds a single provideAppInitializer calling
  HardwareBackButtonService.register() immediately after ThemeService's
  initializer, so the listener is live before the router resolves the
  first route
- register() early-returns off native platforms, so this is inert on
  the web build (full Karma suite and production build stay green)

Co-authored-by: Cursor <cursoragent@cursor.com>
… resources

- Add client/scripts/generate-placeholder-assets.mjs, rasterizing
  src/assets/lc_logo.svg via sharp into icon-only/foreground/background
  and splash/splash-dark source PNGs under client/assets/
- Run npx @capacitor/assets generate --android to produce the full
  Android density matrix (mipmap-*, drawable-*) plus -night qualified
  splash resources so the OS picks the dark splash before any JS runs
- Sync the generated resources into client/android via npm run cap:sync

Co-authored-by: Cursor <cursoragent@cursor.com>
- main.ts: call SplashScreen.hide() once bootstrapApplication() resolves,
  guarded by Capacitor.isNativePlatform() (D-07) — hides on WebView-ready,
  not on a fixed timer or first interactive route
- theme.service.ts: add syncNativeStatusBar(), an effect() driven by
  isDarkMode() that sets StatusBar style/background from the same
  --p-content-background token .site-header already paints with (D-05),
  with a hex-format guard before crossing the Capacitor bridge

Co-authored-by: Cursor <cursoragent@cursor.com>
Repo-wide prettier --write . in the pre-commit hook (lint-staged runs
it without a filename placeholder) reformatted this untracked .mjs file
to double quotes since it falls outside the **/*.{js,ts} eslint glob
that would otherwise restore single quotes. No functional change.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Gradle 8.14.3's daemon cannot run on JDK 25 (the only JDK available via
  Android Studio's bundled JBR on this machine): the Groovy/ASM version it
  ships fails to parse class file major version 69 during build-script
  compilation ("BUG! exception in phase 'semantic analysis' ... Unsupported
  class file major version 69")
- Gradle 8.14.4 backports the Groovy/ASM bump needed for JDK 25 support
  while staying on the same AGP-compatible 8.14.x line, so this is a patch
  bump with no build-logic changes
- Verified: `./gradlew :app:assembleDebug :app:processReleaseManifest`
  now completes successfully; debug APK assembles and the release
  manifest merge (the check this plan exists to prove) runs correctly

Co-authored-by: Cursor <cursoragent@cursor.com>
…s work

- The debug APK serves the bundled app from Capacitor's default
  https://localhost origin. The WebView's mixed-content policy blocks
  XMLHttpRequests to the debug-only cleartext host (http://10.0.2.2:5000,
  D-09/D-10) even though the network security config already permits it
  at the OS level, so every API call silently failed on the emulator
- Found during Task 1 checkpoint pre-automation: cold-starting the debug
  APK left the app stuck on the splash screen forever with "Mixed
  Content ... has been blocked" console errors
- Fix: MainActivity now sets WebSettings.MIXED_CONTENT_ALWAYS_ALLOW,
  guarded by BuildConfig.DEBUG (a per-build-type generated constant,
  false in release) so this relaxation can never activate in a release
  build — same debug/release isolation guarantee as the network
  security config from plan 02. buildFeatures.buildConfig had to be
  enabled explicitly since AGP 8 no longer generates BuildConfig by
  default

Co-authored-by: Cursor <cursoragent@cursor.com>
…ootstrap

- Found during Task 1 checkpoint pre-automation: instanceSettingsService
  .getInstanceSettings() and menuItemsService.getMenuItems() are wired
  as Angular app initializers with no error handling. Any failure
  (offline, unreachable backend, CORS) errors the underlying Observable,
  which rejects bootstrapApplication()'s promise — main.ts's .then()
  that calls SplashScreen.hide() never runs, so the native splash stays
  on screen forever with no way to recover
- This directly breaks this plan's own checkpoint step 6 (airplane-mode
  cold start must still render the UI shell, D-07) and would fail on
  any transient network hiccup, not just full outages
- Fix: both initializers now catchError and resolve gracefully instead
  of rejecting bootstrap — instance settings/menu load failures log to
  console and fall back to default state (browser-detected language,
  no preloaded menu) rather than blocking the app from ever rendering

Co-authored-by: Cursor <cursoragent@cursor.com>
…WebView

- Found during Task 1 checkpoint pre-automation: after fixing the
  mixed-content block, API calls from the debug APK still failed —
  the browser blocked them with "No 'Access-Control-Allow-Origin'
  header" because flask-cors was hardcoded to the single FRONTEND_HOST
  origin (http://localhost:4200), and Capacitor's WebView serves the
  app from https://localhost, a different origin
- FRONTEND_HOST also doubles as the base URL for email links elsewhere
  in the codebase, so it can't safely become a multi-value field
- Fix: new optional ADDITIONAL_CORS_ORIGINS config (comma-separated),
  merged into the flask-cors origins list alongside FRONTEND_HOST.
  Defaults to empty, so prod/test/CI CORS behavior is unchanged unless
  a developer opts in via their local (gitignored) dev.cfg — documented
  in template.cfg

Co-authored-by: Cursor <cursoragent@cursor.com>
Use http androidScheme to avoid mixed-content blocks on cleartext
images, rewrite loopback hosts in File URLs and rich-text HTML to
10.0.2.2, and document Capacitor CORS origins in template.cfg.

Co-authored-by: Cursor <cursoragent@cursor.com>
Install @capacitor/preferences, turn on CapacitorHttp for Android API
calls, and add a unit-tested resolveApiHost helper seeded from the
environment default (INST-01 / SHELL-03 foundation).

Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve Preferences-backed apiHost in main.ts and build all ApiService
URLs from RUNTIME_API_HOST so JWT refresh and uploads share one origin.

Co-authored-by: Cursor <cursoragent@cursor.com>
Temporary Preferences-backed API URL editor for UAT, with HTTPS-or-emulator-loopback validation before reload.

Co-authored-by: Cursor <cursoragent@cursor.com>
Strict product+version on /api/health plus Preferences saved-instance helpers for the Phase 16 picker.

Co-authored-by: Cursor <cursoragent@cursor.com>
Isolate JWT localStorage and Dexie DBs by host so instance switches cannot leak sessions or offline drafts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Gate first-run on empty saved list, validate LocalCrag health before save, and expose Switch instance in the account menu.

Co-authored-by: Cursor <cursoragent@cursor.com>
Defer product-identity and version soft-warn so already-deployed LocalCrag servers remain addable from the picker.

Co-authored-by: Cursor <cursoragent@cursor.com>
Hide app chrome on /instances, expose Switch instance for guests,
and navigate to / after save or switch so the picker does not stick.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add GPS_BRIDGE InjectionToken + registerPlugin('GpsBridge') contract
- Stub installNativeGpsShim/uninstall for Wave 0 (plan 03 implements)
- Jasmine specs fail on install-gate / mapping / refcount until GREEN

Co-authored-by: Cursor <cursoragent@cursor.com>
- Set mockGpsRecording=false for Android so Record cannot false-pass
- Require !Capacitor.isNativePlatform() before mock walker load/install

Co-authored-by: Cursor <cursoragent@cursor.com>
- Depend on play-services-location 21.4.0
- Declare ACCESS_FINE/COARSE only (no FGS/BG)
- Implement start/stop/getCurrentPosition + locationUpdate events

Co-authored-by: Cursor <cursoragent@cursor.com>
- Wire local Capacitor plugin into BridgeActivity
- Keep DEBUG mixed-content block after super.onCreate

Co-authored-by: Cursor <cursoragent@cursor.com>
- Patch get/watch/clear on native only with watcher refcount start/stop
- Map finite locationUpdate payloads; drop non-finite lat/lng

Co-authored-by: Cursor <cursoragent@cursor.com>
- Await GpsBridge.requestPermissions in startGeoWatch on native
- Deny exits Record via onGeoPermissionDenied; pause keeps stream

Co-authored-by: Cursor <cursoragent@cursor.com>
dorthrithil and others added 12 commits August 9, 2026 16:29
- Install GpsBridge geolocation shim on native map init (no permission)
- Uninstall and force-stop on recording.destroy / leave (D-09)

Co-authored-by: Cursor <cursoragent@cursor.com>
- Extend GpsBridgePlugin with background/notifications permission methods
- Stub ensureRockExplorerTrackingPermissions (always false until plan 03)
- Wave 0 Jasmine coverage for D-08 order, disclosure, deny hard-stops

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add nativeGpsTrackingActive signal defaulting false (D-14)
- Introduce CAPACITOR_APP InjectionToken for testable App listeners
- Facade specs assert appStateChange → flushDraftQueue until plan 03

Co-authored-by: Cursor <cursoragent@cursor.com>
- Declare FGS location triad, BG location, POST_NOTIFICATIONS, and GpsForegroundService
- Own FLP + ongoing notification in service; plugin start/stop + BG/notifications aliases

Co-authored-by: Cursor <cursoragent@cursor.com>
- Implement ensureRockExplorerTrackingPermissions (FG→disclosure→BG→notifications)
- Wire startGeoWatch to staged gate + ConfirmDialog Play disclosure
- Clear nativeGpsTrackingActive on stop/destroy; normal-locale i18n only

Co-authored-by: Cursor <cursoragent@cursor.com>
- Register appStateChange → flushDraftQueue on native; remove on destroy
- Show minimal tracking-active chip in Record toolbar when signal is live
- Assert pause keeps nativeGpsTrackingActive (D-07)

Co-authored-by: Cursor <cursoragent@cursor.com>
Allow private-LAN API hosts and debug cleartext, skip hung startup
fetches before instance onboarding, and rewrite MinIO loopback media
to the runtime API host so Galaxy UAT works with adb reverse.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Lock RockExplorerLiveSessionGuard / LiveSessionEndHandlers contract
- RED Cancel/Finish/Discard/passthrough/no-soft-continue expects (INST-07)

Co-authored-by: Cursor <cursoragent@cursor.com>
- Same-host bypass passes; different-host runGuardedAction fails until plan 02
- Menu switchInstance/logout expect guard hooks (D-06/D-07)

Co-authored-by: Cursor <cursoragent@cursor.com>
- GREEN RockExplorerLiveSessionGuard with Finish/Discard/Cancel sequencing
- Add LiveSessionResolveDialogComponent and root DialogService

Co-authored-by: Cursor <cursoragent@cursor.com>
- Register/clear live handlers from recording facade including discardActiveLiveSession
- Guard Switch instance and logout before navigate/dispatch; picker host-change after D-08

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add Finish/Discard/Cancel strings in en/de/fr/it/nl only (no gym mirrors)

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant