Bimba.git

commit f42cabb4c00acd643489c8758be0f6d490414ea8

Author: Adam <git@apiote.xyz>

error handling

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


diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt
index d4ef93183cd94fa5f9fc54d150ea130c42535c54..9003b635331206b7d7214cd7d2b8146607f47e52 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt
@@ -14,7 +14,7 @@ data class Server(val host: String, val token: String, val feeds: String)
 
 data class Result(val stream: InputStream?, val error: Error?)
 
-data class Error(val statusCode: Int, val stringResource: Int)
+data class Error(val statusCode: Int, val stringResource: Int, val imageResource: Int)
 
 @Suppress("BlockingMethodInNonBlockingContext")
 suspend fun getFeeds(cm: ConnectivityManager, server: Server): Result {
@@ -46,7 +46,7 @@ suspend fun rawRequest(url: URL, server: Server, cm: ConnectivityManager): Result {
 	@Suppress("DEPRECATION")  // fix_later(API_29, API_23) https://developer.android.com/reference/android/net/ConnectivityManager#getActiveNetwork()
 	if (cm.activeNetworkInfo == null) {
 		// todo check false-positives
-		return Result(null, Error(0, R.string.error_offline))
+		return Result(null, Error(0, R.string.error_offline, R.drawable.error_net))
 	}
 	return withContext(Dispatchers.IO) {
 		val c = (url.openConnection() as HttpURLConnection).apply {
@@ -56,23 +56,25 @@ 		try {
 			if (c.responseCode == 200) {
 				Result(c.inputStream, null)
 			} else {
-				val string = when (c.responseCode) {
-					400 -> R.string.error_400
-					401 -> R.string.error_401
-					403 -> R.string.error_403
-					404 -> R.string.error_404 // todo check if server returns 404
-					429 -> R.string.error_429
-					500 -> R.string.error_50x
-					502 -> R.string.error_50x
-					503 -> R.string.error_50x
-					504 -> R.string.error_50x
-					else -> R.string.error_unknown
+				val (string, image) = when (c.responseCode) {
+					400 -> Pair(R.string.error_400, R.drawable.error_app)
+					401 -> Pair(R.string.error_401, R.drawable.error_sec)
+					403 -> Pair(R.string.error_403, R.drawable.error_sec)
+					404 -> Pair(
+						R.string.error_404,
+						R.drawable.error_search
+					) // todo check if server returns 404
+					429 -> Pair(R.string.error_429, R.drawable.error_limit)
+					500 -> Pair(R.string.error_50x, R.drawable.error_server)
+					502 -> Pair(R.string.error_50x, R.drawable.error_server)
+					503 -> Pair(R.string.error_50x, R.drawable.error_server)
+					504 -> Pair(R.string.error_50x, R.drawable.error_server)
+					else -> Pair(R.string.error_unknown, R.drawable.error_other)
 				}
-				Result(c.errorStream, Error(c.responseCode, string))
+				Result(c.errorStream, Error(c.responseCode, string, image))
 			}
 		} catch (e: IOException) {
-			// todo timeout, no Internet connection
-			Result(null, Error(0, R.string.error_connecting))
+			Result(null, Error(0, R.string.error_connecting, R.drawable.error_server))
 		}
 	}
 }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt
index 0e9e821e8aec10c6e835504ac36019f720980646..eb3ce1061c018ec3040fe7d9b95aaab3d109d4af 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity
 import android.os.Bundle
 import android.util.Log
 import android.view.View
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.view.WindowCompat
 import androidx.recyclerview.widget.LinearLayoutManager
@@ -92,6 +93,7 @@ 			}
 			if (departuresResult.error != null) {
 				Log.e("Departures", "$departuresResult")
 				Log.e("Departures", "$response")
+				showError(departuresResult.error)
 			} else {
 				updateItems((response as DeparturesSuccess))
 			}
@@ -104,12 +106,39 @@ 			DeparturesResponse.unmarshal(stream)
 		}
 	}
 
+	private fun showError(error: Error) {
+		binding.departuresProgress.visibility = View.GONE
+		binding.departuresRecycler.visibility = View.GONE
+		binding.errorImage.visibility = View.VISIBLE
+		binding.errorText.visibility = View.VISIBLE
+
+		binding.errorText.text = getString(error.stringResource)
+		binding.errorImage.setImageDrawable(AppCompatResources.getDrawable(this, error.imageResource))
+	}
+
 	private fun updateItems(response: DeparturesSuccess) {
 		binding.departuresProgress.visibility = View.GONE
-		binding.departuresRecycler.visibility = View.VISIBLE
 		adapter.update(response.departures)
 		binding.collapsingLayout.apply {
 			title = response.stop.name
+		}
+		if (response.departures.isEmpty()) {
+			binding.errorImage.visibility = View.VISIBLE
+			binding.errorText.visibility = View.VISIBLE
+			binding.departuresRecycler.visibility = View.GONE
+
+			binding.errorText.text = getString(R.string.no_departures)
+			binding.errorImage.setImageDrawable(
+				AppCompatResources.getDrawable(
+					this,
+					R.drawable.error_search
+				)
+			)
+		} else {
+			binding.departuresOverlay.visibility = View.GONE
+			binding.errorImage.visibility = View.GONE
+			binding.errorText.visibility = View.GONE
+			binding.departuresRecycler.visibility = View.VISIBLE
 		}
 		// todo alerts
 		// todo stop info




diff --git a/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt
index 3ce675b57f7b328e7c503d4f54ebdd41533c1163..038e11559ff0acdd6eaa2158c1aa1cee2a818636 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt
@@ -9,14 +9,19 @@ import android.location.LocationListener
 import android.location.LocationManager
 import android.net.ConnectivityManager
 import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
 import android.util.Log
 import android.view.View
 import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.view.WindowCompat
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.openlocationcode.OpenLocationCode
 import kotlinx.coroutines.*
 import ml.adamsprogs.bimba.departures.DeparturesActivity
+import kotlinx.coroutines.Runnable
+import ml.adamsprogs.bimba.R
 import ml.adamsprogs.bimba.api.*
 import ml.adamsprogs.bimba.databinding.ActivityResultsBinding
 import java.io.InputStream
@@ -31,6 +36,9 @@ 	private val binding get() = _binding!!
 
 	private lateinit var adapter: BimbaResultsAdapter
 	private lateinit var preferences: SharedPreferences
+
+	private val handler = Handler(Looper.getMainLooper())
+	private var runnable = Runnable {}
 
 	override fun onCreate(savedInstanceState: Bundle?) {
 		super.onCreate(savedInstanceState)
@@ -85,12 +93,17 @@ 		val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
 		locationManager.requestLocationUpdates(
 			LocationManager.GPS_PROVIDER, 5000, 10f, this
 		)
-		// todo(error-handling) timeout
+		handler.removeCallbacks(runnable)
+		runnable = Runnable {
+			showError(Error(0, R.string.error_gps, R.drawable.error_gps))
+		}
+		handler.postDelayed(runnable, 60 * 1000)
 		locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
 			?.let { onLocationChanged(it) }
 	}
 
 	override fun onLocationChanged(location: Location) {
+		handler.removeCallbacks(runnable)
 		val code = OpenLocationCode.encode(location.latitude, location.longitude)
 		val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
 		locationManager.removeUpdates(this)
@@ -121,7 +134,7 @@ 			}
 			if (itemsResult.error != null) {
 				Log.e("Results.query", "$itemsResult")
 				Log.e("Results.query", "$response")
-				// todo(error-handling) show empty state
+				showError(itemsResult.error)
 			} else {
 				updateItems((response as ItemsSuccess).items)
 			}
@@ -140,17 +153,44 @@ 			}
 			if (itemsResult.error != null) {
 				Log.e("Results.location", "$itemsResult")
 				Log.e("Results.location", "$response")
-				// todo(error-handling) show empty state
+				showError(itemsResult.error)
 			} else {
 				updateItems((response as ItemsSuccess).items)
 			}
 		}
 	}
 
+	private fun showError(error: Error) {
+		binding.resultsProgress.visibility = View.GONE
+		binding.resultsRecycler.visibility = View.GONE
+		binding.errorImage.visibility = View.VISIBLE
+		binding.errorText.visibility = View.VISIBLE
+
+		binding.errorText.text = getString(error.stringResource)
+		binding.errorImage.setImageDrawable(AppCompatResources.getDrawable(this, error.imageResource))
+	}
+
 	private fun updateItems(items: List<Item>) {
 		binding.resultsProgress.visibility = View.GONE
-		binding.resultsRecycler.visibility = View.VISIBLE
 		adapter.update(items)
+		if (items.isEmpty()) {
+			binding.errorImage.visibility = View.VISIBLE
+			binding.errorText.visibility = View.VISIBLE
+			binding.resultsRecycler.visibility = View.GONE
+
+			binding.errorText.text = getString(R.string.error_404)
+			binding.errorImage.setImageDrawable(
+				AppCompatResources.getDrawable(
+					this,
+					R.drawable.error_search
+				)
+			)
+		} else {
+			binding.resultsOverlay.visibility = View.GONE
+			binding.errorImage.visibility = View.GONE
+			binding.errorText.visibility = View.GONE
+			binding.resultsRecycler.visibility = View.VISIBLE
+		}
 	}
 
 	private suspend fun unmarshallItemResponse(stream: InputStream): ItemsResponse {




diff --git a/app/src/main/res/drawable/error_app.xml b/app/src/main/res/drawable/error_app.xml
new file mode 100644
index 0000000000000000000000000000000000000000..adaa37eb80f7276f9ac19caec2c1c8c9fca932de
--- /dev/null
+++ b/app/src/main/res/drawable/error_app.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M18,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM15.5,12c0,-1.38 1.12,-2.5 2.5,-2.5 0.42,0 0.8,0.11 1.15,0.29l-3.36,3.36c-0.18,-0.35 -0.29,-0.73 -0.29,-1.15zM18,14.5c-0.42,0 -0.8,-0.11 -1.15,-0.29l3.36,-3.36c0.18,0.35 0.29,0.73 0.29,1.15 0,1.38 -1.12,2.5 -2.5,2.5zM17,18L7,18L7,6h10v1h2L19,3c0,-1.1 -0.9,-2 -2,-2L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v1z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_gps.xml b/app/src/main/res/drawable/error_gps.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d83a38e79abe3672f08f1e811d88e184ddadc1e4
--- /dev/null
+++ b/app/src/main/res/drawable/error_gps.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15L23,13v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zM16.27,17.54C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46,-4.27l9.81,9.81z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_limit.xml b/app/src/main/res/drawable/error_limit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..03344fd1addcf979b8cdc1c4cb33cf699add2401
--- /dev/null
+++ b/app/src/main/res/drawable/error_limit.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M23,5.5V20c0,2.2 -1.8,4 -4,4h-7.3c-1.08,0 -2.1,-0.43 -2.85,-1.19L1,14.83c0,0 1.26,-1.23 1.3,-1.25c0.22,-0.19 0.49,-0.29 0.79,-0.29c0.22,0 0.42,0.06 0.6,0.16C3.73,13.46 8,15.91 8,15.91V4c0,-0.83 0.67,-1.5 1.5,-1.5S11,3.17 11,4v7h1V1.5C12,0.67 12.67,0 13.5,0S15,0.67 15,1.5V11h1V2.5C16,1.67 16.67,1 17.5,1S19,1.67 19,2.5V11h1V5.5C20,4.67 20.67,4 21.5,4S23,4.67 23,5.5z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_net.xml b/app/src/main/res/drawable/error_net.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dde6900202fa85a63b608e96248a012b815f8d1d
--- /dev/null
+++ b/app/src/main/res/drawable/error_net.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_other.xml b/app/src/main/res/drawable/error_other.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a4c7fcdbf80efb3e3fc7daebf5cf6634412ca8b0
--- /dev/null
+++ b/app/src/main/res/drawable/error_other.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" 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-2h2v2zM13,13h-2L11,7h2v6z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_search.xml b/app/src/main/res/drawable/error_search.xml
new file mode 100644
index 0000000000000000000000000000000000000000..66931d11070e03fd2d2f20c26700a0d77a45c17a
--- /dev/null
+++ b/app/src/main/res/drawable/error_search.xml
@@ -0,0 +1,6 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5C16,5.91 13.09,3 9.5,3C6.08,3 3.28,5.64 3.03,9h2.02C5.3,6.75 7.18,5 9.5,5C11.99,5 14,7.01 14,9.5S11.99,14 9.5,14c-0.17,0 -0.33,-0.03 -0.5,-0.05v2.02C9.17,15.99 9.33,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57L14,14.71v0.79l5,4.99L20.49,19L15.5,14z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M6.47,10.82l-2.47,2.47l-2.47,-2.47l-0.71,0.71l2.47,2.47l-2.47,2.47l0.71,0.71l2.47,-2.47l2.47,2.47l0.71,-0.71l-2.47,-2.47l2.47,-2.47z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_sec.xml b/app/src/main/res/drawable/error_sec.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e6842fb025a6ca3102a2f1da225a2e0048ca2f66
--- /dev/null
+++ b/app/src/main/res/drawable/error_sec.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,2L4,5v6.09c0,5.05 3.41,9.76 8,10.91c4.59,-1.15 8,-5.86 8,-10.91V5L12,2zM15.5,14.09l-1.41,1.41L12,13.42L9.91,15.5L8.5,14.09L10.59,12L8.5,9.91L9.91,8.5L12,10.59l2.09,-2.09l1.41,1.41L13.42,12L15.5,14.09z"/>
+</vector>




diff --git a/app/src/main/res/drawable/error_server.xml b/app/src/main/res/drawable/error_server.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6fddd7413b943415429acce7c9d329dd06e2c98b
--- /dev/null
+++ b/app/src/main/res/drawable/error_server.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="?attr/colorOnSurface"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M11,8.17L6.49,3.66C8.07,2.61 9.96,2 12,2c5.52,0 10,4.48 10,10c0,2.04 -0.61,3.93 -1.66,5.51l-1.46,-1.46C19.59,14.87 20,13.48 20,12c0,-3.35 -2.07,-6.22 -5,-7.41V5c0,1.1 -0.9,2 -2,2h-2V8.17zM21.19,21.19l-1.41,1.41l-2.27,-2.27C15.93,21.39 14.04,22 12,22C6.48,22 2,17.52 2,12c0,-2.04 0.61,-3.93 1.66,-5.51L1.39,4.22l1.41,-1.41L21.19,21.19zM11,18c-1.1,0 -2,-0.9 -2,-2v-1l-4.79,-4.79C4.08,10.79 4,11.38 4,12c0,4.08 3.05,7.44 7,7.93V18z"/>
+</vector>




diff --git a/app/src/main/res/layout/activity_departures.xml b/app/src/main/res/layout/activity_departures.xml
index 914ead5d00e7339ebd0f746d5ae2f084e4d9697a..552d23dd735a7ccaefdba0088b845182b3d6fda6 100644
--- a/app/src/main/res/layout/activity_departures.xml
+++ b/app/src/main/res/layout/activity_departures.xml
@@ -1,15 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
 	xmlns:app="http://schemas.android.com/apk/res-auto"
+	xmlns:tools="http://schemas.android.com/tools"
 	android:layout_width="match_parent"
 	android:layout_height="match_parent">
 
 	<androidx.constraintlayout.widget.ConstraintLayout
-		android:id="@+id/departures_progress"
+		android:id="@+id/departures_overlay"
 		android:layout_width="match_parent"
 		android:layout_height="match_parent">
 
 		<ProgressBar
+			android:id="@+id/departures_progress"
 			style="?android:attr/progressBarStyle"
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
@@ -17,6 +19,33 @@ 			app:layout_constraintBottom_toBottomOf="parent"
 			app:layout_constraintEnd_toEndOf="parent"
 			app:layout_constraintStart_toStartOf="parent"
 			app:layout_constraintTop_toTopOf="parent" />
+
+		<ImageView
+			android:id="@+id/errorImage"
+			android:layout_width="92dp"
+			android:layout_height="92dp"
+			android:visibility="gone"
+			app:layout_constraintBottom_toBottomOf="parent"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintStart_toStartOf="parent"
+			app:layout_constraintTop_toTopOf="parent"
+			tools:ignore="ContentDescription"
+			tools:src="@drawable/error_net" />
+
+		<TextView
+			android:id="@+id/errorText"
+			android:layout_width="0dp"
+			android:layout_height="wrap_content"
+			android:layout_marginStart="16dp"
+			android:layout_marginTop="8dp"
+			android:layout_marginEnd="16dp"
+			android:textAlignment="center"
+			android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+			android:visibility="gone"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintStart_toStartOf="parent"
+			app:layout_constraintTop_toBottomOf="@+id/errorImage"
+			tools:text="No connection" />
 	</androidx.constraintlayout.widget.ConstraintLayout>
 
 	<com.google.android.material.appbar.AppBarLayout




diff --git a/app/src/main/res/layout/activity_results.xml b/app/src/main/res/layout/activity_results.xml
index f390da570b37b1b1deb63b036506b34be05d63b6..544c223c2396559b92f1042c1f6832ab9cd69e43 100644
--- a/app/src/main/res/layout/activity_results.xml
+++ b/app/src/main/res/layout/activity_results.xml
@@ -1,15 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
 	xmlns:app="http://schemas.android.com/apk/res-auto"
+	xmlns:tools="http://schemas.android.com/tools"
 	android:layout_width="match_parent"
 	android:layout_height="match_parent">
 
 	<androidx.constraintlayout.widget.ConstraintLayout
-		android:id="@+id/results_progress"
+		android:id="@+id/results_overlay"
 		android:layout_width="match_parent"
 		android:layout_height="match_parent">
 
 		<ProgressBar
+			android:id="@+id/results_progress"
 			style="?android:attr/progressBarStyle"
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
@@ -17,6 +19,34 @@ 			app:layout_constraintBottom_toBottomOf="parent"
 			app:layout_constraintEnd_toEndOf="parent"
 			app:layout_constraintStart_toStartOf="parent"
 			app:layout_constraintTop_toTopOf="parent" />
+
+		<ImageView
+			android:id="@+id/errorImage"
+			android:layout_width="92dp"
+			android:layout_height="92dp"
+			android:visibility="gone"
+			app:layout_constraintBottom_toBottomOf="parent"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintStart_toStartOf="parent"
+			app:layout_constraintTop_toTopOf="parent"
+			tools:ignore="ContentDescription"
+			tools:src="@drawable/error_net" />
+
+		<TextView
+			android:id="@+id/errorText"
+			android:layout_width="0dp"
+			android:layout_height="wrap_content"
+			android:layout_marginStart="16dp"
+			android:layout_marginTop="8dp"
+			android:layout_marginEnd="16dp"
+			android:textAlignment="center"
+			android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+			android:visibility="gone"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintStart_toStartOf="parent"
+			app:layout_constraintTop_toBottomOf="@+id/errorImage"
+			tools:text="No connection" />
+
 	</androidx.constraintlayout.widget.ConstraintLayout>
 
 	<com.google.android.material.appbar.AppBarLayout




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 906554c8b1e3a54af2cfe5069b4641644dff11d8..51b4adeb82ec6ebadf9d62aada03881473d907d9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -20,4 +20,6 @@ 	There was an error on the sever. Try again later
 	<string name="error_unknown">Unknown error happened</string>
 	<string name="error_connecting">Error connecting to the server. Try again later</string>
 	<string name="error_offline">You are offline. Connect to the Internet</string> <!-- send a bug report to bimba@git.apiote.xyz, details are: url=$URL, response=$response -->
+	<string name="error_gps">Cannot obtain location</string>
+	<string name="no_departures">No departures</string>
 </resources>
\ No newline at end of file