feat(push): M7 Part 2 — opt-in push notifications (embedded ntfy distributor)
All checks were successful
PR Checks / android-build (pull_request) Successful in 20m16s

Implements the app side of M7 push (docs/android/PLAN.md §11). The app EMBEDS
its own distributor — ntfy is only the relay server, no second app installed,
no Google Play Services. New feature slice; no existing screen's data flow
changes.

- core/push: NtfyTopic (random unguessable topic + endpoint/SSE URL builders),
  PushTickle (content-free { stream, ref } parser over ntfy's SSE envelope),
  NtfyStreamClient (bare-client OkHttp SSE to <ntfy>/<topic>/sse, reconnect/
  backoff cloned from ShardStreamClient), PushNotifier (channels + per-stream
  deep-link notification), PushService (foreground service holding the
  connection), PushManager (mint topic / register-unregister device / start-stop,
  keyed to the session), PushPreferences (DataStore state).
- data: NotificationsApi + DTOs + NotificationsRepository over the merged
  /auth/me/devices + /auth/me/notifications/* contract; push block on SettingsDto.
- ui/notifications: settings screen + VM — per-stream toggles, personal streams
  greyed until a game account is linked, POST_NOTIFICATIONS request on enable.
- Navigation: Routes.NOTIFICATIONS + stream→route deep-link map, menu entry,
  RunicApp + MainActivity intent handling; teardown wired into logout + server
  switch (deregister while bearer valid) and every sign-out (local, via session
  observer).
- Manifest: POST_NOTIFICATIONS + FOREGROUND_SERVICE(_DATA_SYNC) + the service.

Deviation (recorded in PLAN.md): direct-ntfy transport, no UnifiedPush library
— the plan's stated likely path; keeps the APK Google-free and dependency-light,
with a PushResult/transport seam for a future FCM Play flavor. Requires the small
companion push.ntfyUrl settings field (website#<pr>).

18 new JVM tests; :app:testDebugUnitTest + lintDebug + assembleDebug green.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-20 15:25:59 -05:00
parent d0fb6bbdfc
commit e2ced06a83
28 changed files with 1559 additions and 3 deletions

View File

@@ -6,6 +6,13 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Opt-in push notifications (M7): the runtime notification permission (API 33+)
and a foreground service that holds the persistent ntfy connection open — the
embedded UnifiedPush distributor, so no separate app is needed (PLAN.md §11). -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application
android:name=".RunicGatewayApp"
android:allowBackup="true"
@@ -26,6 +33,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- The embedded distributor's persistent ntfy connection (M7, PLAN.md §11).
dataSync foreground type; not exported — started only by PushManager. -->
<service
android:name=".core.push.PushService"
android:exported="false"
android:foregroundServiceType="dataSync" />
</application>
</manifest>