Åpne valgt tilfluktsrom i ekstern kartapp for gangvei

Ny knapp (ic_directions) ved siden av del-knappen i bunnarket. Fyrer
ACTION_VIEW med geo:lat,lon?q=lat,lon(Tilfluktsrom – adresse), som
håndteres av enhver installert kartapp — inkludert offline-kapable
OsmAnd/Organic Maps, viktig ved degradert nett. Ingen vendor-URL-er.

- Koordinater formateres med Locale.US så norsk systemspråk ikke gir
  desimalkomma i URI-en
- Fallback: ActivityNotFoundException → koordinater kopieres til
  klippebordet + Toast (navigate_no_map_app). startActivity+catch brukes
  framfor resolveActivity() for å slippe <queries> (pakkesynlighet API 30+)
- Strenger action_navigate / navigate_no_map_app i en/nb/nn
- 48dp berøringsflate og contentDescription som de andre ikonknappene

Verifisert på emulator (Pixel 6 API 35): trykk → START ACTION_VIEW
geo: → Google Maps åpnes.

Forgejo: #2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrDGDmN2WAXvNPS85rWJGK
This commit is contained in:
Ole-Morten Duesund 2026-08-17 13:00:23 +02:00
commit daf8b62d39
7 changed files with 84 additions and 2 deletions

View file

@ -1,6 +1,9 @@
package no.naiv.tilfluktsrom
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.view.HapticFeedbackConstants
@ -22,6 +25,7 @@ import android.provider.Settings
import android.util.Log
import android.view.View
import android.widget.Toast
import java.util.Locale
import java.util.concurrent.TimeUnit
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
@ -271,6 +275,11 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
shareShelter()
}
binding.navigateButton.setOnClickListener {
it.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
openInMapApp()
}
binding.cacheRetryButton.setOnClickListener {
it.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
val loc = currentLocation
@ -816,6 +825,51 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
startActivity(Intent.createChooser(shareIntent, getString(R.string.action_share)))
}
/**
* Open the selected shelter in an external map app for walking directions.
*
* Uses a plain geo: URI (ACTION_VIEW), never a vendor-specific URL, so any
* installed map app can claim it including offline-capable ones such as
* OsmAnd and Organic Maps, which matters when the network is degraded.
* geo:lat,lon?q=lat,lon(label) is the de-facto form all major apps parse.
*
* Coordinates are formatted with Locale.US: a Norwegian default locale
* would otherwise emit decimal commas and produce an unparsable URI.
*
* If no app handles geo: (rare, but possible on de-Googled devices with
* no map app installed), the coordinates are copied to the clipboard and
* a toast says so, so the user can still paste them somewhere useful.
* startActivity + ActivityNotFoundException is used instead of
* resolveActivity() because the latter is subject to package-visibility
* filtering on API 30+ and would need a <queries> manifest entry.
*/
private fun openInMapApp() {
val selected = selectedShelter
if (selected == null) {
Toast.makeText(this, R.string.share_no_shelter, Toast.LENGTH_SHORT).show()
return
}
val shelter = selected.shelter
val lat = String.format(Locale.US, "%.6f", shelter.latitude)
val lon = String.format(Locale.US, "%.6f", shelter.longitude)
val label = Uri.encode("${getString(R.string.app_name)} \u2013 ${shelter.adresse}")
val geoUri = Uri.parse("geo:$lat,$lon?q=$lat,$lon($label)")
try {
startActivity(Intent(Intent.ACTION_VIEW, geoUri))
} catch (e: ActivityNotFoundException) {
val coords = "$lat, $lon"
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(getString(R.string.app_name), coords))
Toast.makeText(
this,
getString(R.string.navigate_no_map_app, coords),
Toast.LENGTH_LONG
).show()
}
}
/** Update the freshness indicator below the status bar with color-coded age. */
private fun updateFreshnessIndicator() {
val lastUpdate = repository.getLastUpdateMs()

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Material Design "directions" icon (Apache 2.0) — opens the selected
shelter in an external map app for walking directions. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5L14,12h-4v3L8,15v-4c0,-0.55 0.45,-1 1,-1h5L14,7.5l3.5,3.5 -3.5,3.5z" />
</vector>

View file

@ -265,6 +265,16 @@
tools:text="1.2 km - 400 plasser - Rom 776" />
</LinearLayout>
<ImageButton
android:id="@+id/navigateButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/action_navigate"
android:src="@drawable/ic_directions"
app:tint="@color/text_secondary" />
<ImageButton
android:id="@+id/shareButton"
android:layout_width="48dp"

View file

@ -29,6 +29,7 @@
<string name="action_cache_ok">Lagre kart</string>
<string name="action_cache_now">Lagre nå</string>
<string name="action_reset_navigation">Tilbakestill navigasjonsvisning</string>
<string name="action_navigate">Åpne i kartapp</string>
<string name="action_share">Del tilfluktsrom</string>
<string name="action_grant_permission">Gi tilgang</string>
<string name="action_location_settings">Aktiver</string>
@ -58,6 +59,7 @@
<string name="share_subject">Tilfluktsrom</string>
<string name="share_body">Tilfluktsrom: %1$s\n%2$d plasser\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f\n%5$s</string>
<string name="share_no_shelter">Ingen tilfluktsrom valgt</string>
<string name="navigate_no_map_app">Fant ingen kartapp koordinater kopiert: %1$s</string>
<!-- Tilgjengelighet -->
<string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string>

View file

@ -29,6 +29,7 @@
<string name="action_cache_ok">Lagre kart</string>
<string name="action_cache_now">Lagre no</string>
<string name="action_reset_navigation">Tilbakestill navigasjonsvising</string>
<string name="action_navigate">Opne i kartapp</string>
<string name="action_share">Del tilfluktsrom</string>
<string name="action_grant_permission">Gje tilgang</string>
<string name="action_location_settings">Aktiver</string>
@ -58,6 +59,7 @@
<string name="share_subject">Tilfluktsrom</string>
<string name="share_body">Tilfluktsrom: %1$s\n%2$d plassar\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f\n%5$s</string>
<string name="share_no_shelter">Ingen tilfluktsrom valt</string>
<string name="navigate_no_map_app">Fann ingen kartapp koordinatar kopierte: %1$s</string>
<!-- Tilgjenge -->
<string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string>

View file

@ -29,6 +29,7 @@
<string name="action_cache_ok">Cache map</string>
<string name="action_cache_now">Cache now</string>
<string name="action_reset_navigation">Reset navigation view</string>
<string name="action_navigate">Open in map app</string>
<string name="action_share">Share shelter</string>
<string name="action_grant_permission">Grant access</string>
<string name="action_location_settings">Enable</string>
@ -58,6 +59,7 @@
<string name="share_subject">Emergency shelter</string>
<string name="share_body">Shelter: %1$s\n%2$d places\n%3$.6f, %4$.6f\ngeo:%3$.6f,%4$.6f\n%5$s</string>
<string name="share_no_shelter">No shelter selected</string>
<string name="navigate_no_map_app">No map app found coordinates copied: %1$s</string>
<!-- Accessibility -->
<string name="direction_arrow_description">Direction to shelter, %s away</string>