Legg til sivilforsvarsinformasjon med offisielle DSB-retningslinjer (v1.4.0)

Ny info-knapp (ℹ) i statuslinja opnar ein dialog med sivilforsvars-
instruksjonar: viktig melding-signal, flyalarm, søk dekning, lytt til
NRK P1 på DAB-radio, og faren over-signal. Alt innhald er verifisert
mot offisielle DSB-retningslinjer og fungerer utan nett. Støttar alle
tre språk (en, nb, nn).

Fixes #4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-09 09:32:55 +01:00
commit fca34f893b
10 changed files with 240 additions and 3 deletions

View file

@ -37,6 +37,7 @@ import no.naiv.tilfluktsrom.databinding.ActivityMainBinding
import no.naiv.tilfluktsrom.location.LocationProvider import no.naiv.tilfluktsrom.location.LocationProvider
import no.naiv.tilfluktsrom.location.ShelterFinder import no.naiv.tilfluktsrom.location.ShelterFinder
import no.naiv.tilfluktsrom.location.ShelterWithDistance import no.naiv.tilfluktsrom.location.ShelterWithDistance
import no.naiv.tilfluktsrom.ui.CivilDefenseInfoDialog
import no.naiv.tilfluktsrom.ui.ShelterListAdapter import no.naiv.tilfluktsrom.ui.ShelterListAdapter
import no.naiv.tilfluktsrom.util.DistanceUtils import no.naiv.tilfluktsrom.util.DistanceUtils
import no.naiv.tilfluktsrom.widget.ShelterWidgetProvider import no.naiv.tilfluktsrom.widget.ShelterWidgetProvider
@ -220,6 +221,10 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
selectedShelter?.let { highlightShelterOnMap(it) } selectedShelter?.let { highlightShelterOnMap(it) }
} }
binding.infoButton.setOnClickListener {
CivilDefenseInfoDialog().show(supportFragmentManager, CivilDefenseInfoDialog.TAG)
}
binding.refreshButton.setOnClickListener { binding.refreshButton.setOnClickListener {
forceRefresh() forceRefresh()
} }

View file

@ -0,0 +1,44 @@
package no.naiv.tilfluktsrom.ui
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import no.naiv.tilfluktsrom.R
/**
* Full-screen dialog showing civil defense instructions.
* Static content all text comes from string resources for offline use and i18n.
*/
class CivilDefenseInfoDialog : DialogFragment() {
companion object {
const val TAG = "CivilDefenseInfoDialog"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.Theme_Tilfluktsrom_Dialog)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.dialog_civil_defense, container, false)
}
override fun onStart() {
super.onStart()
dialog?.window?.apply {
setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
}
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Material Design info icon -->
<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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2V7h2v2z" />
</vector>

View file

@ -35,6 +35,15 @@
android:textSize="12sp" android:textSize="12sp"
tools:text="@string/status_ready" /> tools:text="@string/status_ready" />
<ImageButton
android:id="@+id/infoButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_civil_defense_info"
android:src="@drawable/ic_info"
app:tint="@color/status_text" />
<ImageButton <ImageButton
android:id="@+id/refreshButton" android:id="@+id/refreshButton"
android:layout_width="36dp" android:layout_width="36dp"

View file

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/status_bar_bg"
android:padding="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/civil_defense_title"
android:textColor="@color/shelter_primary"
android:textSize="20sp"
android:textStyle="bold" />
<!-- Step 1: The alarm signal -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/civil_defense_step1_title"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="@string/civil_defense_step1_body"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Step 2: Go indoors -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/civil_defense_step2_title"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="@string/civil_defense_step2_body"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Step 3: Find nearest shelter -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/civil_defense_step3_title"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="@string/civil_defense_step3_body"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Step 4: Listen to NRK P1 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/civil_defense_step4_title"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="@string/civil_defense_step4_body"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Step 5: All clear signal -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/civil_defense_step5_title"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/civil_defense_step5_body"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Source attribution -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/civil_defense_source"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:textStyle="italic" />
</LinearLayout>
</ScrollView>

View file

@ -64,4 +64,19 @@
<!-- Tilgjengelighet --> <!-- Tilgjengelighet -->
<string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string> <string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string>
<!-- Sivilforsvar -->
<string name="action_civil_defense_info">Sivilforsvarsinformasjon</string>
<string name="civil_defense_title">Hva du skal gjøre når alarmen går</string>
<string name="civil_defense_step1_title">1. Viktig melding-signalet</string>
<string name="civil_defense_step1_body">Tre serier med korte støt med ett minutts stillhet mellom hver serie. Dette betyr: søk informasjon umiddelbart. Slå på DAB-radio, TV, eller sjekk offisielle kilder på nett.</string>
<string name="civil_defense_step2_title">2. Flyalarm</string>
<string name="civil_defense_step2_body">Korte støt som varer omtrent ett minutt. Dette betyr umiddelbar fare for angrep — søk dekning nå. Gå til nærmeste tilfluktsrom, kjeller eller innerrom umiddelbart.</string>
<string name="civil_defense_step3_title">3. Gå innendørs og finn dekning</string>
<string name="civil_defense_step3_body">Kom deg innendørs. Lukk alle vinduer, dører og ventilasjonsåpninger. Bruk denne appen for å finne nærmeste offentlige tilfluktsrom. Kompasset og kartet fungerer uten internett. Hvis det ikke er noe tilfluktsrom i nærheten, gå til en kjeller eller et innerrom bort fra vinduer.</string>
<string name="civil_defense_step4_title">4. Lytt til NRK på DAB-radio</string>
<string name="civil_defense_step4_body">Lytt til NRK P1 på DAB-radio for offisielle oppdateringer og instruksjoner fra myndighetene. DAB-radio fungerer selv når mobilnettet og internett er nede. Ha en batteridrevet eller sveivbar DAB-radio som del av beredskapsutstyret ditt.</string>
<string name="civil_defense_step5_title">5. Faren over</string>
<string name="civil_defense_step5_body">Én sammenhengende tone på omtrent 30 sekunder. Faren eller angrepet er over. Fortsett å følge instruksjoner fra myndighetene.</string>
<string name="civil_defense_source">Kilde: DSB (Direktoratet for samfunnssikkerhet og beredskap)</string>
</resources> </resources>

View file

@ -64,4 +64,19 @@
<!-- Tilgjenge --> <!-- Tilgjenge -->
<string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string> <string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string>
<!-- Sivilforsvar -->
<string name="action_civil_defense_info">Sivilforsvarsinformasjon</string>
<string name="civil_defense_title">Kva du skal gjere når alarmen går</string>
<string name="civil_defense_step1_title">1. Viktig melding-signalet</string>
<string name="civil_defense_step1_body">Tre seriar med korte støyt med eitt minutt stille mellom kvar serie. Dette tyder: søk informasjon med ein gong. Slå på DAB-radio, TV, eller sjekk offisielle kjelder på nett.</string>
<string name="civil_defense_step2_title">2. Flyalarm</string>
<string name="civil_defense_step2_body">Korte støyt som varar omtrent eitt minutt. Dette tyder umiddelbar fare for åtak — søk dekning no. Gå til næraste tilfluktsrom, kjellar eller innerrom med ein gong.</string>
<string name="civil_defense_step3_title">3. Gå innandørs og finn dekning</string>
<string name="civil_defense_step3_body">Kom deg innandørs. Lukk alle vindauge, dører og ventilasjonsopningar. Bruk denne appen for å finne næraste offentlege tilfluktsrom. Kompasset og kartet fungerer utan internett. Om det ikkje er noko tilfluktsrom i nærleiken, gå til ein kjellar eller eit innerrom bort frå vindauge.</string>
<string name="civil_defense_step4_title">4. Lytt til NRK på DAB-radio</string>
<string name="civil_defense_step4_body">Lytt til NRK P1 på DAB-radio for offisielle oppdateringar og instruksjonar frå styresmaktene. DAB-radio fungerer sjølv når mobilnettet og internett er nede. Ha ein batteridrive eller sveivbar DAB-radio som del av beredskapsutstyret ditt.</string>
<string name="civil_defense_step5_title">5. Faren over</string>
<string name="civil_defense_step5_body">Éin samanhengande tone på omtrent 30 sekund. Faren eller åtaket er over. Hald fram med å følgje instruksjonar frå styresmaktene.</string>
<string name="civil_defense_source">Kjelde: DSB (Direktoratet for samfunnstryggleik og beredskap)</string>
</resources> </resources>

View file

@ -64,4 +64,19 @@
<!-- Accessibility --> <!-- Accessibility -->
<string name="direction_arrow_description">Direction to shelter, %s away</string> <string name="direction_arrow_description">Direction to shelter, %s away</string>
<!-- Civil defense info -->
<string name="action_civil_defense_info">Civil defense information</string>
<string name="civil_defense_title">What to do when the alarm sounds</string>
<string name="civil_defense_step1_title">1. Important message signal</string>
<string name="civil_defense_step1_body">Three series of short blasts with one minute of silence between each series. This means: seek information immediately. Turn on DAB radio, TV, or check official sources online.</string>
<string name="civil_defense_step2_title">2. Air raid alarm</string>
<string name="civil_defense_step2_body">Short blasts lasting approximately one minute. This means immediate danger of attack — seek shelter now. Go to the nearest shelter, basement, or inner room immediately.</string>
<string name="civil_defense_step3_title">3. Go indoors and find shelter</string>
<string name="civil_defense_step3_body">Get indoors. Close all windows, doors, and ventilation openings. Use this app to find the nearest public shelter (tilfluktsrom). The compass and map work offline. If no shelter is nearby, go to a basement or an inner room away from windows.</string>
<string name="civil_defense_step4_title">4. Listen to NRK on DAB radio</string>
<string name="civil_defense_step4_body">Tune in to NRK P1 on DAB radio for official updates and instructions from authorities. DAB radio works even when mobile networks and the internet are down. Keep a battery-powered or hand-crank DAB radio as part of your emergency kit.</string>
<string name="civil_defense_step5_title">5. All clear</string>
<string name="civil_defense_step5_body">One continuous tone lasting approximately 30 seconds. The danger or attack is over. Continue to follow instructions from authorities.</string>
<string name="civil_defense_source">Source: DSB (Norwegian Directorate for Civil Protection)</string>
</resources> </resources>

View file

@ -8,4 +8,9 @@
<item name="android:statusBarColor">@color/status_bar_bg</item> <item name="android:statusBarColor">@color/status_bar_bg</item>
<item name="android:navigationBarColor">@color/background</item> <item name="android:navigationBarColor">@color/background</item>
</style> </style>
<style name="Theme.Tilfluktsrom.Dialog" parent="Theme.Material3.Dark.Dialog">
<item name="android:windowBackground">@color/status_bar_bg</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
</resources> </resources>

View file

@ -1,4 +1,4 @@
versionMajor=1 versionMajor=1
versionMinor=3 versionMinor=4
versionPatch=1 versionPatch=0
versionCode=5 versionCode=6