Compare commits

...

4 commits

Author SHA1 Message Date
567af09072 Bump versjon til v1.5.0 (versionCode 8)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:19:12 +01:00
73b583111a Legg til dokument om menneska bak applikasjonen
Estimerer at ~116 000 personar har bidrege til verktøya, biblioteka,
datakjeldene og infrastrukturen som gjer det mogleg å byggje denne appen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:17:23 +01:00
093de8487b Legg til F-Droid-skjermbilete og Maestro-automatisering
Automatiserte skjermbilete for alle tre språk (en-US, nb-NO, nn-NO)
med Maestro-flyt. Fire bilete per språk: kartvisning, valt tilfluktsrom,
kompassvisning og sivilforsvarsinfo. GPS-posisjon er sett til Bergen
sentrum (Torgallmenningen).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:17:16 +01:00
15c6797fcd Vis ventestatus i botnark når GPS ikkje er tilgjengeleg
Når tilfluktsromdata er lasta men GPS-posisjon manglar, viser
botnaka no «Ventar på GPS…» i staden for å vera tom. Posisjonsfiks
erstattar ventestatusen automatisk når han kjem.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:17:07 +01:00
24 changed files with 438 additions and 4 deletions

View file

@ -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"

95
.maestro/screenshots.yaml Normal file
View file

@ -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"

86
.maestro/take-screenshots.sh Executable file
View file

@ -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 <avd_name>
# 3. Build and install the app: ./gradlew installDebug
# 4. Run this script: .maestro/take-screenshots.sh
#
# Screenshots are saved directly into fastlane/metadata/android/<locale>/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/"

162
STANDING_ON_SHOULDERS.md Normal file
View file

@ -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,000120,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 1950s80s, 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.

View file

@ -298,7 +298,11 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
Toast.makeText(this@MainActivity, R.string.error_shelter_not_found, Toast.LENGTH_SHORT).show() 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) { } catch (e: CancellationException) {
throw e throw e
@ -724,6 +728,15 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
binding.loadingOverlay.visibility = View.GONE 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. */ /** Persist last GPS fix so the widget can use it even when the app isn't running. */
private fun saveLastLocation(location: Location) { private fun saveLastLocation(location: Location) {
getSharedPreferences("widget_prefs", Context.MODE_PRIVATE).edit() getSharedPreferences("widget_prefs", Context.MODE_PRIVATE).edit()

View file

@ -0,0 +1,2 @@
- Show waiting status when GPS is unavailable instead of empty shelter list
- Add F-Droid screenshots for all locales

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

View file

@ -1,4 +1,4 @@
versionMajor=1 versionMajor=1
versionMinor=4 versionMinor=5
versionPatch=1 versionPatch=0
versionCode=7 versionCode=8