Vojtěch Maršál 7f695b0786 Keep notifications alive when the app is closed (foreground service)
Adds LiveSocketService, a started foreground service that holds the /ws
WebSocket open after the app is swiped away and raises system notifications
for incoming alerts and messages while backgrounded. Shown as a low-priority
ongoing "Listening" notification (channel "Live updates").

- Started/stopped together with the push toggle (Profile) and on login/logout.
- foregroundServiceType=dataSync (+ FOREGROUND_SERVICE_DATA_SYNC permission).
- START_STICKY + onTaskRemoved no-op so it survives task removal; the OS may
  still kill it on explicit Force-Stop or reboot-until-reopened.
- MainShell no longer posts notifications itself (the service owns that) and
  just keeps the bell count fresh; the poll remains as a catch-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 15:03:52 +02:00

Bounty Board — Android

A native Android client for the Bounty Board consulting work-management platform. Built with Kotlin + Jetpack Compose (Material 3), it covers the full role-based feature set — admin, consultant, and developer — over the server's cookie-session + CSRF API.

The visual identity mirrors the web app's "ledger / bounty-poster" aesthetic: warm paper grounds, brown/amber ink, hard offset shadows (no blur), 2px corners, ◈ bounty chips, and the original Fraunces / Schibsted Grotesk / Spline Sans Mono typefaces (bundled, instanced from the web app's variable fonts).

First run

The app opens on a Login page. The default server is https://bountyboard.anypreta.com/. Expand Server on the login screen to point the app at any other Bounty Board instance — the base URL is persisted and every request is retargeted at runtime. Sign in with email/password, register a developer account, or use "Forgot password". A forced password change is handled automatically when the server requires it.

Features

  • Auth — login (with configurable base URL), self-registration, forgot password, forced password change. Session + CSRF are handled transparently by a persistent cookie jar and a double-submit interceptor.
  • Developer — bounty board with search / project / min-bounty / sort filters and claim flow; My Tasks kanban (Assigned · In progress · In review · Done); per-task start / submit / abandon / withdraw; comments; time logging; metrics dashboard; leaderboard.
  • Consultant — atomization board (grouped by project, status filters, quick publish); subdivide / extend / edit / publish; reviews queue with an acceptance- criteria checklist; approve / decline claims; assign-to-AI; developer pool management; metrics.
  • Admin — users (roles, disable, force-reset, delete); projects/customers wizard (per-system credential fields, test connection, sync now); runtime settings; audit log; live service-status panel.
  • Shared — task detail with full role-aware action bar and timeline; real-time-feel messaging (DMs, group/project conversations, image + file attachments, read receipts); notification center with deep links; profile (avatar upload, bio/contact, theme picker with all six themes, leaderboard opt-out, sign-out-everywhere); public profile cards.

Architecture

data/        AppContainer (manual DI) · Repository · SessionManager · SettingsStore (DataStore)
  net/       Retrofit ApiService · PersistentCookieJar · CSRF + dynamic-host interceptors · DTOs
  model/     wire data classes
ui/
  theme/     palette (6 themes) · typography · 2px shapes · hard-offset shadow modifier
  components/ BBButton · BBCard · BountyChip · StatusBadge · BBAvatar · BBTextField · dialogs · TaskCard
  nav/       AppRoot (bootstrap/auth gate) · MainShell (role drawer + NavHost) · Routes
  auth/ developer/ consultant/ admin/ task/ messaging/ shared/ profile/   feature screens + ViewModels
  • Networking: OkHttp + Retrofit (Gson). The base URL is dynamic — a host-selection interceptor rewrites scheme/host/port per request, so changing the server on the login screen takes effect immediately without rebuilding Retrofit. Coil shares the OkHttp stack so /files/{id} image loads carry the session cookie.
  • Auth: cookie-session only. bb_session (httpOnly) and bb_csrf are persisted in a PersistentCookieJar; mutating requests echo bb_csrf into the X-CSRF-Token header.
  • State: ViewModels expose Compose mutableStateOf snapshots; screens are stateless and driven by LocalAppContainer.

Building

Requirements: JDK 17 or 21 (AGP 8.5 does not support newer JDKs as the build JVM), the Android SDK with platform 34 and build-tools 34.0.0, and Gradle 8.9+.

The easiest path is to open the project in Android Studio (Koala / 2024.1+), let it sync, and Run.

From the CLI (the Gradle wrapper is included; point JAVA_HOME at a JDK 1721):

cd BountyBoard_android
JAVA_HOME=/path/to/jdk-21 ./gradlew assembleDebug   # APK → app/build/outputs/apk/debug/
JAVA_HOME=/path/to/jdk-21 ./gradlew installDebug    # install on a connected device/emulator

A prebuilt debug APK is committed at BountyBoard-debug.apk in the project root — adb install -r BountyBoard-debug.apk to side-load it directly.

local.properties already points sdk.dir at ~/Android/Sdk; adjust if your SDK lives elsewhere. The app allows cleartext traffic so you can point it at a local http:// dev server.

Sign-in

Local email/password and OIDC SSO both work. "Sign in with SSO" opens the server's OIDC redirect flow in an in-app browser; once the IdP redirects back, the session cookies are captured into the app's HTTP stack and you're signed in. If the server has no OIDC configured, the app reports that instead of failing silently.

Notes

  • minSdk 26, targetSdk 34. No proprietary services; the launcher icon, splash, and fonts are all bundled.
  • The server contract is documented in ../BountyBoard/api/openapi.yaml and ../BountyBoard/specification.md; this client implements the §6 application API.
S
Description
No description provided
Readme 55 MiB
Languages
Kotlin 100%