Android-app (Kotlin + Jetpack Compose) die een rit registreert en bij stilstand automatisch
een historisch beeld van de locatie toont. De volledige productspecificatie staat in
HierToen-technische-bouwspecificatie.pdf — dat
document is leidend, zie CLAUDE.md.
Bouwvolgorde uit spec §17.1:
- Stap 1 — Project, thema, navigatie, basis-README
- Stap 2 — Room-schema en Trip/TrackPoint repositories
- Stap 3 — Foreground TrackingService en actieve-rit UI
- Stap 4 — MotionStateEngine met configureerbare drempels
- Stap 5 — "Deze plek bewaren" en TripMoment
- Stap 6 — WikimediaSource en kandidaatselectie
- Stap 7 — Veilige fotoweergave
- Stap 8 — Route review en exports
- Stap 9 — Settings, privacy en fouttoestanden
- [~] Stap 10 — APK en veldtestpakket: elke CI-run publiceert nu een debug-APK als build-
artefact (
hiertoen-debug-apk, 30 dagen bewaard); de echte veldtest met een fysiek toestel is aan degene die de app installeert, niet iets wat in deze omgeving kan.
Er is in deze omgeving geen lokale Android SDK/Gradle-installatie beschikbaar; de build en
tests worden geverifieerd via GitHub Actions (.github/workflows/android-ci.yml) bij elke
push. Zie de Actions-tab van de repository voor de laatste build- en teststatus, en het
tabblad "Artifacts" van een geslaagde run voor de installeerbare debug-APK.
- Open deze map als project in Android Studio (Ladybug of nieuwer).
- Android Studio herkent het ontbreken van
gradle-wrapper.jaren biedt aan de wrapper te herstellen bij het synchroniseren — accepteer dit, of genereer 'm handmatig met een lokaal geïnstalleerde Gradle:gradle wrapper. - Sync het project; de Android Gradle Plugin downloadt de benodigde SDK-platformen automatisch (compileSdk/targetSdk 35, minSdk 28).
- Run op een emulator of fysiek toestel (Run ▸ app). Voor ritregistratie is een fysiek toestel met echte GPS nodig — een emulator kan dit met een gesimuleerde route benaderen.
gradle wrapper
./gradlew assembleDebug
./gradlew testDebugUnitTestZie spec §9.3 voor de module-indeling. Tot nu toe geïmplementeerd:
app/src/main/java/nl/hiertoen/app/
├── MainActivity.kt
├── HierToenApp.kt
├── core/ # ActivityType, GeoMath (gedeeld)
├── motion/ # MotionStateEngine — statusmachine §5, puur Kotlin/testbaar
├── export/ # GpxExporter, GeoJsonExporter — §6.5
├── photos/ # WikimediaClient, kandidatenscoring §7, PhotoSearchService
├── settings/ # SettingsRepository (DataStore) — §11
├── tracking/ # TrackingService (foreground), validatie, adaptieve opslag §6
├── ui/
│ ├── theme/ # Donker, zwart met oranje accent — §1.3
│ ├── navigation/ # Start / Trips / TripDetail / ActiveTrip / Settings
│ ├── permissions/ # Runtime-toestemmingen, aangevraagd bij "Start rit" — §12.4
│ └── screens/ # Start, actieve rit (rijscherm), ritdetail, instellingen
└── data/
├── local/ # Room: Trip/TrackPoint/TripMoment, DAO's, database, migraties
└── repository/ # TripRepository tussen Room en de rest van de app
Room-DAO-tests en de MotionStateEngine-tests draaien als JVM-unit tests (Robolectric voor de
Room-tests, gewone JUnit voor de statusmachine en de validatie-/opslaglogica), zodat ze zonder
emulator werken — zowel lokaal (./gradlew testDebugUnitTest) als in CI. TrackingService
zelf (foreground service + Play Services locatie/activity-API's) heeft geen geautomatiseerde
test — dat vereist een emulator of fysiek toestel en hoort bij de veldtest in stap 10.
Zonder sleutel doet de app precies wat hierboven staat: alleen Wikimedia, geen fallback, niets crasht. Met een sleutel krijg je ook een beeld op plekken waar Wikimedia niets heeft.
- Maak een Google Cloud-project met billing (Google rekent per aanroep na een gratis quotum).
- Zet de Street View Static API aan (APIs & Services → Library).
- Maak een API-sleutel (APIs & Services → Credentials → Create credentials → API key).
- Beperk 'm tot Street View Static API (Restrict key → API restrictions). Een Android-
app-restrictie (pakketnaam
nl.hiertoen.app+ SHA-1 van de debug-keystore) kan ook, maar is voor dit veldtest-gebruik niet verplicht — zonder app-restrictie werkt de sleutel ook prima, alleen iets minder streng afgeschermd. - Zet 'm als repository-secret: GitHub → dit repo → Settings → Secrets and variables →
Actions → New repository secret → naam
STREETVIEW_API_KEY, waarde de sleutel zelf. - Push (of herstart handmatig) een CI-run; de resulterende debug-APK heeft de sleutel dan
ingebakken via
BuildConfig.STREETVIEW_API_KEY.
De sleutel komt nooit in de repository terecht — hij wordt alleen via de GitHub Actions-secret
in de build gelezen (app/build.gradle.kts: Gradle-property → env var → local.properties,
in die volgorde, en anders leeg). Zonder sleutel gedraagt de app zich exact als voorheen.
Nog niet van toepassing — dit is een debug-only scaffold. Zie spec §16.2 voor het
publicatiepad (sideload → besloten testgroep → Play Internal/Closed Testing) en §18 voor het
nog te bevestigen pakketnaam-beslispunt (nl.hiertoen.app, voorlopig).
gradle-wrapper.jaris niet meegecommit (binair bestand); wordt gegenereerd door Android Studio of doorgradle wrapperte draaien.TrackingServiceherstelt zichzelf niet automatisch na een door het OS geforceerde processtop; een onderbroken rit wordt bij de volgende koude start herkend als RECOVERABLE (§6.4) en het startscherm biedt dan "Hervatten" (nieuw segment, oude aggregaten blijven staan) of "Afronden" aan (§4.5) — maar dit is alleen op een echt toestel end-to-end te verifiëren. Voor het geval een rit toch op ACTIVE/PAUSED blijft staan zonder dat de RECOVERABLE-flow triggert, heeft "Eerdere ritten" nu ook een handmatige "Beëindigen"-knop per rit. De fotoweergave (rijscherm én ritdetail) heeft daarnaast altijd een zichtbare sluitknop, zodat een niet-netjes-afgesloten rit de app niet meer kan "vastzetten" op een foto.- Foto's uit een eerdere rit zijn terug te bekijken vanuit het ritdetail (tik op een moment met een getoond beeld); bij een Street View-resultaat staat er ook een knop naar de echte interactieve 360°-panoramaviewer van Google Maps (via intent, geen eigen viewer). Voor Wikimedia-foto's is dat bewust niet aanwezig — de meeste zijn geen equirectangulaire panorama's, dus die knop zou daar meestal niets doen.
- Instellingen (§11) worden per rit één keer gelezen bij start/hervatten, niet live herladen tijdens een lopende rit — een wijziging in Instellingen werkt pas vanaf de volgende rit.
- Ritdetail toont de route op een echte OpenStreetMap-kaart (osmdroid, §18: "MapLibre met
geschikte tile-provider" — hier osmdroid met OSM-raster-tegels, geen API-sleutel nodig).
tile.openstreetmap.orgrechtstreeks aanroepen is prima voor dit persoonlijke veldtest-MVP, maar bij bredere verspreiding hoort daar een eigen tile-provider of caching-laag bij (OSM's tile-usage-policy is niet bedoeld voor productieverkeer van veel gebruikers). Het rijscherm zelf toont nog geen live routekaart (§4.2 "Routekaart: schakelbaar") — dat is nu alleen statistieken, kaart volgt later. Export (GPX/GeoJSON) gaat via Storage Access Framework, geen WRITE_EXTERNAL_STORAGE nodig. CSV-export is bewust overgeslagen (§6.5 noemt het "gewenst"). - Wikimedia Commons (§7.1 rang 2) is de primaire bron; gecureerde archieffoto's (rang 1) en Mapillary zijn Fase 2 (§2.2). Google Street View (rang 4) is er nu wél, maar alleen als fallback wanneer Wikimedia niets bruikbaars oplevert, en altijd expliciet als "actuele opname" — nooit als historisch beeld, precies zoals §8.3 voorschrijft ("de publieke API biedt geen betrouwbare tijdlijnselector"). Zie "Street View instellen" hieronder.
- Fout- en debuglogging voor de beeldzoekopdracht staat onder de tags
HierToen/Wikimedia,HierToen/PhotosenHierToen/Tracking(adb logcat -s HierToen/Wikimedia HierToen/Photos HierToen/Tracking) — nuttig om te zien of een stop wél een zoekopdracht start en wat die oplevert, zonder dat de app crasht als er iets misgaat (elke fout valt terug op PHOTO_NOT_FOUND in plaats van de rit te onderbreken). - De fotoweergave op het rijscherm gaat via
TrackingSessionState.displayedPhoto, expliciet gegate door de pure functiedisplayedPhotoFor()(nooit iets anders dan STILL) — die functie is los getest, maar de intervalwissel van de locatie-polling zelf (sneller pollen tijdens STILL, zodat de foto op tijd verdwijnt) is alleen op een echt toestel te verifiëren. - Geen "toen/nu"-vergelijking, tijdlijn met alternatieve beelden of langere-stilstand-verrijking (§4.3 vervolggedrag) — dat is expliciet Fase 2/latere UX-verfijning, niet MVP.