Actionable banner når posisjon ikke er tilgjengelig
Tidligere sto statusteksten igjen på «Venter på GPS…» uansett om årsaken var manglende tillatelse, avslåtte stedstjenester eller bare at GPS-en ikke hadde fått fix ennå. For en nødsituasjonsapp er det en reell feilmodus: brukeren får ingen hint om hva som kan gjøres for å finne nærmeste tilfluktsrom. Ny noLocationBanner plasseres øverst i innholdsområdet (rett under statuslinjen) slik at den ikke kolliderer med de flytende handlingsknappene over bunnarket, og viser én av tre tilstander: 1. Tillatelse avslått eller ikke gitt — «Posisjonstilgang nødvendig for å finne nærmeste tilfluktsrom. Du kan også trykke på et merke i kartet.» + «Gi tilgang» som åpner ACTION_APPLICATION_DETAILS_SETTINGS. 2. Tillatelse gitt, men stedstjenester slått av — «Stedstjenester er slått av. Aktiver dem eller velg et tilfluktsrom fra kartet.» + «Aktiver» som åpner ACTION_LOCATION_SOURCE_SETTINGS. 3. Begge OK — banner er skjult og den eksisterende «Venter på GPS…»-teksten gjelder. Helperen updateLocationStatusBanner() kalles fra loadData(), permission-result-kallbacket og onResume(), slik at banneret oppdaterer seg både ved appstart, umiddelbart etter avslag, og når brukeren kommer tilbake fra systeminnstillingene. AlertDialog-en ved permanent avslag er fjernet til fordel for det ikke-modale banneret, som lar brukeren fortsatt pan-ne kartet og velge tilfluktsrom manuelt. Toasten på mykt avslag er beholdt som en kort bekreftelse. API-nivå-fallbacket bruker LocationManager.isLocationEnabled på API 28+, isProviderEnabled for GPS/Network på API 26–27. Verifisert på emulator i alle fire tilstander (avslag → App Settings, tjeneste-av → Posisjonsinnstillinger, gjenopprettet). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ce0827e9f
commit
f0c4a1f5b4
5 changed files with 97 additions and 22 deletions
|
|
@ -10,9 +10,11 @@ import android.hardware.SensorEvent
|
||||||
import android.hardware.SensorEventListener
|
import android.hardware.SensorEventListener
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.location.LocationManager
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
@ -94,28 +96,10 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
||||||
|
|
||||||
if (fineGranted || coarseGranted) {
|
if (fineGranted || coarseGranted) {
|
||||||
startLocationUpdates()
|
startLocationUpdates()
|
||||||
} else {
|
|
||||||
// Check if user permanently denied (don't show rationale = permanently denied)
|
|
||||||
val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
|
|
||||||
this, Manifest.permission.ACCESS_FINE_LOCATION
|
|
||||||
)
|
|
||||||
if (!shouldShowRationale) {
|
|
||||||
// Permission permanently denied — guide user to settings
|
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setTitle(R.string.permission_location_title)
|
|
||||||
.setMessage(R.string.permission_denied)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
||||||
data = Uri.fromParts("package", packageName, null)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
updateLocationStatusBanner()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -259,6 +243,8 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
|
updateLocationStatusBanner()
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
var hasData = repository.hasCachedData()
|
var hasData = repository.hasCachedData()
|
||||||
|
|
@ -378,6 +364,44 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateLocationStatusBanner() {
|
||||||
|
val banner = binding.noLocationBanner
|
||||||
|
val text = binding.locationBannerText
|
||||||
|
val action = binding.locationBannerAction
|
||||||
|
|
||||||
|
when {
|
||||||
|
!locationProvider.hasLocationPermission() -> {
|
||||||
|
text.setText(R.string.status_location_permission_needed)
|
||||||
|
action.setText(R.string.action_grant_permission)
|
||||||
|
action.setOnClickListener {
|
||||||
|
startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = Uri.fromParts("package", packageName, null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
banner.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
!isLocationServicesEnabled() -> {
|
||||||
|
text.setText(R.string.status_location_services_off)
|
||||||
|
action.setText(R.string.action_location_settings)
|
||||||
|
action.setOnClickListener {
|
||||||
|
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
|
||||||
|
}
|
||||||
|
banner.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else -> banner.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isLocationServicesEnabled(): Boolean {
|
||||||
|
val lm = getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return false
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
lm.isLocationEnabled
|
||||||
|
} else {
|
||||||
|
lm.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
|
||||||
|
lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startLocationUpdates() {
|
private fun startLocationUpdates() {
|
||||||
// Use repeatOnLifecycle(STARTED) so GPS stops when Activity is paused
|
// Use repeatOnLifecycle(STARTED) so GPS stops when Activity is paused
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -757,6 +781,10 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
||||||
binding.mapView.onResume()
|
binding.mapView.onResume()
|
||||||
myLocationOverlay?.enableMyLocation()
|
myLocationOverlay?.enableMyLocation()
|
||||||
|
|
||||||
|
// Re-check permission + location-services state so the banner updates
|
||||||
|
// when the user returns from Settings.
|
||||||
|
updateLocationStatusBanner()
|
||||||
|
|
||||||
val sm = sensorManager ?: return
|
val sm = sensorManager ?: return
|
||||||
|
|
||||||
// Try rotation vector first (best compass source)
|
// Try rotation vector first (best compass source)
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:contentDescription="@string/a11y_map"
|
android:contentDescription="@string/a11y_map"
|
||||||
app:layout_constraintTop_toBottomOf="@id/statusBar"
|
app:layout_constraintTop_toBottomOf="@id/noLocationBanner"
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottomSheet" />
|
app:layout_constraintBottom_toTopOf="@id/bottomSheet" />
|
||||||
|
|
||||||
<!-- Direction arrow overlay (shown when toggled) -->
|
<!-- Direction arrow overlay (shown when toggled) -->
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
android:background="@color/compass_bg"
|
android:background="@color/compass_bg"
|
||||||
android:contentDescription="@string/a11y_compass"
|
android:contentDescription="@string/a11y_compass"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintTop_toBottomOf="@id/statusBar"
|
app:layout_constraintTop_toBottomOf="@id/noLocationBanner"
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottomSheet">
|
app:layout_constraintBottom_toTopOf="@id/bottomSheet">
|
||||||
|
|
||||||
<no.naiv.tilfluktsrom.ui.DirectionArrowView
|
<no.naiv.tilfluktsrom.ui.DirectionArrowView
|
||||||
|
|
@ -149,6 +149,41 @@
|
||||||
app:backgroundTint="@color/shelter_primary"
|
app:backgroundTint="@color/shelter_primary"
|
||||||
app:tint="@color/white" />
|
app:tint="@color/white" />
|
||||||
|
|
||||||
|
<!-- Warning banner: location unavailable (permission denied or services off).
|
||||||
|
Placed at the top of the content area (below the status bar) so it never
|
||||||
|
collides with the floating action buttons anchored above the bottom sheet. -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/noLocationBanner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/warning_bg"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="12dp"
|
||||||
|
android:paddingVertical="6dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/statusBar">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/locationBannerText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/warning_text"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="@string/status_location_permission_needed" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/locationBannerAction"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:textColor="@color/warning_text"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
tools:text="@string/action_grant_permission" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Warning banner: no offline map cache -->
|
<!-- Warning banner: no offline map cache -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/noCacheBanner"
|
android:id="@+id/noCacheBanner"
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,11 @@
|
||||||
<string name="action_cache_now">Lagre nå</string>
|
<string name="action_cache_now">Lagre nå</string>
|
||||||
<string name="action_reset_navigation">Tilbakestill navigasjonsvisning</string>
|
<string name="action_reset_navigation">Tilbakestill navigasjonsvisning</string>
|
||||||
<string name="action_share">Del tilfluktsrom</string>
|
<string name="action_share">Del tilfluktsrom</string>
|
||||||
|
<string name="action_grant_permission">Gi tilgang</string>
|
||||||
|
<string name="action_location_settings">Aktiver</string>
|
||||||
<string name="warning_no_map_cache">Ingen frakoblet kart lagret. Kartet krever internett.</string>
|
<string name="warning_no_map_cache">Ingen frakoblet kart lagret. Kartet krever internett.</string>
|
||||||
|
<string name="status_location_permission_needed">Posisjonstilgang nødvendig for å finne nærmeste tilfluktsrom. Du kan også trykke på et merke i kartet.</string>
|
||||||
|
<string name="status_location_services_off">Stedstjenester er slått av. Aktiver dem eller velg et tilfluktsrom fra kartet.</string>
|
||||||
|
|
||||||
<!-- Tillatelser -->
|
<!-- Tillatelser -->
|
||||||
<string name="permission_location_title">Posisjonstillatelse kreves</string>
|
<string name="permission_location_title">Posisjonstillatelse kreves</string>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,11 @@
|
||||||
<string name="action_cache_now">Lagre no</string>
|
<string name="action_cache_now">Lagre no</string>
|
||||||
<string name="action_reset_navigation">Tilbakestill navigasjonsvising</string>
|
<string name="action_reset_navigation">Tilbakestill navigasjonsvising</string>
|
||||||
<string name="action_share">Del tilfluktsrom</string>
|
<string name="action_share">Del tilfluktsrom</string>
|
||||||
|
<string name="action_grant_permission">Gje tilgang</string>
|
||||||
|
<string name="action_location_settings">Aktiver</string>
|
||||||
<string name="warning_no_map_cache">Ingen fråkopla kart lagra. Kartet krev internett.</string>
|
<string name="warning_no_map_cache">Ingen fråkopla kart lagra. Kartet krev internett.</string>
|
||||||
|
<string name="status_location_permission_needed">Posisjonstilgang trengst for å finne næraste tilfluktsrom. Du kan òg trykke på eit merke i kartet.</string>
|
||||||
|
<string name="status_location_services_off">Stedstenester er slått av. Aktiver dei eller vel eit tilfluktsrom frå kartet.</string>
|
||||||
|
|
||||||
<!-- Løyve -->
|
<!-- Løyve -->
|
||||||
<string name="permission_location_title">Posisjonsløyve krevst</string>
|
<string name="permission_location_title">Posisjonsløyve krevst</string>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,11 @@
|
||||||
<string name="action_cache_now">Cache now</string>
|
<string name="action_cache_now">Cache now</string>
|
||||||
<string name="action_reset_navigation">Reset navigation view</string>
|
<string name="action_reset_navigation">Reset navigation view</string>
|
||||||
<string name="action_share">Share shelter</string>
|
<string name="action_share">Share shelter</string>
|
||||||
|
<string name="action_grant_permission">Grant access</string>
|
||||||
|
<string name="action_location_settings">Enable</string>
|
||||||
<string name="warning_no_map_cache">No offline map cached. Map requires internet.</string>
|
<string name="warning_no_map_cache">No offline map cached. Map requires internet.</string>
|
||||||
|
<string name="status_location_permission_needed">Location access needed to find the nearest shelter. You can also tap a marker on the map.</string>
|
||||||
|
<string name="status_location_services_off">Location services are off. Enable them or pick a shelter from the map.</string>
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<string name="permission_location_title">Location permission required</string>
|
<string name="permission_location_title">Location permission required</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue