From 3c49dbdcde06dde6532a5546b67ee7150b80dcbd Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Sun, 8 Mar 2026 22:34:33 +0100 Subject: [PATCH] Legg til automatisk widgetoppdatering og de-Google-retningslinjer (v1.3.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Widget oppdaterer seg sjølv via WorkManager kvar 15. min i bakgrunnen, og i sanntid når appen er open og mottek GPS-oppdateringar. Oppdaterer CLAUDE.md med de-Google-kompatibilitetsprinsipp. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 18 ++++++- app/build.gradle.kts | 3 ++ .../java/no/naiv/tilfluktsrom/MainActivity.kt | 2 + .../widget/ShelterWidgetProvider.kt | 25 +++++++++- .../tilfluktsrom/widget/WidgetUpdateWorker.kt | 48 +++++++++++++++++++ version.properties | 4 +- 6 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/no/naiv/tilfluktsrom/widget/WidgetUpdateWorker.kt diff --git a/CLAUDE.md b/CLAUDE.md index 1775270..261261a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,13 +3,28 @@ ## Project Overview Android app (Kotlin) that helps find the nearest public shelter (tilfluktsrom) in Norway during emergencies. Offline-first design: must work without internet after initial data cache. +## Design Principles + +### De-Google Compatibility +The app must work on devices without Google Play Services (e.g. LineageOS, GrapheneOS, /e/OS). Every feature that uses a Google-specific API must have a fallback that works without it. Use Google Play Services when available for better accuracy/performance, but never as a hard dependency. + +**Pattern**: Check for Play Services at runtime, fall back to AOSP/standard APIs. +- **Location**: Prefer FusedLocationProviderClient (Play Services) → fall back to LocationManager (AOSP) +- **Maps**: OSMDroid (no Google dependency) +- **Database**: Room/SQLite (no Google dependency) +- **Background work**: WorkManager (works without Play Services via built-in scheduler) + +### Offline-First +This is an emergency app. Assume internet and infrastructure may be degraded or unavailable. All core functionality (finding nearest shelter, compass navigation, sharing location) must work offline after initial data cache. Avoid solutions that depend on external servers being reachable. + ## Architecture - **Language**: Kotlin, targeting Android API 26+ (Android 8.0+) - **Build**: Gradle 8.7, AGP 8.5.2, KSP for Room annotation processing - **Maps**: OSMDroid (offline-capable OpenStreetMap) - **Database**: Room (SQLite) for shelter data cache - **HTTP**: OkHttp for data downloads -- **Location**: Google Play Services Fused Location Provider +- **Location**: FusedLocationProviderClient (Play Services) with LocationManager fallback +- **Background**: WorkManager for periodic widget updates - **UI**: Traditional Views with ViewBinding ## Key Data Flow @@ -29,6 +44,7 @@ no.naiv.tilfluktsrom/ ├── data/ # Room entities, DAO, repository, GeoJSON parser, map cache ├── location/ # GPS location provider, nearest shelter finder ├── ui/ # Custom views (DirectionArrowView), adapters +├── widget/ # Home screen widget, WorkManager periodic updater └── util/ # Coordinate conversion (UTM→WGS84), distance calculations ``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a646779..d905c26 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -91,6 +91,9 @@ dependencies { // Google Play Services Location (precise GPS) implementation("com.google.android.gms:play-services-location:21.3.0") + // WorkManager (periodic widget updates) + implementation("androidx.work:work-runtime-ktx:2.9.1") + // Coroutines implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") } diff --git a/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt b/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt index 9e7a2da..bcc0cd0 100644 --- a/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt +++ b/app/src/main/java/no/naiv/tilfluktsrom/MainActivity.kt @@ -39,6 +39,7 @@ import no.naiv.tilfluktsrom.location.ShelterFinder import no.naiv.tilfluktsrom.location.ShelterWithDistance import no.naiv.tilfluktsrom.ui.ShelterListAdapter import no.naiv.tilfluktsrom.util.DistanceUtils +import no.naiv.tilfluktsrom.widget.ShelterWidgetProvider import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController import org.osmdroid.views.overlay.Marker @@ -414,6 +415,7 @@ class MainActivity : AppCompatActivity(), SensorEventListener { } updateSelectedShelterUI() + ShelterWidgetProvider.requestUpdate(this) } /** diff --git a/app/src/main/java/no/naiv/tilfluktsrom/widget/ShelterWidgetProvider.kt b/app/src/main/java/no/naiv/tilfluktsrom/widget/ShelterWidgetProvider.kt index ef16094..62ba2d6 100644 --- a/app/src/main/java/no/naiv/tilfluktsrom/widget/ShelterWidgetProvider.kt +++ b/app/src/main/java/no/naiv/tilfluktsrom/widget/ShelterWidgetProvider.kt @@ -23,8 +23,11 @@ import no.naiv.tilfluktsrom.util.DistanceUtils /** * Home screen widget showing the nearest shelter with distance. * - * Update strategy: no automatic periodic updates (updatePeriodMillis=0). - * Updates only when the user taps the refresh button, which sends ACTION_REFRESH. + * Update strategy: + * - Background: WorkManager runs every 15 min while widget exists + * - Live: MainActivity sends ACTION_REFRESH on each GPS location update + * - Manual: user taps the refresh button on the widget + * * Tapping the widget body opens MainActivity. * * Uses LocationManager directly (not the hybrid LocationProvider) because @@ -36,6 +39,24 @@ class ShelterWidgetProvider : AppWidgetProvider() { companion object { private const val TAG = "ShelterWidget" const val ACTION_REFRESH = "no.naiv.tilfluktsrom.widget.REFRESH" + + /** Trigger a widget refresh from anywhere (e.g. MainActivity on location update). */ + fun requestUpdate(context: Context) { + val intent = Intent(context, ShelterWidgetProvider::class.java).apply { + action = ACTION_REFRESH + } + context.sendBroadcast(intent) + } + } + + override fun onEnabled(context: Context) { + super.onEnabled(context) + WidgetUpdateWorker.schedule(context) + } + + override fun onDisabled(context: Context) { + super.onDisabled(context) + WidgetUpdateWorker.cancel(context) } override fun onUpdate( diff --git a/app/src/main/java/no/naiv/tilfluktsrom/widget/WidgetUpdateWorker.kt b/app/src/main/java/no/naiv/tilfluktsrom/widget/WidgetUpdateWorker.kt new file mode 100644 index 0000000..34529fd --- /dev/null +++ b/app/src/main/java/no/naiv/tilfluktsrom/widget/WidgetUpdateWorker.kt @@ -0,0 +1,48 @@ +package no.naiv.tilfluktsrom.widget + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import java.util.concurrent.TimeUnit + +/** + * Periodic background worker that refreshes the home screen widget. + * + * Scheduled every 15 minutes (WorkManager's minimum interval). + * Simply triggers the widget's existing update logic via broadcast. + */ +class WidgetUpdateWorker( + context: Context, + params: WorkerParameters +) : CoroutineWorker(context, params) { + + override suspend fun doWork(): Result { + ShelterWidgetProvider.requestUpdate(applicationContext) + return Result.success() + } + + companion object { + private const val WORK_NAME = "widget_update" + + /** Schedule periodic widget updates. Safe to call multiple times. */ + fun schedule(context: Context) { + val request = PeriodicWorkRequestBuilder( + 15, TimeUnit.MINUTES + ).build() + + WorkManager.getInstance(context).enqueueUniquePeriodicWork( + WORK_NAME, + ExistingPeriodicWorkPolicy.KEEP, + request + ) + } + + /** Cancel periodic updates (e.g. when all widgets are removed). */ + fun cancel(context: Context) { + WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME) + } + } +} diff --git a/version.properties b/version.properties index 5320a1d..bb37353 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ versionMajor=1 versionMinor=3 -versionPatch=0 -versionCode=4 +versionPatch=1 +versionCode=5