diff --git a/.maestro/screenshots-nn.yaml b/.maestro/screenshots-nn.yaml new file mode 100644 index 0000000..dae75a5 --- /dev/null +++ b/.maestro/screenshots-nn.yaml @@ -0,0 +1,72 @@ +# Maestro flow: Capture F-Droid screenshots for nn-NO (Nynorsk) +# +# Nynorsk is not supported as a system locale on stock Android. +# This flow uses per-app locale (API 33+) which requires NOT clearing app state, +# since clearState wipes per-app locale settings. +# +# Run the main screenshots.yaml first (any locale) so that map tiles are cached. +# Then set the per-app locale and run this flow: +# +# adb shell "cmd locale set-app-locales no.naiv.tilfluktsrom --locales nn" +# maestro test .maestro/screenshots-nn.yaml + +appId: no.naiv.tilfluktsrom + +env: + OUTPUT_DIR: "fastlane/metadata/android" + +--- + +# Mock GPS: Bergen centrum (Torgallmenningen) +- setLocation: + latitude: 60.3913 + longitude: 5.3221 + +# Launch WITHOUT clearing state (preserves per-app locale and cached tiles) +- launchApp: + appId: no.naiv.tilfluktsrom + clearState: false + permissions: + android.permission.ACCESS_FINE_LOCATION: allow + android.permission.ACCESS_COARSE_LOCATION: allow + +# Wait for shelter data to load +- extendedWaitUntil: + visible: + id: "statusText" + timeout: 15000 + +# Wait for shelter list to populate +- extendedWaitUntil: + visible: + id: "shelterList" + timeout: 15000 + +- waitForAnimationToEnd + +# Screenshot 1: Map view +- takeScreenshot: + path: "${OUTPUT_DIR}/nn-NO/images/phoneScreenshots/1_map_view" + +# Screenshot 2: Tap second shelter +- tapOn: "Håkonsgaten 5" +- waitForAnimationToEnd +- takeScreenshot: + path: "${OUTPUT_DIR}/nn-NO/images/phoneScreenshots/2_shelter_selected" + +# Screenshot 3: Compass view +- tapOn: + id: "toggleViewFab" +- waitForAnimationToEnd +- takeScreenshot: + path: "${OUTPUT_DIR}/nn-NO/images/phoneScreenshots/3_compass_view" + +# Screenshot 4: Civil defense dialog +- tapOn: + id: "toggleViewFab" +- waitForAnimationToEnd +- tapOn: + id: "infoButton" +- waitForAnimationToEnd +- takeScreenshot: + path: "${OUTPUT_DIR}/nn-NO/images/phoneScreenshots/4_civil_defense_info" diff --git a/.maestro/screenshots.yaml b/.maestro/screenshots.yaml new file mode 100644 index 0000000..de15230 --- /dev/null +++ b/.maestro/screenshots.yaml @@ -0,0 +1,95 @@ +# Maestro flow: Capture F-Droid screenshots for Tilfluktsrom +# +# Usage (from project root): +# maestro test .maestro/screenshots.yaml +# +# With a specific locale: +# LOCALE=nb-NO maestro test .maestro/screenshots.yaml +# +# Prerequisites: +# - Android emulator (API 31+) or device running +# - App installed: ./gradlew installDebug +# - Internet connection (needed for map tile caching) +# - Install Maestro: curl -Ls https://get.maestro.mobile.dev | bash +# +# GPS is mocked to Bergen centrum (Torgallmenningen area), +# which has several shelters within walking distance. + +appId: no.naiv.tilfluktsrom + +env: + LOCALE: "en-US" + OUTPUT_DIR: "fastlane/metadata/android" + +--- + +# Mock GPS: Bergen centrum (Torgallmenningen) +- setLocation: + latitude: 60.3913 + longitude: 5.3221 + +# Grant location permission and launch with fresh state +- launchApp: + appId: no.naiv.tilfluktsrom + clearState: true + permissions: + android.permission.ACCESS_FINE_LOCATION: allow + android.permission.ACCESS_COARSE_LOCATION: allow + +# Wait for shelter data to load from bundled asset. +- extendedWaitUntil: + visible: + id: "statusText" + timeout: 15000 + +# The app shows a map caching prompt on first GPS fix when no cache exists. +# Tap "Cache map" and wait for caching to complete (~15s). +# This prevents the overlay from re-appearing on subsequent GPS updates. +- extendedWaitUntil: + visible: + id: "loadingOkButton" + timeout: 15000 + +- tapOn: + id: "loadingOkButton" + +# Wait for caching to finish (4 zoom levels x 9 grid points x 300ms ≈ 11s + network) +- extendedWaitUntil: + notVisible: + id: "loadingOverlay" + timeout: 30000 + +# Let the map settle and shelter list populate after caching +- waitForAnimationToEnd +- extendedWaitUntil: + visible: + id: "shelterList" + timeout: 10000 + +# Screenshot 1: Map view with shelter markers and bottom sheet +- takeScreenshot: + path: "${OUTPUT_DIR}/${LOCALE}/images/phoneScreenshots/1_map_view" + +# Screenshot 2: Tap the second shelter (Håkonsgaten 5) to show a different selection. +# Shelter addresses are locale-independent, so this works in all languages. +- tapOn: "Håkonsgaten 5" +- waitForAnimationToEnd +- takeScreenshot: + path: "${OUTPUT_DIR}/${LOCALE}/images/phoneScreenshots/2_shelter_selected" + +# Screenshot 3: Switch to compass view +- tapOn: + id: "toggleViewFab" +- waitForAnimationToEnd +- takeScreenshot: + path: "${OUTPUT_DIR}/${LOCALE}/images/phoneScreenshots/3_compass_view" + +# Screenshot 4: Switch back to map, open civil defense info dialog +- tapOn: + id: "toggleViewFab" +- waitForAnimationToEnd +- tapOn: + id: "infoButton" +- waitForAnimationToEnd +- takeScreenshot: + path: "${OUTPUT_DIR}/${LOCALE}/images/phoneScreenshots/4_civil_defense_info" diff --git a/.maestro/take-screenshots.sh b/.maestro/take-screenshots.sh new file mode 100755 index 0000000..d44bb4e --- /dev/null +++ b/.maestro/take-screenshots.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Generate F-Droid screenshots for all locales using Maestro. +# +# Prerequisites: +# 1. Install Maestro: curl -Ls https://get.maestro.mobile.dev | bash +# 2. Start an Android emulator (API 33+): emulator -avd +# 3. Build and install the app: ./gradlew installDebug +# 4. Run this script: .maestro/take-screenshots.sh +# +# Screenshots are saved directly into fastlane/metadata/android//images/ +# +# Locale handling: +# - en-US and nb-NO use system locale (settings put system system_locales) +# - nn-NO (Nynorsk) requires per-app locale since Android doesn't support +# Nynorsk as a system locale — it falls back to Bokmål + +set -euo pipefail +cd "$(dirname "$0")/.." + +FLOW=".maestro/screenshots.yaml" +FLOW_NN=".maestro/screenshots-nn.yaml" + +restart_framework() { + adb shell stop 2>/dev/null + sleep 2 + adb shell start 2>/dev/null + sleep 8 +} + +ensure_root() { + adb root 2>/dev/null || true + sleep 1 +} + +echo "=== Ensuring root access ===" +ensure_root + +# --- en-US --- +echo "=== Capturing screenshots for en-US ===" +mkdir -p "fastlane/metadata/android/en-US/images/phoneScreenshots" +rm -f "fastlane/metadata/android/en-US/images/.gitkeep" + +adb shell "settings put system system_locales en-US" +restart_framework + +sed -i 's/LOCALE: ".*"/LOCALE: "en-US"/' "$FLOW" +maestro test "$FLOW" +echo "=== Done: en-US ===" +echo "" + +# --- nb-NO --- +echo "=== Capturing screenshots for nb-NO ===" +mkdir -p "fastlane/metadata/android/nb-NO/images/phoneScreenshots" +rm -f "fastlane/metadata/android/nb-NO/images/.gitkeep" + +adb shell "settings put system system_locales nb-NO" +restart_framework + +sed -i 's/LOCALE: ".*"/LOCALE: "nb-NO"/' "$FLOW" +maestro test "$FLOW" +sed -i 's/LOCALE: "nb-NO"/LOCALE: "en-US"/' "$FLOW" +echo "=== Done: nb-NO ===" +echo "" + +# --- nn-NO (Nynorsk) --- +# Android doesn't support nn as a system locale, so we use per-app locale. +# The main flow must have run first to cache map tiles (nn flow uses clearState: false). +echo "=== Capturing screenshots for nn-NO ===" +mkdir -p "fastlane/metadata/android/nn-NO/images/phoneScreenshots" +rm -f "fastlane/metadata/android/nn-NO/images/.gitkeep" + +adb shell "am force-stop no.naiv.tilfluktsrom" +adb shell "cmd locale set-app-locales no.naiv.tilfluktsrom --locales nn" +sleep 2 + +maestro test "$FLOW_NN" +echo "=== Done: nn-NO ===" +echo "" + +# Restore en-US +adb shell "settings put system system_locales en-US" +adb shell "cmd locale set-app-locales no.naiv.tilfluktsrom --locales en" +restart_framework + +echo "All screenshots captured." +echo "Check: fastlane/metadata/android/*/images/phoneScreenshots/" diff --git a/STANDING_ON_SHOULDERS.md b/STANDING_ON_SHOULDERS.md new file mode 100644 index 0000000..e33c1bd --- /dev/null +++ b/STANDING_ON_SHOULDERS.md @@ -0,0 +1,162 @@ +# Standing on Shoulders + +## How many people made this app possible? + +Tilfluktsrom is a small Android app — about 900 source files — that helps +Norwegians find their nearest public shelter in an emergency. One person built +it in under a day. But that was only possible because of the accumulated work of +roughly **100,000–120,000 identifiable people**, spanning decades, countries, and +disciplines. + +This document traces the human effort behind every layer of the stack. + +--- + +## Layer 0: Physical Infrastructure — GPS & Sensors (~10,500 people) + +| Component | Role | Est. people | +|---|---|---| +| GPS constellation | 31 satellites, maintained by US Space Force | ~5,000 | +| Magnetometer/compass sensors | Enable the direction arrow to point at shelters | ~500 | +| ARM architecture | The CPU instruction set running every Android device | ~5,000 | + +Before a single line of code runs, hardware designed by tens of thousands of +engineers must be in orbit, in your pocket, and on the circuit board. + +## Layer 1: Internet & Standards (~5,250 people) + +| Component | Role | Est. people | +|---|---|---| +| TCP/IP, DNS, HTTP, TLS | The protocols that carry shelter data from server to phone | ~5,000 | +| GeoJSON specification | The format the shelter data is published in (IETF RFC 7946) | ~50 | +| EPSG / coordinate reference systems | The math behind UTM33N → WGS84 coordinate conversion | ~200 | + +## Layer 2: Operating Systems & Runtimes (~27,200 people) + +| Component | Role | Est. people | +|---|---|---| +| Linux kernel | Foundation of Android; ~20,000 documented unique contributors | ~20,000 | +| Android (AOSP) | The mobile OS, built on Linux by Google + community | ~5,000 | +| JVM / OpenJDK + Java | The language runtime Kotlin compiles to | ~2,000 | +| ART (Android Runtime) | Replaced Dalvik; runs the compiled bytecode | ~200 | + +## Layer 3: Programming Languages (~1,200 people) + +| Language | Origin | Est. people | +|---|---|---| +| Kotlin | JetBrains (Czech Republic/Russia) + community | ~500 | +| TypeScript | Microsoft + community (for the PWA) | ~500 | +| Groovy / Kotlin DSL | Gradle build scripts | ~200 | + +## Layer 4: Build Tools & Dev Infrastructure (~5,400 people) + +| Tool | Role | Est. people | +|---|---|---| +| Gradle | Build automation | ~500 | +| Android Gradle Plugin | Android-specific build pipeline | ~200 | +| KSP (Kotlin Symbol Processing) | Code generation for Room database | ~100 | +| R8 / ProGuard | Release minification and optimization | ~100 | +| Vite | PWA bundler | ~800 | +| Bun | Package manager and JS runtime | ~400 | +| Git | Version control | ~1,500 | +| Android Studio / IntelliJ | IDE (JetBrains + Google) | ~1,500 | +| Maven Central, Google Maven, npm | Package registry infrastructure | ~300 | + +## Layer 5: Libraries — Android App (~2,550 people) + +| Library | What it does | Est. people | +|---|---|---| +| AndroidX (Core, AppCompat, Activity, Lifecycle) | UI and app architecture foundation | ~800 | +| Material Design | Visual design language, research, and components | ~500 | +| ConstraintLayout | Flexible screen layouts | ~100 | +| Room | Type-safe SQLite wrapper for the shelter cache | ~200 | +| WorkManager | Periodic home screen widget updates | ~150 | +| Kotlinx Coroutines | Async data loading without blocking the UI | ~200 | +| OkHttp (Square) | Downloads the GeoJSON ZIP from Geonorge | ~200 | +| OSMDroid | Offline OpenStreetMap rendering | ~150 | +| Play Services Location | FusedLocationProvider for precise GPS | ~200 | +| SQLite | The embedded database engine | ~50 | + +## Layer 6: Libraries — PWA (~1,350 people) + +| Library | Role | Est. people | +|---|---|---| +| Leaflet | Interactive web maps (created in Ukraine) | ~800 | +| leaflet.offline | Offline tile caching | ~20 | +| idb | IndexedDB wrapper for offline storage | ~30 | +| vite-plugin-pwa | Service worker and Workbox integration | ~100 | +| Vitest | Test framework | ~400 | + +## Layer 7: Data — The Content That Makes It Useful (~56,000 people) + +| Source | Role | Est. people | +|---|---|---| +| OpenStreetMap | Global map data; ~2M registered mappers, ~10,000+ active in Norway | ~50,000 | +| Kartverket / Geonorge | Norwegian Mapping Authority; national geodata infrastructure | ~800 | +| DSB (Direktoratet for samfunnssikkerhet og beredskap) | Created and maintains the public shelter registry | ~200 | +| The shelter builders | Construction, engineering, civil defense planning since the Cold War | ~5,000+ | + +The app's data exists because of Cold War civil defense planning. The shelters +were built in the 1950s–80s, digitized by DSB, published via Geonorge's open +data mandate — a chain of decisions spanning 70 years that now fits in a 320 KB +GeoJSON file. + +## Layer 8: AI-Assisted Development (~6,000 people) + +| Component | Role | Est. people | +|---|---|---| +| Anthropic / Claude | Researchers, engineers, safety team | ~1,000 | +| ML research lineage | Transformers, attention, RLHF, scaling laws — across academia & industry | ~5,000 | +| Training data | The collective written output of humanity | incalculable | + +## Layer 9: Distribution (~500 people) + +| Component | Role | Est. people | +|---|---|---| +| F-Droid | Open-source app store infrastructure and review | ~300 | +| Fastlane | Metadata and screenshot tooling | ~200 | + +--- + +## Summary + +| Layer | People | +|---|---| +| Physical infrastructure (GPS, ARM, sensors) | ~10,500 | +| Internet & standards | ~5,250 | +| Operating systems & runtimes | ~27,200 | +| Programming languages | ~1,200 | +| Build tools & dev infrastructure | ~5,400 | +| Direct libraries (Android) | ~2,550 | +| Direct libraries (PWA) | ~1,350 | +| Data (maps, shelters, geodesy) | ~56,000 | +| AI-assisted development | ~6,000 | +| Distribution | ~500 | +| **Conservative total** | **~116,000** | + +This is conservative. It excludes: + +- The millions of OSM mappers globally whose edits feed the tile rendering pipeline +- Hardware manufacturing (semiconductor fabs, device assembly — millions of workers) +- The educators who taught all these people their craft +- The civil defense planners who decided Norway needed public shelters +- The mathematicians behind Haversine, UTM projections, and geodesy going back centuries + +Including OpenStreetMap's full contributor base and hardware, the number crosses +**2 million** easily. + +--- + +## Perspective + +For every line of application code, roughly 100,000 people made the tools, data, +and infrastructure that line depends on. No single company, country, or +organization could have built this stack alone. Linux (Finland → global), Kotlin +(Czech Republic/Russia → JetBrains), OSM (UK → global), GPS (US military → +civilian), Leaflet (Ukraine), SQLite (US, public domain) — this emergency app is +a product of genuine global cooperation. + +The fact that one person can build a working, offline-capable emergency app in +under a day is arguably one of the most remarkable expressions of accumulated +human cooperation — and almost none of it was coordinated by any central +authority. diff --git a/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt b/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt index af96248..2f11f27 100644 --- a/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt +++ b/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt @@ -298,7 +298,11 @@ class MainActivity : AppCompatActivity(), SensorEventListener { Toast.makeText(this@MainActivity, R.string.error_shelter_not_found, Toast.LENGTH_SHORT).show() } } - currentLocation?.let { updateNearestShelters(it) } + if (currentLocation != null) { + updateNearestShelters(currentLocation!!) + } else { + showWaitingForLocation() + } } } catch (e: CancellationException) { throw e @@ -724,6 +728,15 @@ class MainActivity : AppCompatActivity(), SensorEventListener { binding.loadingOverlay.visibility = View.GONE } + /** + * Show a waiting state in the bottom sheet when shelters are loaded + * but no GPS fix is available yet. + */ + private fun showWaitingForLocation() { + binding.selectedShelterAddress.text = getString(R.string.status_no_location) + binding.selectedShelterDetails.text = getString(R.string.status_shelters_loaded, allShelters.size) + } + /** Persist last GPS fix so the widget can use it even when the app isn't running. */ private fun saveLastLocation(location: Location) { getSharedPreferences("widget_prefs", Context.MODE_PRIVATE).edit() diff --git a/fastlane/metadata/android/en-US/changelogs/8.txt b/fastlane/metadata/android/en-US/changelogs/8.txt new file mode 100644 index 0000000..c6d65db --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/8.txt @@ -0,0 +1,2 @@ +- Show waiting status when GPS is unavailable instead of empty shelter list +- Add F-Droid screenshots for all locales diff --git a/fastlane/metadata/android/en-US/images/.gitkeep b/fastlane/metadata/android/en-US/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_map_view.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_map_view.png new file mode 100644 index 0000000..5d9675f Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_map_view.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_shelter_selected.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_shelter_selected.png new file mode 100644 index 0000000..85dcd32 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_shelter_selected.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3_compass_view.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_compass_view.png new file mode 100644 index 0000000..22f723e Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_compass_view.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_civil_defense_info.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_civil_defense_info.png new file mode 100644 index 0000000..c732755 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_civil_defense_info.png differ diff --git a/fastlane/metadata/android/nb-NO/changelogs/8.txt b/fastlane/metadata/android/nb-NO/changelogs/8.txt new file mode 100644 index 0000000..49bed3b --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/8.txt @@ -0,0 +1,2 @@ +- Vis ventestatus når GPS ikke er tilgjengelig i stedet for tom tilfluktsromliste +- Legg til F-Droid-skjermbilder for alle språk diff --git a/fastlane/metadata/android/nb-NO/images/.gitkeep b/fastlane/metadata/android/nb-NO/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1_map_view.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1_map_view.png new file mode 100644 index 0000000..bddc652 Binary files /dev/null and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1_map_view.png differ diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2_shelter_selected.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2_shelter_selected.png new file mode 100644 index 0000000..2fe8b38 Binary files /dev/null and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2_shelter_selected.png differ diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3_compass_view.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3_compass_view.png new file mode 100644 index 0000000..a89d2a2 Binary files /dev/null and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3_compass_view.png differ diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4_civil_defense_info.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4_civil_defense_info.png new file mode 100644 index 0000000..9b5edb0 Binary files /dev/null and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4_civil_defense_info.png differ diff --git a/fastlane/metadata/android/nn-NO/changelogs/8.txt b/fastlane/metadata/android/nn-NO/changelogs/8.txt new file mode 100644 index 0000000..d96550b --- /dev/null +++ b/fastlane/metadata/android/nn-NO/changelogs/8.txt @@ -0,0 +1,2 @@ +- Vis ventestatus når GPS ikkje er tilgjengeleg i staden for tom tilfluktsromliste +- Legg til F-Droid-skjermbilete for alle språk diff --git a/fastlane/metadata/android/nn-NO/images/.gitkeep b/fastlane/metadata/android/nn-NO/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/fastlane/metadata/android/nn-NO/images/phoneScreenshots/1_map_view.png b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/1_map_view.png new file mode 100644 index 0000000..2a3b179 Binary files /dev/null and b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/1_map_view.png differ diff --git a/fastlane/metadata/android/nn-NO/images/phoneScreenshots/2_shelter_selected.png b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/2_shelter_selected.png new file mode 100644 index 0000000..037c472 Binary files /dev/null and b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/2_shelter_selected.png differ diff --git a/fastlane/metadata/android/nn-NO/images/phoneScreenshots/3_compass_view.png b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/3_compass_view.png new file mode 100644 index 0000000..1128bb9 Binary files /dev/null and b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/3_compass_view.png differ diff --git a/fastlane/metadata/android/nn-NO/images/phoneScreenshots/4_civil_defense_info.png b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/4_civil_defense_info.png new file mode 100644 index 0000000..da75ca3 Binary files /dev/null and b/fastlane/metadata/android/nn-NO/images/phoneScreenshots/4_civil_defense_info.png differ diff --git a/version.properties b/version.properties index ef64dbc..e9b517b 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ versionMajor=1 -versionMinor=4 -versionPatch=1 -versionCode=7 +versionMinor=5 +versionPatch=0 +versionCode=8