No description
  • Kotlin 99%
  • Shell 1%
Find a file
Rune Jensen 6cceba0b9b
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/build Pipeline was successful
Compact widget layout so it fits a 2x1 (1-cell-tall) size
Top-align the content, trim vertical padding (12->6dp) and shrink the
travel-time figure (28->23sp), so the route, time and updated-at lines all
fit a 1-cell-high widget instead of the timestamp clipping at the bottom.
Bump to 0.1.4 (5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQD9jG6DUrEwVM2ar5j7Ao
2026-07-21 21:35:59 +02:00
.woodpecker Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
app Compact widget layout so it fits a 2x1 (1-cell-tall) size 2026-07-21 21:35:59 +02:00
gradle Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
.gitignore Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
build.gradle.kts Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
build.sh Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
gradle.properties Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
gradlew Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
gradlew.bat Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
logo.png Use logo as app launcher icon (converted to PNG) 2026-07-21 15:41:41 +02:00
README.md Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00
settings.gradle.kts Initial commit: trafikapp Android app 2026-07-21 15:35:46 +02:00

trafikapp

Android companion app for the Historic traffic API (trafikhistorik) — the same data the web UI shows, on your phone, plus home-screen widgets for live travel times.

It reads from the live environment at https://api.trafikhistorik.jaduer.dk/; there is no login (the API is read-only and unauthenticated).

Screens

Bottom-navigation tabs, mirroring the web UI:

  • Begivenheder — traffic incident posts with nested status updates, filterable by Alle / Aktive / Afsluttede, keyset "Vis flere" pagination.
  • Rejsetid — travel-time routes paired by opposite direction (A ↔ B), each with a live "now" badge (current minutes + congestion factor ×) and a history chart. Range picker 2t / 6t / 48t / 7d / 3u, same windows as the web.
  • Bus — bus delay over the day (minutes vs. planned departure time) plus a list of the largest delays.
  • Odin — Odin messages, searchable by first report / station, offset pagination.
  • Vejdirektoratet — road announcements, searchable by title / summary, offset pagination.

Charts use Vico (Compose, Material 3).

Rejsetid home-screen widget

Add the Rejsetid widget from your launcher and pick a route in the configuration screen. Each widget instance tracks one route and shows its current travel time, congestion factor and last-updated time. A WorkManager job refreshes every placed widget about every 15 minutes — matching the API's sampling cadence — so the value stays current without opening the app. Tapping the widget opens the app.

Stack

Native Kotlin + Jetpack Compose, minSdk 33 (Android 13+), targetSdk 36. Dependencies kept close to the API surface: Compose BOM + Material 3, Navigation, Lifecycle/ViewModel, OkHttp + kotlinx.serialization (hand-rolled typed client mirroring the API — no Retrofit/codegen), Vico (charts), Glance + WorkManager (widget). Tests: JUnit + MockWebServer.

The typed client and its models live in app/src/main/kotlin/dk/jaduer/trafikapp/data; note the /api/travel-times endpoints are not in the API's openapi.yaml (they live in the Flask source), so the models there were derived from trafikhistorik/app/routes/travel_times.py.

Building

Everything runs in Docker; no local Android toolchain needed.

./build.sh                  # unit tests + debug APK (default)
./build.sh test             # just the unit tests
./build.sh assembleDebug    # just the APK -> app/build/outputs/apk/debug/
./build.sh assembleRelease  # R8-minified release APK -> app/build/outputs/apk/release/

Unit tests are plain JVM: the typed API client runs against a MockWebServer fake of the traffic API with fixtures shaped like the live responses.

assembleRelease works locally without any signing secrets — it just produces an unsigned app-release-unsigned.apk, useful for checking that R8 shrinking still builds cleanly. Signed release APKs are built by CI on tags; see "Release/signing" below.

Installing

Signed APKs are attached to Forgejo releases on every version tag. Two ways to install:

  • Direct: download trafikapp-<tag>.apk from the latest release and open it on the device (enable "install unknown apps" for your browser/file manager once, when prompted).
  • Obtainium: add this repo as an app source (it auto-detects Forgejo/Gitea releases) and Obtainium tracks new tags. There is no Play Store listing.

Release/signing (for maintainers)

Release builds are signed with a dedicated app-signing keystore (never the debug key) and R8-minified. The signing config in app/build.gradle.kts reads the keystore path and passwords from environment variables and is only applied when they're set — this is what lets local ./build.sh assembleRelease succeed unsigned, and lets CI sign only on tag builds where the secrets are injected.

One-time: generate the keystore

keytool -genkeypair -v \
  -keystore trafikapp-release.jks \
  -alias trafikapp \
  -keyalg RSA -keysize 4096 -validity 10000

Keep the resulting .jks and its passwords somewhere durable and private (a password manager, not this repo) — losing it means future releases can no longer update installs signed with it.

Woodpecker secrets

.woodpecker/build.yaml builds and signs the release APK only on tag events, using these repo secrets:

Secret Value
trafikapp_keystore base64 -w0 trafikapp-release.jks output
trafikapp_keystore_password keystore password
trafikapp_key_alias key alias (trafikapp above)
trafikapp_key_password key password
gitea_token Forgejo API token with write access to releases

Set them in the Woodpecker UI under the repo's Secrets settings. On a tag push, CI decodes the keystore to a temp file, runs assembleRelease with TRAFIKAPP_KEYSTORE/TRAFIKAPP_KEYSTORE_PASSWORD/TRAFIKAPP_KEY_ALIAS/ TRAFIKAPP_KEY_PASSWORD set, renames the output to trafikapp-${CI_COMMIT_TAG}.apk, and attaches it to the matching Forgejo release.