diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..c544235
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,455 @@
+# Architecture
+
+This document describes the architecture of Tilfluktsrom, a Norwegian emergency shelter finder available as an Android app and a Progressive Web App (PWA). Both platforms share the same design philosophy — offline-first, no hard external dependencies — but are implemented independently.
+
+## Table of Contents
+
+- [Design Principles](#design-principles)
+- [Data Pipeline](#data-pipeline)
+- [Android App](#android-app)
+ - [Package Structure](#package-structure)
+ - [Data Layer](#data-layer)
+ - [Location & Navigation](#location--navigation)
+ - [Compass System](#compass-system)
+ - [Map & Tile Caching](#map--tile-caching)
+ - [Build Variants](#build-variants)
+ - [Home Screen Widget](#home-screen-widget)
+ - [Deep Linking](#deep-linking)
+- [Progressive Web App](#progressive-web-app)
+ - [Module Structure](#module-structure)
+ - [Build System](#build-system)
+ - [Data Layer (PWA)](#data-layer-pwa)
+ - [Location & Compass (PWA)](#location--compass-pwa)
+ - [Map & Offline Tiles (PWA)](#map--offline-tiles-pwa)
+ - [Service Worker](#service-worker)
+ - [UI Components](#ui-components)
+- [Shared Algorithms](#shared-algorithms)
+ - [UTM33N → WGS84 Conversion](#utm33n--wgs84-conversion)
+ - [Haversine Distance & Bearing](#haversine-distance--bearing)
+- [Offline Capability Summary](#offline-capability-summary)
+- [Security & Privacy](#security--privacy)
+- [Internationalization](#internationalization)
+
+---
+
+## Design Principles
+
+### Offline-First
+This is an emergency app. Core functionality — finding the nearest shelter, compass navigation, distance display — must work without internet after initial setup. Network is used only for initial data download, periodic refresh, and map tile caching.
+
+### De-Google Compatibility (Android)
+The Android app runs on devices without Google Play Services (LineageOS, GrapheneOS, /e/OS). Every Google-specific API has an AOSP fallback. Play Services improve accuracy and battery life when available, but are never required.
+
+### Minimal Dependencies
+Both platforms use few, well-chosen libraries. No heavy frameworks, no external CDNs at runtime. The PWA bundles everything locally; the Android app uses only OSMDroid, Room, OkHttp, and WorkManager.
+
+### Data Sovereignty
+Shelter data comes directly from Geonorge (the Norwegian mapping authority). No intermediate servers. The app fetches, converts, and caches the data locally.
+
+---
+
+## Data Pipeline
+
+Both platforms consume the same upstream data source:
+
+```
+Geonorge ZIP (EPSG:25833 UTM33N)
+ ↓ Download (~320KB)
+ ↓ Extract GeoJSON from ZIP
+ ↓ Parse features
+ ↓ Convert UTM33N → WGS84 (Karney method)
+ ↓ Validate (Norway bounding box, required fields)
+ ↓ Store locally
+```
+
+**Source URL:** `https://nedlasting.geonorge.no/geonorge/Samfunnssikkerhet/TilfluktsromOffentlige/GeoJSON/Samfunnssikkerhet_0000_Norge_25833_TilfluktsromOffentlige_GeoJSON.zip`
+
+**Fields per shelter:**
+
+| Field | Description |
+|------------|--------------------------------------|
+| `lokalId` | Unique identifier (UUID) |
+| `romnr` | Shelter room number |
+| `plasser` | Capacity (number of people) |
+| `adresse` | Street address |
+| `latitude` | WGS84 latitude (converted from UTM) |
+| `longitude`| WGS84 longitude (converted from UTM) |
+
+**When conversion happens:**
+- **Android:** At runtime during data download (`ShelterGeoJsonParser`)
+- **PWA:** At build time (`scripts/fetch-shelters.ts`), output is pre-converted `shelters.json`
+
+---
+
+## Android App
+
+**Language:** Kotlin · **Min SDK:** 26 (Android 8.0) · **Target SDK:** 35
+**Build:** Gradle 8.7, AGP 8.5.2, KSP for Room annotation processing
+
+### Package Structure
+
+```
+no.naiv.tilfluktsrom/
+├── TilfluktsromApp.kt # Application class (OSMDroid config)
+├── MainActivity.kt # Central UI controller (~870 lines)
+├── data/
+│ ├── Shelter.kt # Room entity
+│ ├── ShelterDatabase.kt # Room database singleton
+│ ├── ShelterDao.kt # Data access object (Flow queries)
+│ ├── ShelterRepository.kt # Repository: bundled seed + network refresh
+│ ├── ShelterGeoJsonParser.kt # GeoJSON ZIP → Shelter list
+│ └── MapCacheManager.kt # Offline map tile pre-caching
+├── location/
+│ ├── LocationProvider.kt # GPS provider (flavor-specific)
+│ └── ShelterFinder.kt # Nearest N shelters by Haversine
+├── ui/
+│ ├── DirectionArrowView.kt # Custom compass arrow View
+│ ├── ShelterListAdapter.kt # RecyclerView adapter for shelter list
+│ ├── CivilDefenseInfoDialog.kt # Emergency instructions
+│ └── AboutDialog.kt # Privacy and copyright
+├── util/
+│ ├── CoordinateConverter.kt # UTM33N → WGS84 (Karney method)
+│ └── DistanceUtils.kt # Haversine distance and bearing
+└── widget/
+ ├── ShelterWidgetProvider.kt # Home screen widget (flavor-specific)
+ └── WidgetUpdateWorker.kt # WorkManager periodic update
+```
+
+Files under `location/` and `widget/` have separate implementations per build variant:
+- `app/src/standard/java/` — Google Play Services variant
+- `app/src/fdroid/java/` — AOSP-only variant
+
+### Data Layer
+
+**Storage:** Room (SQLite) with a single `shelters` table.
+
+**Loading strategy (three-layer fallback):**
+
+1. **Bundled asset** (`assets/shelters.json`): Pre-converted WGS84 data, loaded on first launch via `ShelterRepository.seedFromAsset()`. Marked as stale (timestamp=0) so a network refresh is attempted when possible.
+
+2. **Room database**: ~556 shelters cached locally. Reactive updates via Kotlin Flow. Atomic refresh: `deleteAll()` + `insertAll()` in a single transaction.
+
+3. **Network refresh**: Downloads the Geonorge ZIP via OkHttp, parses with `ShelterGeoJsonParser`. Staleness threshold: 7 days. Runs in the background; failure does not block the UI.
+
+**GeoJSON parsing pipeline:**
+```
+OkHttp response stream
+ → ZipInputStream (find .geojson entry, 10MB size limit)
+ → JSON parsing (features array)
+ → Per feature:
+ → Extract UTM33N coordinates
+ → CoordinateConverter.utm33nToWgs84()
+ → Validate: lokalId not blank, plasser ≥ 0, within Norway bounds
+ → Create Shelter entity (skip malformed features with warning)
+```
+
+### Location & Navigation
+
+**LocationProvider** abstracts GPS access with two flavor implementations:
+
+| Flavor | Primary | Fallback |
+|-----------|----------------------------------|-------------------|
+| standard | FusedLocationProviderClient | LocationManager |
+| fdroid | LocationManager | — |
+
+Both emit location updates via Kotlin Flow. Update interval: 5 seconds, fastest 2 seconds.
+
+**ShelterFinder** takes the user's position and all shelters, computes Haversine distance and initial bearing for each, sorts by distance, and returns the N nearest (default: 3) as `ShelterWithDistance` objects.
+
+### Compass System
+
+**Sensor priority:**
+1. Rotation Vector sensor (`TYPE_ROTATION_VECTOR`) — most accurate, single sensor
+2. Accelerometer + Magnetometer — low-pass filtered (α=0.25) for smoothing
+3. No compass available — error message shown
+
+**Direction calculation:**
+```
+arrowAngle = shelterBearing − deviceHeading
+```
+
+**DirectionArrowView** is a custom View that draws:
+- A large arrow rotated by `arrowAngle`, pointing toward the shelter
+- An optional north indicator on the perimeter for compass calibration
+
+### Map & Tile Caching
+
+**Map library:** OSMDroid (OpenStreetMap, no Google dependency).
+
+**Tile caching:** OSMDroid's built-in `SqlTileWriter` passively caches every tile loaded. `MapCacheManager` supplements this with active pre-caching:
+
+- Pans the MapView across a 3×3 grid at zoom levels 10, 12, 14, 16
+- 300ms delay between pans (respects OSM tile usage policy)
+- Covers ~15km radius around the user
+- Progress reported via callback for UI display
+
+Tile cache stored in app-specific internal storage (`osmdroidBasePath`).
+
+### Build Variants
+
+```
+productFlavors {
+ standard { } // Google Play Services + AOSP fallback
+ fdroid { } // AOSP only
+}
+```
+
+The `standard` flavor adds `com.google.android.gms:play-services-location`. Runtime detection via `GoogleApiAvailability` determines which code path runs.
+
+Both flavors produce identical user experiences — `standard` achieves faster GPS fixes and better battery efficiency when Play Services are present.
+
+### Home Screen Widget
+
+**ShelterWidgetProvider** displays the nearest shelter's address, capacity, and distance. Updated by:
+
+1. **MainActivity** — sends latest location on each GPS update
+2. **WorkManager** — `WidgetUpdateWorker` runs every 15 minutes, requests a fresh location fix
+3. **Manual** — user taps refresh button on the widget
+
+**Location resolution (priority order):**
+1. Location from intent (WorkManager or MainActivity)
+2. FusedLocationProviderClient cache (standard)
+3. Active GPS request (10s timeout)
+4. LocationManager cache
+5. SharedPreferences saved location (max 24h old)
+
+### Deep Linking
+
+**HTTPS App Links:** `https://tilfluktsrom.naiv.no/shelter/{lokalId}`
+
+The domain is configured in one place: `DEEP_LINK_DOMAIN` in `build.gradle.kts` (exposed as `BuildConfig.DEEP_LINK_DOMAIN` and manifest placeholder `${deepLinkHost}`).
+
+- `autoVerify="true"` on the HTTPS intent filter triggers Android's App Links verification at install time
+- Verification requires `/.well-known/assetlinks.json` to be served by the PWA (in `pwa/public/.well-known/`)
+- If the app is installed and verified, `/shelter/*` links open the app directly (no disambiguation dialog)
+- If not installed, the link opens in the browser, where the PWA handles it
+
+Share messages include the HTTPS URL, which SMS apps auto-link as a tappable URL.
+
+---
+
+## Progressive Web App
+
+**Stack:** TypeScript, Vite 5, Leaflet, idb (IndexedDB wrapper), vite-plugin-pwa
+**Package manager:** bun
+
+### Module Structure
+
+```
+pwa/
+├── index.html # SPA shell, CSP headers, semantic layout
+├── vite.config.ts # Build config, PWA plugin, tile caching rules
+├── manifest.webmanifest # PWA metadata and icons
+├── scripts/
+│ └── fetch-shelters.ts # Build-time: download + convert shelter data
+├── public/
+│ └── data/shelters.json # Pre-processed shelter data (build artifact)
+└── src/
+ ├── main.ts # Entry point, SW registration, locale init
+ ├── app.ts # Main controller (~400 lines)
+ ├── types.ts # Shelter, ShelterWithDistance, LatLon interfaces
+ ├── data/
+ │ ├── shelter-repository.ts # Fetch + IndexedDB storage
+ │ └── shelter-db.ts # IndexedDB wrapper (idb library)
+ ├── location/
+ │ ├── location-provider.ts # navigator.geolocation wrapper
+ │ ├── compass-provider.ts # DeviceOrientationEvent (iOS/Android)
+ │ └── shelter-finder.ts # Haversine nearest-N calculation
+ ├── ui/
+ │ ├── map-view.ts # Leaflet map, custom SVG markers
+ │ ├── compass-view.ts # Canvas-based direction arrow
+ │ ├── shelter-list.ts # Bottom sheet shelter list
+ │ ├── loading-overlay.ts # Modal spinner / cache prompt
+ │ ├── about-dialog.ts # Privacy, data info, cache clear
+ │ ├── civil-defense-dialog.ts # DSB 5-step emergency guide
+ │ └── status-bar.ts # Status text and refresh button
+ ├── cache/
+ │ └── map-cache-manager.ts # Tile pre-caching via programmatic panning
+ ├── i18n/
+ │ ├── i18n.ts # Locale detection, string substitution
+ │ ├── en.ts # English strings
+ │ ├── nb.ts # Bokmål strings
+ │ └── nn.ts # Nynorsk strings
+ ├── util/
+ │ └── distance-utils.ts # Haversine distance, bearing, formatting
+ └── main.css # Dark theme, Leaflet overrides, dialogs
+```
+
+### Build System
+
+**Vite** bundles the app with content-hashed filenames for cache busting.
+
+**Key configuration:**
+- Base path: `./` (relative, deployable anywhere)
+- `__BUILD_REVISION__` define: ISO timestamp injected at build time, used to invalidate service worker caches
+- **vite-plugin-pwa**: Generates the service worker with Workbox. Precaches all static assets. Runtime-caches OSM tile requests with CacheFirst strategy (30-day expiry, max 5000 entries).
+
+**Build-time data preprocessing:**
+`scripts/fetch-shelters.ts` downloads the Geonorge ZIP, extracts the GeoJSON, converts UTM33N→WGS84, validates, and writes `public/data/shelters.json`. This means coordinate conversion is a build step, not a runtime cost.
+
+```bash
+bun run fetch-shelters # Download and convert shelter data
+bun run build # TypeScript check + Vite build + SW generation
+```
+
+### Data Layer (PWA)
+
+**Storage:** IndexedDB via the `idb` library.
+
+**Schema:**
+- Object store `shelters` (keyPath: `lokalId`) — full shelter records
+- Object store `metadata` — `lastUpdate` timestamp
+
+**Loading strategy:**
+1. Check IndexedDB for cached shelters
+2. If empty or stale (>7 days): fetch `shelters.json` (precached by service worker)
+3. Replace all records in a single transaction
+4. Reactive: UI reads from IndexedDB after load
+
+Unlike the Android app, the PWA does not perform coordinate conversion at runtime — `shelters.json` is pre-converted at build time and served as a static asset.
+
+### Location & Compass (PWA)
+
+**Location:** `navigator.geolocation.watchPosition()` with high accuracy enabled, 10s maximum age, 30s timeout.
+
+**Compass:** DeviceOrientationEvent with platform-specific handling:
+
+| Platform | API | Heading Source |
+|----------------|------------------------------|--------------------------|
+| iOS Safari | `deviceorientation` | `webkitCompassHeading` |
+| Android Chrome | `deviceorientationabsolute` | `(360 − alpha) % 360` |
+
+iOS 13+ requires an explicit permission request (`DeviceOrientationEvent.requestPermission()`), triggered by a user gesture.
+
+Low-pass filter (smoothing factor 0.3) with 0/360° wraparound handling ensures fluid rotation.
+
+### Map & Offline Tiles (PWA)
+
+**Map library:** Leaflet with OpenStreetMap tiles. No CDN — Leaflet is bundled from `node_modules`.
+
+**Custom markers:** SVG-based `divIcon` elements — orange house with "T" for shelters, blue circle for user location. Selected shelter uses a larger yellow marker.
+
+**Auto-fit logic:** When a shelter is selected, the map fits bounds to show both user and shelter (30% padding), unless the user has manually panned or zoomed. A "reset view" button appears after manual interaction.
+
+**Tile pre-caching:** `MapCacheManager` uses the same approach as the Android app — programmatically pans the map across a 3×3 grid at 4 zoom levels. The service worker's runtime cache intercepts and stores the tile requests. Cache location stored in localStorage (rounded to ~11km precision for privacy).
+
+### Service Worker
+
+Generated by vite-plugin-pwa (Workbox):
+
+- **Precaching:** All static assets (JS, CSS, HTML, JSON, images) with content-hash versioning
+- **Runtime caching:** OSM tiles from `{a,b,c}.tile.openstreetmap.org` with CacheFirst strategy, 30-day TTL, max 5000 entries
+- **Navigation fallback:** Serves `index.html` for all navigation requests (SPA behavior)
+- **Auto-update:** New service worker activates automatically; `controllerchange` event notifies the user
+
+`main.ts` injects the build revision into the service worker context, ensuring each build invalidates stale caches. It also requests persistent storage (`navigator.storage.persist()`) to prevent the browser from evicting cached data.
+
+### UI Components
+
+The PWA uses no framework — all UI is vanilla TypeScript manipulating the DOM.
+
+| Component | Description |
+|--------------------------|----------------------------------------------------------|
+| `app.ts` | Main controller: wires components, manages state |
+| `map-view.ts` | Leaflet map with custom markers, auto-fit, interaction tracking |
+| `compass-view.ts` | Canvas-rendered arrow with north indicator, requestAnimationFrame |
+| `shelter-list.ts` | Bottom sheet with 3 nearest shelters, click selection |
+| `loading-overlay.ts` | Modal: spinner during load, OK/Skip for cache prompt |
+| `about-dialog.ts` | Privacy statement, data sources, "clear cache" button |
+| `civil-defense-dialog.ts`| DSB emergency instructions (5 steps) |
+| `status-bar.ts` | Data freshness indicator, refresh button |
+
+Dialogs are created dynamically and implement focus management (save/restore previous focus, trap focus inside modal).
+
+---
+
+## Shared Algorithms
+
+Both platforms implement these algorithms independently (no shared code), ensuring each platform has zero runtime dependencies on the other.
+
+### UTM33N → WGS84 Conversion
+
+**Algorithm:** Karney series expansion method.
+
+Converts EUREF89 / UTM zone 33N (EPSG:25833) to WGS84 (EPSG:4326).
+
+**Constants:**
+- Semi-major axis: 6,378,137 m
+- Flattening: 1/298.257223563
+- UTM zone 33 central meridian: 15° E
+- Scale factor: 0.9996
+- False easting: 500,000 m
+
+**Steps:**
+1. Remove false easting/northing
+2. Compute footprint latitude using series expansion
+3. Apply iterative corrections for latitude and longitude
+4. Add central meridian offset
+
+**Validation:** Reject results outside Norway bounding box (57–72°N, 3–33°E).
+
+**Implementations:**
+- Android: `util/CoordinateConverter.kt` (runtime)
+- PWA: `scripts/fetch-shelters.ts` (build-time)
+
+### Haversine Distance & Bearing
+
+**Distance** between two WGS84 points:
+```
+a = sin²(Δφ/2) + cos(φ₁) · cos(φ₂) · sin²(Δλ/2)
+d = 2R · atan2(√a, √(1−a))
+```
+Where R = 6,371 km (Earth mean radius).
+
+**Initial bearing** from point 1 to point 2:
+```
+θ = atan2(sin(Δλ) · cos(φ₂), cos(φ₁) · sin(φ₂) − sin(φ₁) · cos(φ₂) · cos(Δλ))
+```
+Result in degrees: 0° = north, 90° = east, 180° = south, 270° = west.
+
+**Implementations:**
+- Android: `util/DistanceUtils.kt`
+- PWA: `util/distance-utils.ts`
+
+---
+
+## Offline Capability Summary
+
+| Capability | Android | PWA |
+|-------------------------|----------------------------------|------------------------------------|
+| Shelter data | Room DB + bundled asset seed | IndexedDB + precached JSON |
+| Map tiles | OSMDroid SqlTileWriter + active pre-cache | Service worker CacheFirst + active pre-cache |
+| GPS | Device hardware (no network) | Device hardware (no network) |
+| Compass | Device sensors | DeviceOrientationEvent |
+| Distance/bearing math | Local computation | Local computation |
+| Network required for | Initial refresh, new tiles | Initial page load, new tiles |
+| Staleness threshold | 7 days | 7 days |
+
+---
+
+## Security & Privacy
+
+- **No tracking:** No analytics, no telemetry, no external requests beyond Geonorge data and OSM tiles
+- **No user accounts:** No registration, no login, no server-side storage
+- **Location stays local:** GPS coordinates are used only for distance calculation and never transmitted
+- **HTTPS enforced:** Android network security config restricts to TLS; PWA CSP headers restrict connections
+- **ZIP bomb protection:** 10MB limit on uncompressed GeoJSON (Android parser)
+- **CSP headers (PWA):** Restrictive Content-Security-Policy blocks external scripts; `img-src` allows only OSM tile servers
+- **Input validation:** Shelter data validated at parse time (bounds check, required fields, non-negative capacity)
+- **Minimal permissions:** Location only; no contacts, camera, storage (except legacy tile cache on Android ≤9)
+
+---
+
+## Internationalization
+
+Both platforms support three languages:
+
+| Language | Android | PWA |
+|---------------------|----------------------|----------------|
+| English (default) | `values/strings.xml` | `i18n/en.ts` |
+| Norwegian Bokmål | `values-nb/strings.xml` | `i18n/nb.ts` |
+| Norwegian Nynorsk | `values-nn/strings.xml` | `i18n/nn.ts` |
+
+**Android:** Uses Android's built-in locale resolution. Per-app language selection supported on Android 13+ via `locales_config.xml`.
+
+**PWA:** Detects browser language (`navigator.language`). String substitution supports `%d` (number) and `%s` (string) placeholders. Falls back to English for unsupported locales.
diff --git a/CLAUDE.md b/CLAUDE.md
index a5b4d4d..df4f859 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -58,10 +58,26 @@ no.naiv.tilfluktsrom/
- **fdroid**: AOSP-only, no Google dependencies
## Distribution
-- **Forgejo** (primary): `kode.naiv.no/olemd/tilfluktsrom` — releases with both APK variants
+- **Forgejo** (primary): `kode.naiv.no/olemd/tilfluktsrom` — releases with both APK variants + PWA tarball
- **GitHub** (mirror): `github.com/olemd/tilfluktsrom` — automatically mirrored from Forgejo, do not push manually
- **F-Droid**: Metadata maintained in a separate fdroiddata repo (GitLab fork). F-Droid builds from source using the `fdroid` variant and signs with the F-Droid key.
+## Release Process
+When creating a new release:
+1. Bump `versionCode` and `versionName` in `app/build.gradle.kts`
+2. Update User-Agent string in `ShelterRepository.kt` to match new version
+3. Build Android APKs: `./gradlew assembleStandardRelease assembleFdroidRelease`
+4. Build PWA: `cd pwa && bun scripts/fetch-shelters.ts && bun run build && tar -czf /tmp/tilfluktsrom-vX.Y.Z-pwa.tar.gz -C dist .`
+5. Commit, push, then create Forgejo release with all three artifacts:
+ ```bash
+ fj release create --create-tag vX.Y.Z --branch main \
+ --attach "/tmp/tilfluktsrom-vX.Y.Z-standard.apk:tilfluktsrom-vX.Y.Z-standard.apk" \
+ --attach "/tmp/tilfluktsrom-vX.Y.Z-fdroid.apk:tilfluktsrom-vX.Y.Z-fdroid.apk" \
+ --body "release notes" "vX.Y.Z"
+ fj release asset create vX.Y.Z /tmp/tilfluktsrom-vX.Y.Z-pwa.tar.gz tilfluktsrom-vX.Y.Z-pwa.tar.gz
+ ```
+6. Install on phone: `adb install -r app/build/outputs/apk/standard/release/app-standard-release.apk`
+
## Screenshots
Use the Android emulator and Maestro to take screenshots for the README and fastlane metadata.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c46b0b4..5992c73 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -14,8 +14,13 @@ android {
applicationId = "no.naiv.tilfluktsrom"
minSdk = 26
targetSdk = 35
- versionCode = 11
- versionName = "1.7.0"
+ versionCode = 12
+ versionName = "1.8.0"
+
+ // Deep link domain — single source of truth for manifest + Kotlin code
+ val deepLinkDomain = "tilfluktsrom.naiv.no"
+ buildConfigField("String", "DEEP_LINK_DOMAIN", "\"$deepLinkDomain\"")
+ manifestPlaceholders["deepLinkHost"] = deepLinkDomain
}
dependenciesInfo {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 58bf2ed..e522483 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,13 +29,14 @@
-
+
+ android:scheme="https"
+ android:host="${deepLinkHost}"
+ android:pathPrefix="/shelter/" />
diff --git a/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt b/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt
index e1fd8e0..5f80847 100644
--- a/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt
+++ b/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt
@@ -143,12 +143,14 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
}
/**
- * Handle tilfluktsrom://shelter/{lokalId} deep link.
+ * Handle https://{domain}/shelter/{lokalId} deep link.
* If shelters are already loaded, select immediately; otherwise store as pending.
*/
private fun handleDeepLinkIntent(intent: Intent?) {
val uri = intent?.data ?: return
- if (uri.scheme != "tilfluktsrom" || uri.host != "shelter") return
+ if (uri.scheme != "https" ||
+ uri.host != BuildConfig.DEEP_LINK_DOMAIN ||
+ uri.path?.startsWith("/shelter/") != true) return
val lokalId = uri.lastPathSegment ?: return
// Clear intent data so config changes don't re-trigger
@@ -669,8 +671,8 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
/**
* Share the currently selected shelter via ACTION_SEND.
- * Includes address, capacity, geo: URI (for non-app recipients),
- * and a tilfluktsrom:// deep link (for app users).
+ * Includes address, capacity, geo: URI, and an HTTPS deep link
+ * that opens the app (if installed) or the PWA (in browser).
*/
private fun shareShelter() {
val selected = selectedShelter
@@ -680,12 +682,14 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
}
val shelter = selected.shelter
+ val deepLink = "https://${BuildConfig.DEEP_LINK_DOMAIN}/shelter/${shelter.lokalId}"
val body = getString(
R.string.share_body,
shelter.adresse,
shelter.plasser,
shelter.latitude,
- shelter.longitude
+ shelter.longitude,
+ deepLink
)
val shareIntent = Intent(Intent.ACTION_SEND).apply {
diff --git a/app/src/main/java/no/naiv/tilfluktsrom/data/ShelterRepository.kt b/app/src/main/java/no/naiv/tilfluktsrom/data/ShelterRepository.kt
index b5afd3b..048a93d 100644
--- a/app/src/main/java/no/naiv/tilfluktsrom/data/ShelterRepository.kt
+++ b/app/src/main/java/no/naiv/tilfluktsrom/data/ShelterRepository.kt
@@ -46,7 +46,7 @@ class ShelterRepository(private val context: Context) {
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(Interceptor { chain ->
chain.proceed(chain.request().newBuilder()
- .header("User-Agent", "Tilfluktsrom/1.7.0")
+ .header("User-Agent", "Tilfluktsrom/1.8.0")
.build())
})
.build()
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 74c464b..8f0f2d3 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -60,7 +60,7 @@
Tilfluktsrom
- Tilfluktsrom: %1$s\n%2$d plasser\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f
+ Tilfluktsrom: %1$s\n%2$d plasser\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f\n%5$s
Ingen tilfluktsrom valgt
diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml
index f3077e5..088398a 100644
--- a/app/src/main/res/values-nn/strings.xml
+++ b/app/src/main/res/values-nn/strings.xml
@@ -60,7 +60,7 @@
Tilfluktsrom
- Tilfluktsrom: %1$s\n%2$d plassar\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f
+ Tilfluktsrom: %1$s\n%2$d plassar\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f\n%5$s
Ingen tilfluktsrom valt
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 38d09b2..34afc98 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -60,7 +60,7 @@
Emergency shelter
- Shelter: %1$s\n%2$d places\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f
+ Shelter: %1$s\n%2$d places\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f\n%5$s
No shelter selected
diff --git a/pwa/.gitignore b/pwa/.gitignore
index b947077..35ca4c8 100644
--- a/pwa/.gitignore
+++ b/pwa/.gitignore
@@ -1,2 +1,3 @@
node_modules/
dist/
+public/data/shelters.json
diff --git a/pwa/index.html b/pwa/index.html
index 876bde3..7440b65 100644
--- a/pwa/index.html
+++ b/pwa/index.html
@@ -6,14 +6,11 @@
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org; connect-src 'self' https://*.tile.openstreetmap.org; font-src 'self'; worker-src 'self'" />
Tilfluktsrom
-
-
-
-
+
+
+
@@ -66,6 +63,6 @@
-
+