Bimba.git

commit 838e94515382b7a3df385f930b32d8b6a17ddd60

Author: Adam Evyčędo <git@apiote.xyz>

move strings to constants

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt
index 0d66dc74963b07142e773eb533bda2804a66e12a..73e6f5d9ede7a37a18a59ca311c10fc7f643b564 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/Api.kt
@@ -32,13 +32,18 @@ 	val feeds: FeedsSettings,
 	val apiPath: String
 ) {
 	companion object {
+		const val DEFAULT = "bimba.apiote.xyz"
+		const val HOST_KEY = "host"
+		const val TOKEN_KEY = "token"
+		const val API_PATH_KEY = "apiPath"
+
 		fun get(context: Context): Server {
 			val preferences = context.getSharedPreferences("shp", MODE_PRIVATE)
-			val apiPath = preferences.getString("apiPath", "")!!
+			val apiPath = preferences.getString(API_PATH_KEY, "")!!
 			val feeds = FeedsSettings.load(context, apiPath)
-			val host = preferences.getString("host", "bimba.apiote.xyz")!!
+			val host = preferences.getString(HOST_KEY, DEFAULT)!!
 			return Server(
-				host, preferences.getString("token", "")!!,
+				host, preferences.getString(TOKEN_KEY, "")!!,
 				feeds, apiPath
 			)
 		}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt
index 5b7701049711808d7606ecf398e3d60a65f685aa..ac05a4944b6d577160c3793997583c9c7126ccf1 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/MainActivity.kt
@@ -39,7 +39,9 @@ import xyz.apiote.bimba.czwek.dashboard.ui.home.HomeFragment
 import xyz.apiote.bimba.czwek.dashboard.ui.map.MapFragment
 import xyz.apiote.bimba.czwek.dashboard.ui.voyage.VoyageFragment
 import xyz.apiote.bimba.czwek.databinding.ActivityMainBinding
+import xyz.apiote.bimba.czwek.onboarding.FirstRunActivity
 import xyz.apiote.bimba.czwek.search.ResultsActivity
+import xyz.apiote.bimba.czwek.settings.DownloadCitiesWorker
 import xyz.apiote.bimba.czwek.settings.ServerChooserActivity
 import xyz.apiote.bimba.czwek.settings.SettingsActivity
 import xyz.apiote.bimba.czwek.settings.feeds.FeedChooserActivity
@@ -58,9 +60,7 @@ 		super.onCreate(savedInstanceState)
 		binding = ActivityMainBinding.inflate(layoutInflater)
 		setContentView(binding.root)
 
-		getSharedPreferences("shp", MODE_PRIVATE).edit(true) {
-			putBoolean("firstRun", false)
-		}
+		FirstRunActivity.setFirstRunDone(this)
 
 		ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
 			val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
@@ -166,12 +166,19 @@ 			}
 		}
 
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+			val notificationPermissionAsked =
+				PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
+					NOTIFICATION_PERMISSION_ASKED, false
+				)
 			if (ActivityCompat.checkSelfPermission(
 					this,
 					Manifest.permission.POST_NOTIFICATIONS
-				) != PackageManager.PERMISSION_GRANTED && shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
+				) != PackageManager.PERMISSION_GRANTED && !notificationPermissionAsked
 			) {
 				requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
+				PreferenceManager.getDefaultSharedPreferences(this).edit {
+					putBoolean(NOTIFICATION_PERMISSION_ASKED, true)
+				}
 			}
 		}
 	}
@@ -227,10 +234,10 @@ 				text.toString().trim().split(" ").first().trim(',').trim()
 			)
 		) {
 			if (PreferenceManager.getDefaultSharedPreferences(applicationContext)
-					.getLong("cities_last_update", -1) < 0
+					.getLong(DownloadCitiesWorker.LAST_UPDATE_KEY, -1) < 0
 			) {
 				if (!PreferenceManager.getDefaultSharedPreferences(applicationContext)
-						.getBoolean("no_geocoding_data_shown", false)
+						.getBoolean(NO_GEOCODING_DATA_SHOWN, false)
 				) {
 					MaterialAlertDialogBuilder(this)
 						.setIcon(R.drawable.geocoding)
@@ -243,6 +250,9 @@ 								text.toString()
 							)
 						}
 						.show()
+					PreferenceManager.getDefaultSharedPreferences(applicationContext).edit{
+						putBoolean(NO_GEOCODING_DATA_SHOWN, true)
+					}
 				}
 			} else {
 				showResults(ResultsActivity.Mode.MODE_SHORT_CODE, text.toString())
@@ -257,13 +267,7 @@ 		/* todo [3.2] (ux,low) animation
 			https://developer.android.com/guide/fragments/animate
 			https://github.com/raheemadamboev/fab-explosion-animation-app
 		*/
-		val intent = Intent(this, ResultsActivity::class.java).apply {
-			putExtra("mode", ResultsActivity.Mode.MODE_POSITION)
-			putExtra("query", query)
-			putExtra("lat", centerLatitude)
-			putExtra("lon", centerLongitude)
-		}
-		startActivity(intent)
+		startActivity(ResultsActivity.getIntent(this, ResultsActivity.Mode.MODE_POSITION, query, centerLatitude, centerLongitude))
 	}
 
 	private fun showResults(mode: ResultsActivity.Mode, query: String = "") {
@@ -271,11 +275,7 @@ 		/* todo [3.2] (ux,low) animation
 			https://developer.android.com/guide/fragments/animate
 			https://github.com/raheemadamboev/fab-explosion-animation-app
 		*/
-		val intent = Intent(this, ResultsActivity::class.java).apply {
-			putExtra("mode", mode)
-			putExtra("query", query)
-		}
-		startActivity(intent)
+		startActivity(ResultsActivity.getIntent(this, mode, query))
 	}
 
 	private fun setNavbarIcons(f: Fragment) {
@@ -300,5 +300,10 @@ 			else -> {
 				binding.bottomNavigation.menu[1].setIcon(R.drawable.home_black)
 			}
 		}
+	}
+
+	companion object {
+		const val NOTIFICATION_PERMISSION_ASKED = "notificationPermissionAsked"
+		const val NO_GEOCODING_DATA_SHOWN = "no_geocoding_data_shown"
 	}
 }
\ No newline at end of file




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt
index 711016241320500c160eac7dd14196cd1b974411..2b1d50a476c8181b6758489d60b61fe772080d09 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/Favourites.kt
@@ -5,7 +5,6 @@
 package xyz.apiote.bimba.czwek.dashboard.ui.home
 
 import android.content.Context
-import android.content.Intent
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -200,13 +199,7 @@ 					)
 			}
 
 			holder.root.setOnClickListener {
-				val intent = Intent(context, DeparturesActivity::class.java).apply {
-					putExtra("code", favourite.stopCode)
-					putExtra("name", favourite.stopName)
-					putExtra("feedID", favourite.feedID)
-					putExtra("linesFilter", favourite.lines.toTypedArray())
-				}
-				context.startActivity(intent)
+				context.startActivity(DeparturesActivity.getIntent(context, favourite.stopCode, favourite.stopName, favourite.feedID, favourite.lines.toTypedArray()))
 			}
 		}
 	}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt
index 42dcc1a330de20dc80aa99bab9e5755931c00351..65339fd2b8ef9eed82359fd09bd00e3bea0bce44 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapFragment.kt
@@ -57,6 +57,13 @@
 
 class MapFragment : Fragment() {
 
+	companion object {
+		const val PREFERENCES_NAME = "shp"
+		const val ZOOM_KEY = "mapZoom"
+		const val CENTRE_LATITUDE_KEY = "mapCentreLat"
+		const val CENTRE_LONGITUDE_KEY = "mapCentreLon"
+	}
+
 	private var maybeBinding: FragmentMapBinding? = null
 	private val binding get() = maybeBinding!!
 
@@ -96,7 +103,7 @@ 		binding.map.overlays.add(RotationGestureOverlay(binding.map).apply { isEnabled = true })
 
 		locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(context), binding.map)
 		context?.let {
-			centreMap(it.getSharedPreferences("shp", MODE_PRIVATE))
+			centreMap(it.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE))
 
 			locationOverlay.setDirectionIcon(
 				AppCompatResources.getDrawable(it, R.drawable.navigation_arrow)?.mutate()
@@ -250,10 +257,10 @@ 	}
 
 	private fun centreMap(preferences: SharedPreferences) {
 		maybeBinding?.map?.controller?.apply {
-			setZoom(preferences.getFloat("mapZoom", 17.0f).toDouble())
+			setZoom(preferences.getFloat(ZOOM_KEY, 17.0f).toDouble())
 			val startPoint = GeoPoint(
-				preferences.getFloat("mapCentreLat", 52.39511f).toDouble(),
-				preferences.getFloat("mapCentreLon", 16.89506f).toDouble()
+				preferences.getFloat(CENTRE_LATITUDE_KEY, 52.39511f).toDouble(),
+				preferences.getFloat(CENTRE_LONGITUDE_KEY, 16.89506f).toDouble()
 			)
 			setCenter(startPoint)
 		}
@@ -264,7 +271,7 @@ 		super.onResume()
 		binding.map.onResume()
 		locationOverlay.enableMyLocation()
 		context?.let { ctx ->
-			ctx.getSharedPreferences("shp", MODE_PRIVATE).let {
+			ctx.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).let {
 				Configuration.getInstance()
 					.load(ctx, it)
 				centreMap(it)
@@ -278,10 +285,10 @@ 		binding.map.onPause()
 		locationOverlay.disableMyLocation()
 		val centre = binding.map.mapCenter
 		context?.let { ctx ->
-			ctx.getSharedPreferences("shp", MODE_PRIVATE).edit(true) {
-				putFloat("mapCentreLat", centre.latitude.toFloat())
-				putFloat("mapCentreLon", centre.longitude.toFloat())
-				putFloat("mapZoom", binding.map.zoomLevelDouble.toFloat())
+			ctx.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).edit(true) {
+				putFloat(CENTRE_LATITUDE_KEY, centre.latitude.toFloat())
+				putFloat(CENTRE_LONGITUDE_KEY, centre.longitude.toFloat())
+				putFloat(ZOOM_KEY, binding.map.zoomLevelDouble.toFloat())
 			}
 		}
 		handler.removeCallbacks(workRunnable)




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt
index 6761cf6abca84e34c17a37b63e36ca2fe1992b9e..29ab95545f1d7ad0c79246c6a85e095b5f023af5 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/map/MapViewModel.kt
@@ -167,12 +167,7 @@ 	private fun showStop(content: View, stop: Stop) {
 		context?.let { ctx ->
 			content.findViewById<TextView>(R.id.stop_name).text = stop.name
 			content.findViewById<Button>(R.id.departures_button).setOnClickListener {
-				val intent = Intent(ctx, DeparturesActivity::class.java).apply {
-					putExtra("code", stop.code)
-					putExtra("name", stop.name)
-					putExtra("feedID", stop.feedID)
-				}
-				startActivity(intent)
+				startActivity(DeparturesActivity.getIntent(requireContext(), stop.code, stop.name, stop.feedID!!))
 			}
 			content.findViewById<Button>(R.id.navigation_button).setOnClickListener {
 				try {




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt b/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt
index a59744516071cdef554282c14ffd0a342a9b3fff..d30151cc7c7eecd37d11647c6c3ec2b41087d518 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/departures/Departures.kt
@@ -118,7 +118,7 @@ 			val alertDescriptions = alerts.map { it.description }.filter { it != "" }
 				.joinToString(separator = "\n")
 			holder?.moreButton?.setOnClickListener {
 				MaterialAlertDialogBuilder(context!!)
-					.setTitle("Alerts")
+					.setTitle(R.string.alerts)
 					.setPositiveButton(R.string.ok) { _, _ -> }
 					.setMessage(alertDescriptions)
 					.show()
@@ -140,6 +140,11 @@ 	private var items: List,
 	private val onClickListener: ((Departure) -> Unit)
 ) :
 	RecyclerView.Adapter<ViewHolder>() {
+
+		companion object {
+			const val ALERT_ITEM_ID = "alert"
+		}
+
 	var lastUpdate: ZonedDateTime =
 		ZonedDateTime.of(0, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())
 		private set
@@ -156,7 +161,7 @@ 		override fun getNewListSize() = newDepartures.size
 
 		override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
 			(oldDepartures[oldItemPosition].departure?.ID
-				?: "alert") == (newDepartures[newItemPosition].departure?.ID ?: "alert")
+				?: ALERT_ITEM_ID) == (newDepartures[newItemPosition].departure?.ID ?: ALERT_ITEM_ID)
 
 		override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
 			val oldDeparture = oldDepartures[oldItemPosition]
@@ -183,7 +188,7 @@ 	private var departuresPositions: MutableMap = HashMap()
 
 	init {
 		items.forEachIndexed { i, departure ->
-			departuresPositions[departure.departure?.ID ?: "alert"] = i
+			departuresPositions[departure.departure?.ID ?: ALERT_ITEM_ID] = i
 		}
 	}
 
@@ -243,7 +248,7 @@ 			departures
 		}
 		val newPositions: MutableMap<String, Int> = HashMap()
 		newDepartures.forEachIndexed { i, departure ->
-			newPositions[departure.departure?.ID ?: "alert"] = i
+			newPositions[departure.departure?.ID ?: ALERT_ITEM_ID] = i
 		}
 		val diff = DiffUtil.calculateDiff(
 			DiffUtilCallback(
@@ -417,7 +422,7 @@ 				findViewById(R.id.alerts).apply {
 					visibility = View.VISIBLE
 					setOnClickListener {
 						MaterialAlertDialogBuilder(context)
-							.setTitle("Alerts")
+							.setTitle(R.string.alerts)
 							.setPositiveButton(R.string.ok) { _, _ -> }
 							.setMessage(departure.alerts.map { it.description }.filter { it != "" }
 								.joinToString(separator = "\n"))




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt
index 7cb5e9c07c7093ca8c6fada65b4d80e54bff7e30..94cd3b2272bb5374c418abd4d4eafbdac616328d 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesActivity.kt
@@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later
 
 package xyz.apiote.bimba.czwek.departures
 
+import android.content.Context
 import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
@@ -52,6 +53,35 @@ import java.time.ZoneId
 import java.time.ZonedDateTime
 
 class DeparturesActivity : AppCompatActivity() {
+	companion object {
+		const val CODE_PARAM = "code"
+		const val NAME_PARAM = "name"
+		const val FEED_PARAM = "feedID"
+		const val LINES_FILTER_PARAM = "linesFilter"
+		fun getIntent(
+			context: Context,
+			code: String,
+			name: String,
+			feedID: String,
+		) = Intent(context, DeparturesActivity::class.java).apply {
+			putExtra(CODE_PARAM, code)
+			putExtra(NAME_PARAM, name)
+			putExtra(FEED_PARAM, feedID)
+		}
+		fun getIntent(
+			context: Context,
+			code: String,
+			name: String,
+			feedID: String,
+			lines: Array<String>
+		) = Intent(context, DeparturesActivity::class.java).apply {
+			putExtra(CODE_PARAM, code)
+			putExtra(NAME_PARAM, name)
+			putExtra(FEED_PARAM, feedID)
+			putExtra(LINES_FILTER_PARAM, lines)
+		}
+	}
+
 	private var _binding: ActivityDeparturesBinding? = null
 	private val binding get() = _binding!!
 
@@ -231,7 +261,7 @@ 								this,
 								R.drawable.filter
 							)
 						)
-							.setTitle("Filtered departures")
+							.setTitle(R.string.filtered_departures)
 							.setMessage(R.string.filtered_stop_question)
 							.setPositiveButton(R.string.filtered) { _, _ ->
 								saveFavourite(viewModel.linesFilter.value!!.keys)
@@ -345,6 +375,8 @@ 			val request = NetworkRequest.Builder()
 				.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build()
 			connectivityManager.registerNetworkCallback(request, networkCallback)
 		}
+
+		// throw Exception("test exception")
 	}
 
 	override fun onResume() {
@@ -367,7 +399,7 @@
 	private fun getName(): String {
 		return when (intent?.action) {
 			Intent.ACTION_VIEW -> getString(R.string.stop_from_qr_code)
-			null -> intent?.extras?.getString("name") ?: ""
+			null -> intent?.extras?.getString(NAME_PARAM) ?: ""
 			else -> ""
 		}
 	}
@@ -381,12 +413,12 @@ 	}
 
 	private fun getLines(): List<String>? {
 		return when (intent?.action) {
-			null -> intent?.extras?.getStringArray("linesFilter")?.toList()
+			null -> intent?.extras?.getStringArray(LINES_FILTER_PARAM)?.toList()
 			else -> null
 		}
 	}
 
-	private fun getCode() = intent?.extras?.getString("code")
+	private fun getCode() = intent?.extras?.getString(CODE_PARAM)
 
 	fun getDepartures(force: Boolean = false) {
 		binding.departuresUpdatesProgress.isIndeterminate = true
@@ -489,8 +521,8 @@ 	}
 
 	private fun saveFavourite(linesFilter: Set<String>) {
 		val context = this
-		val feedID = intent.extras?.getString("feedID")
-		val code = intent?.extras?.getString("code")
+		val feedID = intent.extras?.getString(FEED_PARAM)
+		val code = intent?.extras?.getString(CODE_PARAM)
 		if (feedID == null || code == null) {
 			Toast.makeText(this, R.string.cannot_save_favourite, Toast.LENGTH_LONG).show()
 			return




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt
index 9d3b81505bb8907710dcf14bb6d543a1c5c1e3bf..c8ba0c68769ae2a9a4c561df995acbe21e8524ea 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/departures/DeparturesViewModel.kt
@@ -104,7 +104,7 @@ 				}
 			}
 
 			null -> {
-				val feedID = intent.extras?.getString("feedID")
+				val feedID = intent.extras?.getString(DeparturesActivity.FEED_PARAM)
 				feeds?.get(feedID) ?: throw TrafficResponseException(41)
 			}
 
@@ -145,7 +145,7 @@ 					}
 				} ?: throw TrafficResponseException(41)
 			}
 
-			null -> intent?.extras?.getString("code") ?: throw TrafficResponseException(41)
+			null -> intent?.extras?.getString(DeparturesActivity.CODE_PARAM) ?: throw TrafficResponseException(41)
 			else -> throw TrafficResponseException(41)
 		}
 	}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt
index 117278a8722bd5277e13416aeb8abdeea2ea8c24..e7828395a0b090e4ce33797eb61fa5fc2c789f1d 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/FirstRunActivity.kt
@@ -6,12 +6,13 @@ package xyz.apiote.bimba.czwek.onboarding
 
 import android.app.NotificationChannel
 import android.app.NotificationManager
+import android.content.Context
 import android.content.Intent
 import android.os.Build
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
 import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.preference.PreferenceManager
 import androidx.work.OneTimeWorkRequest
 import androidx.work.WorkManager
 import xyz.apiote.bimba.czwek.R
@@ -19,34 +20,22 @@ import xyz.apiote.bimba.czwek.dashboard.MainActivity
 import xyz.apiote.bimba.czwek.repo.migrateDB
 import xyz.apiote.bimba.czwek.settings.DownloadCitiesWorker
 import xyz.apiote.bimba.czwek.settings.feeds.migrateFeedsSettings
-import java.time.Instant
-import java.time.temporal.ChronoUnit
 
 class FirstRunActivity : AppCompatActivity() {
 	override fun onCreate(savedInstanceState: Bundle?) {
 		installSplashScreen()
 		super.onCreate(savedInstanceState)
 
-		val preferences = getSharedPreferences("shp", MODE_PRIVATE)
-
 		migrateFeedsSettings(this)
 		migrateDB(this)
 		createNotificationChannels()
 
-		val (updatesEnabled, weekPassed) = PreferenceManager.getDefaultSharedPreferences(this).let {
-			arrayOf(
-				it.getBoolean("autoupdate_cities_list", false),
-				Instant.ofEpochSecond(it.getLong("cities_last_update", 0)).plus(7, ChronoUnit.DAYS)
-					.isBefore(Instant.now())
-			)
-		}
-
-		if (updatesEnabled && weekPassed) {
+		if (DownloadCitiesWorker.shouldUpdate(this)) {
 			WorkManager.getInstance(this)
 				.enqueue(OneTimeWorkRequest.from(DownloadCitiesWorker::class.java))
 		}
 
-		val intent = if (preferences.getBoolean("firstRun", true)) {
+		val intent = if (getFirstRun(this)) {
 			Intent(this, OnboardingActivity::class.java)
 		} else {
 			Intent(this, MainActivity::class.java)
@@ -60,12 +49,27 @@ 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 			val name = getString(R.string.cities_channel_name)
 			val descriptionText = getString(R.string.cities_channel_description)
 			val importance = NotificationManager.IMPORTANCE_LOW
-			val channel = NotificationChannel("cities_channel", name, importance).apply {
+			val channel = NotificationChannel(DownloadCitiesWorker.NOTIFICATION_CHANNEL, name, importance).apply {
 				description = descriptionText
 			}
 			val notificationManager: NotificationManager =
 				getSystemService(NOTIFICATION_SERVICE) as NotificationManager
 			notificationManager.createNotificationChannel(channel)
+		}
+	}
+
+	companion object {
+		private const val PREFERENCES_NAME = "shp"
+		private const val FIRST_RUN_KEY = "firstRun"
+
+		fun setFirstRunDone(context: Context) {
+			context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).edit {
+				putBoolean(FIRST_RUN_KEY, false)
+			}
+		}
+
+		fun getFirstRun(context: Context): Boolean {
+			return context.getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE).getBoolean(FIRST_RUN_KEY, true)
 		}
 	}
 }
\ No newline at end of file




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt
index 7833875ef94ce6fe166c7f4536871d0827d49e19..f5df578827b6c4cc1cdb8844307a9ca4fa94b4dc 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt
@@ -4,7 +4,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later
 
 package xyz.apiote.bimba.czwek.onboarding
 
-import android.content.Intent
 import android.graphics.Typeface
 import android.os.Bundle
 import android.text.Spannable
@@ -28,7 +27,7 @@ 	private val binding get() = _binding!!
 
 	private val activityLauncher =
 		registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
-			if (!getSharedPreferences("shp", MODE_PRIVATE).getBoolean("firstRun", true)) {
+			if (!FirstRunActivity.getFirstRun(this)) {
 				finish()
 			}
 		}
@@ -79,9 +78,6 @@ 		}
 	}
 
 	private fun moveOn(simple: Boolean) {
-		val intent = Intent(this, ServerChooserActivity::class.java).apply {
-			putExtra("simple", simple)
-		}
-		activityLauncher.launch(intent)
+		activityLauncher.launch(ServerChooserActivity.getIntent(this, simple))
 	}
 }
\ No newline at end of file




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
index 9a778552b653033536f19fbbb2deb113457e765b..b29210b31e39602eb0a83b32b219f1d55c41ee7b 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
@@ -5,6 +5,7 @@
 package xyz.apiote.bimba.czwek.search
 
 import android.content.Context
+import android.content.Intent
 import android.hardware.Sensor
 import android.hardware.SensorEvent
 import android.hardware.SensorEventListener
@@ -46,6 +47,33 @@ 	enum class Mode {
 		MODE_LOCATION, MODE_SEARCH, MODE_POSITION, MODE_SHORT_CODE_LOCATION, MODE_SHORT_CODE
 	}
 
+	companion object {
+		const val MODE_KEY = "mode"
+		const val QUERY_KEY = "query"
+		const val LATITUDE_KEY = "lat"
+		const val LONGITUDE_KEY = "lon"
+		fun getIntent(
+			context: Context,
+			mode: Mode,
+			query: String,
+			latitude: Double,
+			longitude: Double
+		) =
+			Intent(context, ResultsActivity::class.java).apply {
+				putExtra(MODE_KEY, mode)
+				putExtra(QUERY_KEY, query)
+				putExtra(LATITUDE_KEY, latitude)
+				putExtra(LONGITUDE_KEY, longitude)
+			}
+
+		fun getIntent(context: Context, mode: Mode, query: String) =
+			Intent(context, ResultsActivity::class.java).apply {
+				putExtra(MODE_KEY, mode)
+				putExtra(QUERY_KEY, query)
+			}
+
+	}
+
 	private var _binding: ActivityResultsBinding? = null
 	private val binding get() = _binding!!
 
@@ -91,14 +119,15 @@ 				locate()
 			}
 
 			Mode.MODE_SHORT_CODE_LOCATION -> {
-				val query = intent.extras?.getString("query")
-				getString(R.string.stops_near_code, query)
+				val query = intent.extras?.getString(QUERY_KEY)
+				supportActionBar?.title = getString(R.string.stops_near_code, query)
 				shortOLC = OpenLocationCode(query)
 				locate()
 			}
 
 			Mode.MODE_SHORT_CODE -> {
-				val query = intent.extras?.getString("query")
+				val query = intent.extras?.getString(QUERY_KEY)
+				supportActionBar?.title = getString(R.string.stops_near_code, query)
 				val split = query!!.trim().split(" ")
 				val code = split.first().trim(',').trim()
 				val freePart = split.drop(1).joinToString(" ")
@@ -115,9 +144,9 @@ 				}
 			}
 
 			Mode.MODE_POSITION -> {
-				val query = intent.extras?.getString("query")
-				val lat = intent.extras?.getDouble("lat")
-				val lon = intent.extras?.getDouble("lon")
+				val query = intent.extras?.getString(QUERY_KEY)
+				val lat = intent.extras?.getDouble(LATITUDE_KEY)
+				val lon = intent.extras?.getDouble(LONGITUDE_KEY)
 				supportActionBar?.title = getString(R.string.stops_near_code, query)
 				getQueryablesByLocation(Location(null).apply {
 					latitude = lat!!
@@ -126,7 +155,7 @@ 				}, this)
 			}
 
 			Mode.MODE_SEARCH -> {
-				val query = intent.extras?.getString("query")!!
+				val query = intent.extras?.getString(QUERY_KEY)!!
 				supportActionBar?.title = getString(R.string.results_for, query)
 				getQueryablesByQuery(query, this)
 			}
@@ -135,10 +164,10 @@ 	}
 
 	private fun getMode(): Mode {
 		return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-			intent.extras!!.getSerializable("mode", Mode::class.java)!!
+			intent.extras!!.getSerializable(MODE_KEY, Mode::class.java)!!
 		} else {
 			@Suppress("DEPRECATION")
-			intent.extras!!.get("mode") as Mode
+			intent.extras!!.get(MODE_KEY) as Mode
 		}
 	}
 
@@ -162,7 +191,10 @@ 			handler.postDelayed(runnable, 60 * 1000)
 			locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
 				?.let { onLocationChanged(it) }
 		} catch (_: SecurityException) {
-			// this won’t happen because we don’t start this activity without location permission
+			Log.wtf(
+				"locate",
+				"this shouldn’t happen because we don’t start this activity without location permission"
+			)
 		}
 	}
 




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt
index 02de1d344e0666614ee02207fe90531cd3cb5626..6167fe4d3be5d7ae886b91b3ba9f6a8e00eaca33 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/DownloadCitiesWorker.kt
@@ -1,7 +1,6 @@
 package xyz.apiote.bimba.czwek.settings
 
 import android.Manifest
-import android.app.NotificationManager
 import android.content.Context
 import android.content.pm.PackageManager
 import android.database.sqlite.SQLiteDatabase
@@ -21,7 +20,9 @@ import java.io.BufferedInputStream
 import java.io.BufferedOutputStream
 import java.io.File
 import java.net.URL
+import java.time.Instant
 import java.time.ZonedDateTime
+import java.time.temporal.ChronoUnit
 import java.util.UUID
 import java.util.zip.ZipEntry
 import java.util.zip.ZipInputStream
@@ -29,8 +30,31 @@
 class DownloadCitiesWorker(appContext: Context, workerParams: WorkerParameters) :
 	Worker(appContext, workerParams) {
 
+	companion object {
+		const val AUTOUPDATE_KEY = "autoupdate_cities_list"
+		const val LAST_UPDATE_KEY = "cities_last_update"
+		const val NOTIFICATION_CHANNEL = "cities_channel"
+		const val DATABASE_NAME = "geocoding"
+		const val ETAG_HEADER_NAME = "ETag"
+		const val ETAG_KEY = "cities_etag"
+		const val RESULT_ZIP_FILE = "cities.zip"
+		const val CITIES_URL = "https://download.geonames.org/export/dump/cities15000.zip"
+		const val CITIES_FILE = "cities15000.txt"
+		fun shouldUpdate(context: Context): Boolean {
+			val (updatesEnabled, weekPassed) = PreferenceManager.getDefaultSharedPreferences(context)
+				.let {
+					arrayOf(
+						it.getBoolean(AUTOUPDATE_KEY, false),
+						Instant.ofEpochSecond(it.getLong(LAST_UPDATE_KEY, 0)).plus(7, ChronoUnit.DAYS)
+							.isBefore(Instant.now())
+					)
+				}
+			return updatesEnabled && weekPassed
+		}
+	}
+
 	override fun doWork(): Result {
-		val notificationBuilder = NotificationCompat.Builder(applicationContext, "cities_channel")
+		val notificationBuilder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
 			.setSmallIcon(R.drawable.geocoding)
 			.setContentTitle(applicationContext.getString(R.string.updating_geocoding_data))
 			.setContentText(applicationContext.getString(R.string.downloading_cities_list))
@@ -46,15 +70,15 @@ 				NotificationManagerCompat.from(applicationContext).notify(0, notificationBuilder.build())
 			}
 
 			val db = SQLiteDatabase.openOrCreateDatabase(
-				applicationContext.getDatabasePath("geocoding").path,
+				applicationContext.getDatabasePath(DATABASE_NAME).path,
 				null
 			)
-			val url = URL("https://download.geonames.org/export/dump/cities15000.zip")
+			val url = URL(CITIES_URL)
 			val connection = url.openConnection()
 			var length = connection.contentLength.toLong()
-			val connectionEtag = connection.getHeaderField("ETag")
+			val connectionEtag = connection.getHeaderField(ETAG_HEADER_NAME)
 			val savedEtag = PreferenceManager.getDefaultSharedPreferences(applicationContext)
-				.getString("cities_etag", null)
+				.getString(ETAG_KEY, null)
 			if (savedEtag != null && savedEtag == connectionEtag) {
 				if (ActivityCompat.checkSelfPermission(
 						applicationContext,
@@ -77,7 +101,7 @@ 					.setInputStream(BufferedInputStream(connection.getInputStream())).get()
 			val zipFileStream = BufferedOutputStream(
 				File(
 					applicationContext.noBackupFilesDir.path,
-					"cities.zip"
+					RESULT_ZIP_FILE
 				).outputStream()
 			)
 
@@ -113,7 +137,7 @@ 				) == PackageManager.PERMISSION_GRANTED
 			) {
 				NotificationManagerCompat.from(applicationContext).notify(0, notificationBuilder.build())
 			}
-			val zipFile = File(applicationContext.noBackupFilesDir.path, "cities.zip")
+			val zipFile = File(applicationContext.noBackupFilesDir.path, RESULT_ZIP_FILE)
 			length = zipFile.length()
 			countingStream =
 				BoundedInputStream.Builder().setInputStream(BufferedInputStream(zipFile.inputStream()))
@@ -121,7 +145,7 @@ 					.get()
 			val stream = ZipInputStream(countingStream)
 			var entry: ZipEntry? = stream.nextEntry
 			while (entry != null) {
-				if (entry.name != "cities15000.txt") {
+				if (entry.name != CITIES_FILE) {
 					entry = stream.nextEntry
 					continue
 				}
@@ -185,8 +209,8 @@ 			db.execSQL("alter table place_names2 rename to place_names")
 			db.execSQL("create unique index place_names__name on place_names(name)")
 
 			PreferenceManager.getDefaultSharedPreferences(applicationContext).edit {
-				putLong("cities_last_update", ZonedDateTime.now().toEpochSecond())
-				putString("cities_etag", connectionEtag)
+				putLong(LAST_UPDATE_KEY, ZonedDateTime.now().toEpochSecond())
+				putString(ETAG_KEY, connectionEtag)
 			}
 
 			db.close()




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt
index 88165f880c011dfc8b55b0e6d32ac1363d8b0f55..8eb69c2ac80b7122daaae47c52eb377f6755e1d4 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/ServerChooserActivity.kt
@@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later
 
 package xyz.apiote.bimba.czwek.settings
 
+import android.content.Context
 import android.content.Intent
 import android.content.SharedPreferences
 import android.graphics.Color
@@ -30,15 +31,25 @@ import xyz.apiote.bimba.czwek.api.Server
 import xyz.apiote.bimba.czwek.api.TrafficFormatException
 import xyz.apiote.bimba.czwek.api.getBimba
 import xyz.apiote.bimba.czwek.databinding.ActivityServerChooserBinding
+import xyz.apiote.bimba.czwek.onboarding.FirstRunActivity
 import xyz.apiote.bimba.czwek.settings.feeds.FeedChooserActivity
 
 class ServerChooserActivity : AppCompatActivity() {
+	companion object {
+		const val PARAM_SIMPLE = "simple"
+		const val IN_FEEDS_TRANSACTION = "inFeedsTransaction"
+		const val PREFERENCES_NAME = "shp"
+		fun getIntent(context: Context, simple: Boolean) = Intent(context, ServerChooserActivity::class.java).apply {
+				putExtra(PARAM_SIMPLE, simple)
+		}
+	}
+
 	private var _binding: ActivityServerChooserBinding? = null
 	private val binding get() = _binding!!
 
 	private val activityLauncher =
 		registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
-			if (!preferences.getBoolean("inFeedsTransaction", true)) {
+			if (!preferences.getBoolean(IN_FEEDS_TRANSACTION, true)) {
 				finish()
 			}
 		}
@@ -49,10 +60,10 @@ 	override fun onCreate(savedInstanceState: Bundle?) {
 		enableEdgeToEdge()
 		super.onCreate(savedInstanceState)
 
-		preferences = getSharedPreferences("shp", MODE_PRIVATE)
+		preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)
 
-		if (intent.getBooleanExtra("simple", false)) {
-			setServer("bimba.apiote.xyz", "")
+		if (intent.getBooleanExtra(PARAM_SIMPLE, false)) {
+			setServer(Server.DEFAULT, "")
 			checkServer(true)
 		} else {
 			_binding = ActivityServerChooserBinding.inflate(layoutInflater)
@@ -65,7 +76,7 @@ 				windowInsets
 			}
 
 			preferences.edit(true) {
-				putBoolean("inFeedsTransaction", true)
+				putBoolean(IN_FEEDS_TRANSACTION, true)
 			}
 
 			if (preferences.getBoolean("shibboleet", false)) {
@@ -78,7 +89,7 @@ 			binding.serverField.editText!!.addTextChangedListener { editable ->
 				binding.button.isEnabled = !editable.isNullOrBlank()
 			}
 
-			if (!preferences.getBoolean("firstRun", true)) {
+			if (!FirstRunActivity.getFirstRun(this)) {
 				Server.get(this).let { server ->
 					binding.serverField.editText!!.setText(server.host)
 					binding.tokenField.editText!!.setText(server.token)
@@ -93,7 +104,7 @@ 						binding.button.setTextColor(Color.WHITE)
 						preferences.edit(true) {
 							putBoolean("shibboleet", true)
 						}
-						if (!preferences.getBoolean("firstRun", true)) {
+						if (!FirstRunActivity.getFirstRun(this)) {
 							Server.get(this).let { server ->
 								binding.serverField.editText!!.setText(server.host)
 								binding.tokenField.editText!!.setText(server.token)
@@ -107,7 +118,7 @@ 						setContentView(binding.root)
 						preferences.edit(true) {
 							putBoolean("shibboleet", false)
 						}
-						if (!preferences.getBoolean("firstRun", true)) {
+						if (!FirstRunActivity.getFirstRun(this)) {
 							Server.get(this).let { server ->
 								binding.serverField.editText!!.setText(server.host)
 								binding.tokenField.editText!!.setText(server.token)
@@ -185,7 +196,7 @@ 		}
 	}
 
 	private fun moveOn(bimba: Bimba, isSimple: Boolean) {
-		val token = preferences.getString("token", "")
+		val token = preferences.getString(Server.TOKEN_KEY, "")
 
 		if (bimba.isPrivate() && token == "") {
 			showDialog(R.string.error, R.string.server_private_question, R.drawable.error_sec, null)
@@ -204,20 +215,20 @@ 	}
 
 	private fun setServer(hostname: String, token: String) {
 		preferences.edit(true) {
-			putString("host", hostname)
-			putString("token", token)
+			putString(Server.HOST_KEY, hostname)
+			putString(Server.TOKEN_KEY, token)
 		}
 	}
 
 	private fun updateServer(apiPath: String) {
 		preferences.edit(true) {
-			putString("apiPath", apiPath)
+			putString(Server.API_PATH_KEY, apiPath)
 		}
 	}
 
 	private fun runFeedsActivity() {
 		activityLauncher.launch(Intent(this, FeedChooserActivity::class.java))
-		if (intent.getBooleanExtra("simple", false)) {
+		if (intent.getBooleanExtra(PARAM_SIMPLE, false)) {
 			finish()
 		}
 	}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt
index 4a76cd5ef120f9419ff479301845c81d8240ceae..7f75a263c06670675535a7945c4367d2e62a30a1 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/SettingsActivity.kt
@@ -70,7 +70,7 @@ 				findPreference("download_cities_list")?.isEnabled = false
 			}
 
 			val citiesLastUpdate = PreferenceManager.getDefaultSharedPreferences(requireContext())
-				.getLong("cities_last_update", -1)
+				.getLong(DownloadCitiesWorker.LAST_UPDATE_KEY, -1)
 			if (citiesLastUpdate > 0) {
 				val lastUpdateTime = DateUtils.getRelativeDateTimeString(
 					context,




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt
index 09f4dd40f4c2eca59f1f092a66042214f88f14a6..3d49614ac2d04edf2269267b404c0f4c5ecc7b7e 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt
@@ -23,12 +23,17 @@ import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.api.Server
 import xyz.apiote.bimba.czwek.dashboard.MainActivity
 import xyz.apiote.bimba.czwek.databinding.ActivityFeedChooserBinding
+import xyz.apiote.bimba.czwek.onboarding.FirstRunActivity
 import xyz.apiote.bimba.czwek.repo.FeedInfo
+import xyz.apiote.bimba.czwek.settings.ServerChooserActivity
 
 // TODO on internet connection -> getServer
 // TODO swipe to refresh?
 
 class FeedChooserActivity : AppCompatActivity() {
+	companion object {
+		const val PREFERENCES_NAME = "shp"
+	}
 	private lateinit var viewModel: FeedsViewModel
 	private var _binding: ActivityFeedChooserBinding? = null
 	private val binding get() = _binding!!
@@ -124,11 +129,11 @@ 	}
 
 	private fun moveOn() {
 		viewModel.settings.value?.save(this, Server.get(this))
-		val preferences = getSharedPreferences("shp", MODE_PRIVATE)
+		val preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)
 		preferences.edit(true) {
-			putBoolean("inFeedsTransaction", false)
+			putBoolean(ServerChooserActivity.IN_FEEDS_TRANSACTION, false)
 		}
-		if (preferences.getBoolean("firstRun", true)) {
+		if (FirstRunActivity.getFirstRun(this)) {
 			val intent = Intent(this, MainActivity::class.java)
 			startActivity(intent)
 		}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt
index 632af91e8e3286638d6614460593ed54b59dcc18..31c0bcd141c540e1b70d418b402b6859ee6a9199 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt
@@ -22,7 +22,7 @@
 	fun save(context: Context, server: Server) {
 		val doc = KBson().dump(serializer(), this).toHexString()
 		val feedsPreferences =
-			context.getSharedPreferences("feeds_settings", AppCompatActivity.MODE_PRIVATE)
+			context.getSharedPreferences(PREFERENCES_NAME, AppCompatActivity.MODE_PRIVATE)
 		feedsPreferences.edit {
 			val key = URLEncoder.encode(server.apiPath, "utf-8")
 			putString(key, doc)
@@ -30,9 +30,10 @@ 		}
 	}
 
 	companion object {
+		const val PREFERENCES_NAME = "feeds_settings"
 		fun load(context: Context, apiPath: String = Server.get(context).apiPath): FeedsSettings {
 			val doc = context.getSharedPreferences(
-				"feeds_settings",
+				PREFERENCES_NAME,
 				Context.MODE_PRIVATE
 			).getString(URLEncoder.encode(apiPath, "utf-8"), null)
 			return doc?.let { KBson().load(serializer(), doc.hexToByteArray()) } ?: FeedsSettings(




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fc80263a37be6d8ee0697ece51f1bb3c570cc960..d603ba7d948feef9f2abd3c68ed8d1427f69df28 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -276,4 +276,6 @@ 	Send
 	<string name="discard">Discard</string>
 	<string name="send_with_comment">Send with commend</string>
 	<string name="acra_notification_comment">Comment added to crash report</string>
+	<string name="filtered_departures">Filtered departures</string>
+	<string name="alerts">Alerts</string>
 </resources>