Stasi is a fast, privacy-minded Android app for Athens public transport. It talks to the official OASA Telematics API to show live arrivals, nearby stops, and route maps—without ads, sign-in, or extra clutter.
Website: ntufar.github.io/stasi (landing page for the project)
Goal: open the app and see your next bus in under a second.
| Package | io.github.ntufar.stasi |
| Min / target SDK | 26 / 34 |
| Current version | See app/build.gradle.kts (versionName / versionCode) and CHANGELOG.md |
- Home — Favorite stops with live next arrivals (two per stop).
- Search — Stops and lines by name with Greek-friendly normalization (accents ignored).
- Arrivals — Large countdown-style minutes, line id, destination.
- Nearby — GPS-based stops sorted by distance.
- Map — Route polyline, stops, live vehicle positions, and your location on the map when you allow location (MapLibre, no map API key).
- Offline-friendly cache — Lines/stops cached (24h policy in product spec), arrivals short-lived cache.
- Arrival alerts — Optional local notification when a chosen live arrival is within a few minutes (WorkManager + on-device storage; no remote push server).
Design defaults: dark / AMOLED-friendly Material 3. Primary UI language Greek with English fallback where relevant.
Not in scope (MVP): ticket purchase, multi-leg journey planner, remote push / marketing notifications (alerts are user-triggered and local-only).
- Kotlin, Jetpack Compose, Material 3
- MVVM + repository layer; Retrofit + Gson, Coroutines
- Room (cache), DataStore (preferences + arrival alert state)
- WorkManager (arrival alert polling)
- MapLibre Android SDK
- Google Play services — location (coarse/fine for nearby stops)
Detailed API contracts, rate limits, and architecture notes live in docs/SPEC.md.
- JDK 17 (Temurin matches CI). If your shell’s default
javais newer (e.g. JDK 26), Gradle can fail with a cryptic message—pin 17 before running./gradlew:- macOS:
export JAVA_HOME=$(/usr/libexec/java_home -v 17) - Linux / Windows: set
JAVA_HOMEto your JDK 17 install.
- macOS:
- Android SDK with API 34 platform / build-tools (Android Studio provides this)
- A device or emulator running API 26+
Cursor agents follow the same rule in .cursor/rules/jdk-17-gradle.mdc.
chmod +x ./gradlew # once, if needed
./gradlew assembleDebugDebug APK output:
app/build/outputs/apk/debug/app-debug.apk
CI builds the artifact with -PstasiAbiArm64Only ( arm64-v8a only ) so the downloadable APK stays small; omit that flag locally if you need x86 emulators or universal ABIs.
Run lint and unit tests:
./gradlew lintDebug testDebugUnitTestRelease builds require signing (see below). Release APKs/AABs use R8 minification, resource shrinking, and phone ABIs only (arm64-v8a, armeabi-v7a—no x86 emulator libs).
./gradlew assembleRelease bundleReleaseRelease builds use keystore.properties at the repo root (gitignored). Copy the template and fill in your upload keystore:
cp keystore.properties.example keystore.properties
# Edit keystore.properties; place the keystore file where storeFile points (repo root is typical).If keystore.properties is missing, Gradle still configures the debug build; release will not be signed with your upload key until the file exists.
On every push (any branch) and on workflow_dispatch:
- Lint, unit tests, and a debug APK artifact (
stasi-debug-apk)—built in a dedicated job so you still get an APK when lint/tests fail but the project compiles. The artifact is arm64-v8a-only (smaller download; MapLibre ships large native libs per ABI).
Download: Actions → latest “Android CI” run → Artifacts → stasi-debug-apk.
Triggered by:
workflow_dispatch— build signed AAB (+ APK); optional upload to Play when Publish is enabled.- Push of tags
v*— same build, plus a GitHub Release withstasi-<version>.apkand release notes sliced from CHANGELOG.md for that version.
Repository secrets (signed artifacts):
| Secret | Purpose |
|---|---|
RELEASE_KEYSTORE_BASE64 |
Base64-encoded upload keystore file |
KEYSTORE_STORE_PASSWORD |
Keystore password |
KEYSTORE_KEY_ALIAS |
Key alias |
KEYSTORE_KEY_PASSWORD |
Key password |
PLAY_SERVICE_ACCOUNT_JSON |
(Optional) Play Developer API service account JSON for automated upload |
Play Console app must use package io.github.ntufar.stasi (or change applicationId before your first production upload).
- Human-readable history: CHANGELOG.md (Keep a Changelog style).
- Tag releases as
vMAJOR.MINOR.PATCH(e.g.v0.0.1) to match the GitHub Release automation.
- Network data comes from OASA Telematics (
http://telematics.oasa.gr/api/). The app uses cleartext HTTP for that endpoint (see manifest / network security configuration). - Location is used for nearby stops and for your position on the route map when you allow it; the MVP spec does not persist location history.
- No analytics / crash reporting in the MVP spec—verify current code before claiming compliance in store listings.
app/src/main/java/io/github/ntufar/stasi/
data/ # API, Room, repository, utilities
di/ # App wiring / composition
workers/ # WorkManager jobs (e.g. arrival alerts)
ui/ # Compose screens & theme
MainActivity.kt, StasiApp.kt, StasiApplication.kt
docs/SPEC.md # Product & technical specification
This project is licensed under the MIT License.
Issues and pull requests are welcome. For releases, update CHANGELOG.md under [Unreleased], then add a dated [x.y.z] section when you cut a tag.
Stasi is an independent client for public timetable/vehicle data. It is not affiliated with OASA or official transport operators. Schedules and live data depend on third-party services and may be incomplete or delayed.