Bimba.git

commit 7319a65e120eb9b7a612f2822a7eedb8f1b2450f

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

Merge branch 'develop' into translations

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


diff --git a/.gitignore b/.gitignore
index 73d061c3eff9885fd9b5f102f37943513d0b118f..51ff5c25c1cacc26bc5d708f8334716cf8fb77a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,3 +57,4 @@ converter/local/*.db
 converter/local/metadata.yml
 converter/local/__pycache__
 converter/local/config.py
+.kotlin




diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index eaf4987492797d556a095a211eee2fbebc873db6..752ca17fa23bfb6f4fe112ab5b255bf899742ada 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -18,6 +18,16 @@ == [3.6.1] – 2024-09-04
 
 === Fixed
 
+* phantom feed names in search results
+
+=== Added
+
+* stops and departures from Transitous
+
+== [3.6.1] – 2024-09-04
+
+=== Fixed
+
 * add empty default cache credentials
 
 == [3.6] – 2024-08-30




diff --git a/README.adoc b/README.adoc
index b484f6d8db8a2df8d8fad83be524e87818df188e..9fd6689891dbe5915ae419d699d378944a4df0c4 100644
--- a/README.adoc
+++ b/README.adoc
@@ -4,7 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later
 
 = Bimba
 Adam Evyčędo <me@apiote.xyz>
-v3.6.1 2024-09-04
+v3.7.1 2024-11-30
 :toc:
 
 Bimba is a FLOSS public transport passenger companion; a timetable in your pocket.




diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 14ae30d07aba7f11774548ce2cf91f69b4d641db..fa1aa641706e77412d05ae850be974e2a8165252 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -11,19 +11,19 @@ }
 
 android {
 	namespace = "xyz.apiote.bimba.czwek"
-	compileSdk = 34  // https://gitlab.com/fdroid/fdroiddata/-/issues/3299#note_1989808414
-	buildToolsVersion =
-		"34.0.0"  // https://gitlab.com/fdroid/fdroiddata/-/issues/3299#note_1989808414
+	// NOTE apksigner with `--alignment-preserved` https://gitlab.com/fdroid/fdroiddata/-/issues/3299#note_1989808414
+	compileSdk = 35
+	buildToolsVersion = "35.0.0"
 
 	defaultConfig {
 		applicationId = "xyz.apiote.bimba.czwek"
 		minSdk = 21
 		targetSdk = 35
-		versionCode = 31
-		versionName = "3.6.1"
+		versionCode = 33
+		versionName = "3.7.1"
 
 		testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-		resourceConfigurations += listOf("en", "pl", "it", "de", "fr", "en-rUS")
+		resourceConfigurations += listOf("en", "de", "en-rGB", "en-rUS", "et", "fr", "it", "pl")
 	}
 
 	buildTypes {
@@ -50,26 +50,26 @@ 	}
 }
 
 dependencies {
-	implementation("androidx.core:core-ktx:1.13.1")
+	implementation("androidx.core:core-ktx:1.15.0")
 	implementation("androidx.appcompat:appcompat:1.7.0")
 	implementation("com.google.android.material:material:1.12.0")
-	implementation("androidx.constraintlayout:constraintlayout:2.1.4")
-	implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.6")
-	implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6")
-	implementation("androidx.navigation:navigation-fragment-ktx:2.8.2")
-	implementation("androidx.navigation:navigation-ui-ktx:2.8.2")
+	implementation("androidx.constraintlayout:constraintlayout:2.2.0")
+	implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.7")
+	implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
+	implementation("androidx.navigation:navigation-fragment-ktx:2.8.4")
+	implementation("androidx.navigation:navigation-ui-ktx:2.8.4")
 	implementation("androidx.legacy:legacy-support-v4:1.0.0")
 	implementation("androidx.core:core-splashscreen:1.0.1")
 	implementation("com.google.openlocationcode:openlocationcode:1.0.4")
 	implementation("org.osmdroid:osmdroid-android:6.1.20")
 	implementation("org.yaml:snakeyaml:2.3")
-	implementation("androidx.activity:activity-ktx:1.9.2")
+	implementation("androidx.activity:activity-ktx:1.9.3")
 	implementation("com.otaliastudios:zoomlayout:1.9.0")
 	implementation("dev.bandb.graphview:graphview:0.8.1")
 	implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3")
 	implementation("com.github.jershell:kbson:0.5.0")
 	implementation("androidx.preference:preference-ktx:1.2.1")
-	implementation("androidx.work:work-runtime-ktx:2.9.1")
+	implementation("androidx.work:work-runtime-ktx:2.10.0")
 	implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.10.0")
 	implementation("commons-io:commons-io:2.17.0")
 	implementation("com.google.guava:guava:33.3.1-android")
@@ -77,7 +77,7 @@ 	implementation(project(":fruchtfleisch"))
 	implementation("ch.acra:acra-http:5.11.4")
 	implementation("ch.acra:acra-notification:5.11.4")
 
-	coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.2")
+	coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.3")
 
 	testImplementation("junit:junit:4.13.2")
 	androidTestImplementation("androidx.test.ext:junit:1.2.1")




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousDepartures.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousDepartures.kt
index 66fa6ae249f756a186b7405f5794b29726efd1db..3c52446a49d0db59620b89fa7ac64a29bd774cf5 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousDepartures.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/transitousDepartures.kt
@@ -12,16 +12,15 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.api.responses.ErrorResponse
-import xyz.apiote.bimba.czwek.repo.Alert
 import xyz.apiote.bimba.czwek.repo.Colour
 import xyz.apiote.bimba.czwek.repo.CongestionLevel
-import xyz.apiote.bimba.czwek.repo.Departure
+import xyz.apiote.bimba.czwek.repo.Event
 import xyz.apiote.bimba.czwek.repo.LineStub
 import xyz.apiote.bimba.czwek.repo.LineType
 import xyz.apiote.bimba.czwek.repo.OccupancyStatus
 import xyz.apiote.bimba.czwek.repo.Position
 import xyz.apiote.bimba.czwek.repo.Stop
-import xyz.apiote.bimba.czwek.repo.StopDepartures
+import xyz.apiote.bimba.czwek.repo.StopEvents
 import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import xyz.apiote.bimba.czwek.repo.Vehicle
 import xyz.apiote.bimba.czwek.units.Mps
@@ -37,7 +36,7 @@ 	context: Context,
 	stop: String,
 	date: LocalDate?,
 	limit: Int?
-): StopDepartures {
+): StopEvents {
 	if (!isNetworkAvailable(context)) {
 		throw TrafficResponseException(0, "", Error(0, R.string.error_offline, R.drawable.error_net))
 	}
@@ -75,7 +74,7 @@ 			throw TrafficResponseException(result.error.statusCode, "", result.error)
 		}
 	} else {
 		return withContext(Dispatchers.IO) {
-			val departures = mutableListOf<Departure>()
+			val events = mutableListOf<Event>()
 			var stopID = ""
 			var stopName = ""
 			var latitude = 0.0
@@ -190,10 +189,17 @@ 										ZonedDateTime.ofInstant(
 											Instant.ofEpochSecond(eventTimestamp),
 											ZoneId.systemDefault()
 										)
-									departures.add(
-										Departure(
-											ID = hash,
-											time = Time(
+									events.add(
+										Event(
+											id = hash,
+											arrivalTime = Time(
+												t.hour.toUInt(),
+												t.minute.toUInt(),
+												t.second.toUInt(),
+												(t.dayOfYear - ZonedDateTime.now().dayOfYear).toByte(),
+												ZoneId.systemDefault().id
+											),
+											departureTime = Time(
 												t.hour.toUInt(),
 												t.minute.toUInt(),
 												t.second.toUInt(),
@@ -247,8 +253,8 @@ 					}
 				}
 			}
 
-			return@withContext StopDepartures(
-				departures,
+			return@withContext StopEvents(
+				events,
 				Stop(stopID, stopName, stopName, "", "transitous", Position(latitude, longitude), listOf()),
 				listOf()
 			)




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 2b1d50a476c8181b6758489d60b61fe772080d09..11c0a85d49513a4f0720a5ca7a1854bc429b73c9 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
@@ -14,7 +14,7 @@ import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.RecyclerView
 import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.departures.DeparturesActivity
-import xyz.apiote.bimba.czwek.repo.Departure
+import xyz.apiote.bimba.czwek.repo.Event
 import xyz.apiote.bimba.czwek.repo.Favourite
 import java.time.ZoneId
 import java.time.ZonedDateTime
@@ -23,7 +23,7 @@ import java.util.Optional
 
 class BimbaFavouritesAdapter(
 	private var favourites: List<Favourite>,
-	private var departures: Map<String, Optional<Departure>>,
+	private var departures: Map<String, Optional<Event>>,
 	private val inflater: LayoutInflater,
 	private val context: Context
 ) :
@@ -34,9 +34,9 @@ 		private set
 
 	inner class DiffUtilCallback(
 		private val oldFavourites: List<Favourite>,
-		private val oldDepartures: Map<String, Optional<Departure>?>,
+		private val oldDepartures: Map<String, Optional<Event>?>,
 		private val newFavourites: List<Favourite>,
-		private val newDepartures: Map<String, Optional<Departure>?>
+		private val newDepartures: Map<String, Optional<Event>?>
 	) : DiffUtil.Callback() {
 		override fun getOldListSize() = oldFavourites.size
 
@@ -57,9 +57,9 @@ 				return false
 			}
 
 			val favouritesSame = oldFav.feedName == newFav.feedName &&
-				oldFav.stopName == newFav.stopName &&
-				oldFav.sequence == newFav.sequence &&
-				oldFav.lines == newFav.lines
+					oldFav.stopName == newFav.stopName &&
+					oldFav.sequence == newFav.sequence &&
+					oldFav.lines == newFav.lines
 
 			if (!favouritesSame) {
 				return false
@@ -73,14 +73,14 @@ 			if ((oldDeparture!!.isEmpty && !newDeparture!!.isEmpty) || (!oldDeparture!!.isEmpty && newDeparture!!.isEmpty)) {
 				return false
 			}
 
-			return oldDeparture!!.get().ID == newDeparture!!.get().ID &&
-				oldDeparture!!.get().vehicle.Line == newDeparture!!.get().vehicle.Line &&
-				oldDeparture!!.get().vehicle.Headsign == newDeparture!!.get().vehicle.Headsign &&
-				oldDeparture!!.get().statusText(
-					context,
-					false,
-					lastUpdate
-				) == newDeparture!!.get().statusText(context, false)
+			return oldDeparture!!.get().id == newDeparture!!.get().id &&
+					oldDeparture!!.get().vehicle.Line == newDeparture!!.get().vehicle.Line &&
+					oldDeparture!!.get().vehicle.Headsign == newDeparture!!.get().vehicle.Headsign &&
+					oldDeparture!!.get().statusText(
+						context,
+						false,
+						lastUpdate
+					) == newDeparture!!.get().statusText(context, false)
 		}
 	}
 
@@ -111,7 +111,7 @@ 		this.favourites = favourites
 		diff.dispatchUpdatesTo(this)
 	}
 
-	fun updateDepartures(departures: Map<String, Optional<Departure>>) {
+	fun updateDepartures(departures: Map<String, Optional<Event>>) {
 		this.departures = departures
 		notifyDataSetChanged()
 		lastUpdate = ZonedDateTime.now()
@@ -151,6 +151,10 @@ class FavouriteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
 	val root: View = itemView.findViewById(R.id.favourite)
 	val feedName: TextView = itemView.findViewById(R.id.feed_name)
 	val lineIcon: ImageView = itemView.findViewById(R.id.line_icon)
+	val arrivalStatus: TextView = itemView.findViewById(R.id.arrival_status)
+	val arrivalTime: TextView = itemView.findViewById(R.id.arrival_time)
+	val arrivalTimeFull: TextView = itemView.findViewById(R.id.arrival_full_time)
+	val departureStatus: TextView = itemView.findViewById(R.id.departure_status)
 	val departureTime: TextView = itemView.findViewById(R.id.departure_time)
 	val departureTimeFull: TextView = itemView.findViewById(R.id.departure_full_time)
 	val lineName: TextView = itemView.findViewById(R.id.departure_line)
@@ -162,33 +166,32 @@ 		fun bind(
 			favourite: Favourite,
 			holder: FavouriteViewHolder,
 			context: Context,
-			departure: Optional<Departure>?
+			event: Optional<Event>?
 		) {
-			if (departure == null) {
+			if (event == null) {
 				holder.feedName.text = favourite.feedName
 				holder.stopHeadline.text = favourite.stopName
 				holder.lineIcon.setImageDrawable(null)
 				holder.lineName.text = context.getString(R.string.loading)
-				holder.departureTime.text = ""
-				holder.departureTimeFull.text = ""
+				holder.arrivalTime.text = ""
+				holder.arrivalTimeFull.text = ""
 				holder.headsign.text = ""
-			} else if (departure.isEmpty) {
+			} else if (event.isEmpty) {
 				holder.feedName.text = favourite.feedName
 				holder.stopHeadline.text = favourite.stopName
 				holder.lineIcon.setImageDrawable(null)
 				holder.lineName.text = context.getString(R.string.no_departures).lowercase()
-				holder.departureTime.text = ""
-				holder.departureTimeFull.text = ""
+				holder.arrivalTime.text = ""
+				holder.arrivalTimeFull.text = ""
 				holder.headsign.text = ""
 			} else {
-				val vehicle = departure.get().vehicle
+				val statusTexts = event.get().statusText(context, false)
+				val vehicle = event.get().vehicle
 				holder.feedName.text = favourite.feedName
 				holder.stopHeadline.text = favourite.stopName
 				holder.lineIcon.setImageDrawable(vehicle.Line.icon(context))
 				holder.lineIcon.contentDescription = vehicle.Line.kind.name
 				holder.lineName.text = vehicle.Line.name
-				holder.departureTime.text = departure.get().statusText(context, false)
-				holder.departureTimeFull.text = departure.get().timeString(context)
 				holder.headsign.text =
 					context.getString(R.string.departure_headsign, vehicle.Headsign)
 				holder.headsign.contentDescription =
@@ -196,10 +199,87 @@ 					context.getString(
 						R.string.departure_headsign_content_description,
 						vehicle.Headsign
 					)
+
+				with(event.get()) {
+					if (arrivalTime == departureTime) {
+						holder.arrivalStatus.visibility = View.GONE
+						holder.arrivalTime.visibility = View.GONE
+						holder.arrivalTimeFull.visibility = View.GONE
+						holder.departureTime.apply {
+							text = statusTexts.second
+							visibility = View.VISIBLE
+						}
+						holder.departureTimeFull.apply {
+							text = departureTimeString(context)
+							visibility = View.VISIBLE
+						}
+						if (!exact) {
+							holder.departureStatus.apply {
+								text = context.getString(R.string.approximately)
+								visibility = View.VISIBLE
+							}
+						} else {
+							holder.departureStatus.visibility = View.GONE
+						}
+					} else {
+						if (arrivalTime != null) {
+							holder.arrivalTimeFull.apply {
+								visibility = View.VISIBLE
+								text = arrivalTimeString(context)
+							}
+							holder.arrivalTime.apply {
+								visibility = View.VISIBLE
+								text = statusTexts.first
+							}
+							holder.arrivalStatus.apply {
+								visibility = View.VISIBLE
+								text = if (!exact) {
+									context.getString(R.string.arrival_approximate)
+								} else {
+									context.getString(R.string.arrival)
+								}
+							}
+						} else {
+							holder.arrivalStatus.visibility = View.GONE
+							holder.arrivalTime.visibility = View.GONE
+							holder.arrivalTimeFull.visibility = View.GONE
+						}
+						if (departureTime != null) {
+							holder.departureTimeFull.apply {
+								visibility = View.VISIBLE
+								text = departureTimeString(context)
+							}
+							holder.departureTime.apply {
+								visibility = View.VISIBLE
+								text = statusTexts.second
+							}
+							holder.departureStatus.apply {
+								visibility = View.VISIBLE
+								text = if (!exact) {
+									context.getString(R.string.departure_approximate)
+								} else {
+									context.getString(R.string.departure)
+								}
+							}
+						} else {
+							holder.departureStatus.visibility = View.GONE
+							holder.departureTime.visibility = View.GONE
+							holder.departureTimeFull.visibility = View.GONE
+						}
+					}
+				}
 			}
 
 			holder.root.setOnClickListener {
-				context.startActivity(DeparturesActivity.getIntent(context, favourite.stopCode, favourite.stopName, favourite.feedID, favourite.lines.toTypedArray()))
+				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/home/HomeViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt
index ba6da66f9308e7dc91bd81aca46cff37c3dffee1..def65cf9bf6e3e4d1f37c37e65d5121123a5824d 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt
@@ -18,7 +18,7 @@ import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
-import xyz.apiote.bimba.czwek.repo.Departure
+import xyz.apiote.bimba.czwek.repo.Event
 import xyz.apiote.bimba.czwek.repo.Favourite
 import xyz.apiote.bimba.czwek.repo.FeedInfo
 import xyz.apiote.bimba.czwek.repo.OfflineRepository
@@ -36,8 +36,8 @@ 	var feeds: Map? = null
 	var feedsSettings: FeedsSettings? = null
 	private val mutableFavourites = MutableLiveData<List<Favourite>>()
 	val favourites: LiveData<List<Favourite>> = mutableFavourites
-	private val mutableDepartures = MutableLiveData<Map<String, Optional<Departure>>>()
-	val departures: LiveData<Map<String, Optional<Departure>>> = mutableDepartures
+	private val mutableDepartures = MutableLiveData<Map<String, Optional<Event>>>()
+	val departures: LiveData<Map<String, Optional<Event>>> = mutableDepartures
 
 	fun getQueryables(query: String, context: Context) {
 		viewModelScope.launch {
@@ -89,10 +89,10 @@ 								context,
 								12  // XXX heuristics
 							)
 							stopDepartures?.let { sDs ->
-								if (sDs.departures.isEmpty()) {
+								if (sDs.events.isEmpty()) {
 									Pair(favourite.feedID+favourite.stopCode, Optional.empty())
 								} else {
-									Pair(favourite.feedID+favourite.stopCode, Optional.ofNullable(sDs.departures.find { departure ->
+									Pair(favourite.feedID+favourite.stopCode, Optional.ofNullable(sDs.events.find { departure ->
 											favourite.lines.isEmpty() or favourite.lines.contains(
 												departure.vehicle.Line.name
 											)




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 6aa86b0cb2362798e410558411e5229ae8ba873e..a9ff0719d69c398d56d1f3e06ad2aa3cc8072187 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
@@ -73,7 +73,7 @@ 					vehicle.Headsign
 				)
 			}
 
-			content.findViewById<TextView>(R.id.time).visibility = View.GONE
+			content.findViewById<TextView>(R.id.arrival_time).visibility = View.GONE
 			content.findViewById<TextView>(R.id.local_time).visibility = View.GONE
 
 			content.findViewById<MapView>(R.id.map).visibility = View.GONE




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 17c08de21676990de2632a77c932c099454c4626..01f592aa9c53a29de8ef478f6d1ff9f504d97335 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
@@ -34,13 +34,12 @@ import org.osmdroid.views.MapView
 import org.osmdroid.views.overlay.Marker
 import org.osmdroid.views.overlay.TilesOverlay
 import org.osmdroid.views.overlay.gestures.RotationGestureOverlay
-import org.w3c.dom.Text
 import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.dpToPixelI
 import xyz.apiote.bimba.czwek.repo.Alert
 import xyz.apiote.bimba.czwek.repo.CongestionLevel
-import xyz.apiote.bimba.czwek.repo.Departure
-import xyz.apiote.bimba.czwek.repo.DepartureItem
+import xyz.apiote.bimba.czwek.repo.Event
+import xyz.apiote.bimba.czwek.repo.EventItem
 import xyz.apiote.bimba.czwek.repo.OccupancyStatus
 import xyz.apiote.bimba.czwek.repo.Vehicle
 import xyz.apiote.bimba.czwek.units.UnitSystem
@@ -50,75 +49,118 @@
 class BimbaDepartureViewHolder(itemView: View) : ViewHolder(itemView) {
 	val root: View = itemView.findViewById(R.id.departure)
 	val lineIcon: ImageView = itemView.findViewById(R.id.line_icon)
+	val arrivalTime: TextView = itemView.findViewById(R.id.arrival_time)
+	val arrivalStatus: TextView = itemView.findViewById(R.id.arrival_status)
 	val departureTime: TextView = itemView.findViewById(R.id.departure_time)
+	val departureStatus: TextView = itemView.findViewById(R.id.departure_status)
 	val lineName: TextView = itemView.findViewById(R.id.departure_line)
 	val headsign: TextView = itemView.findViewById(R.id.departure_headsign)
-	val timeStatus: ImageView = itemView.findViewById(R.id.time_status)
+	val eventStatus: ImageView = itemView.findViewById(R.id.event_status)
 
 	companion object {
 		fun bind(
-			departure: Departure,
+			event: Event,
 			holder: BimbaDepartureViewHolder?,
 			context: Context?,
 			showAsTime: Boolean,
-			onClickListener: (Departure) -> Unit,
+			onClickListener: (Event) -> Unit,
 			showingTerminusArrivals: String
 		) {
 			holder?.root?.setOnClickListener {
-				onClickListener(departure)
+				onClickListener(event)
 			}
-			holder?.lineIcon?.setImageDrawable(departure.vehicle.Line.icon(context!!))
-			holder?.lineIcon?.contentDescription = departure.vehicle.Line.kind.name
-			holder?.lineName?.text = departure.vehicle.Line.name
+			holder?.lineIcon?.setImageDrawable(event.vehicle.Line.icon(context!!))
+			holder?.lineIcon?.contentDescription = event.vehicle.Line.kind.name
+			holder?.lineName?.text = event.vehicle.Line.name
 			holder?.headsign?.text =
-				context?.getString(R.string.departure_headsign, departure.vehicle.Headsign)
+				context?.getString(R.string.departure_headsign, event.vehicle.Headsign)
 			holder?.headsign?.contentDescription =
 				context?.getString(
 					R.string.departure_headsign_content_description,
-					departure.vehicle.Headsign
+					event.vehicle.Headsign
 				)
 
 			when {
-				departure.isRealtime -> {
-					holder?.timeStatus?.setImageResource(R.drawable.radar)
-					holder?.timeStatus?.contentDescription =
-						context?.getString(R.string.realtime_content_description)
-					holder?.timeStatus?.let {
+				event.isRealtime -> {
+					holder?.eventStatus?.let {
+						it.contentDescription =
+							context?.getString(R.string.realtime_content_description)
+						it.setImageResource(R.drawable.radar)
 						TooltipCompat.setTooltipText(
 							it,
 							context?.getString(R.string.realtime_content_description)
 						)
+						// TODO all at the same time with setCurrentFraction based on wall clock
+						/*ObjectAnimator.ofPropertyValuesHolder(it, PropertyValuesHolder.ofFloat("alpha", 0.0f))
+							.apply {
+								setDuration(1000)
+								repeatCount = ObjectAnimator.INFINITE
+								repeatMode = ObjectAnimator.REVERSE
+							}
+							.start()*/
 					}
 				}
 
-				departure.exact -> {
-					holder?.timeStatus?.setImageResource(R.drawable.calendar)
-					holder?.timeStatus?.contentDescription =
-						context?.getString(R.string.exact_content_description)
-					holder?.timeStatus?.let {
+				event.exact -> {
+					// FIXME clear animation
+					holder?.eventStatus?.let {
+						it.setImageResource(R.drawable.calendar)
+						it.contentDescription =
+							context?.getString(R.string.schedule_content_description)
 						TooltipCompat.setTooltipText(
 							it,
-							context?.getString(R.string.exact_content_description)
+							context?.getString(R.string.schedule_content_description)
 						)
 					}
 				}
+			}
 
-				else -> {
-					holder?.timeStatus?.setImageResource(R.drawable.inexact)
-					holder?.timeStatus?.contentDescription =
-						context?.getString(R.string.inexact_content_description)
-					holder?.timeStatus?.let {
-						TooltipCompat.setTooltipText(
-							it,
-							context?.getString(R.string.inexact_content_description)
-						)
+			val statusTexts = event.statusText(context, showAsTime)
+			if (event.arrivalTime == event.departureTime) {
+				if (!event.exact) {
+					holder?.arrivalStatus?.apply{
+						visibility = View.VISIBLE
+						text = context?.getString(R.string.approximately)
+					}
+				} else {
+					holder?.arrivalStatus?.visibility = View.INVISIBLE
+				}
+				holder?.arrivalTime?.apply{
+					text = statusTexts.second
+					visibility = View.VISIBLE
+				}
+				holder?.departureStatus?.visibility = View.GONE
+				holder?.departureTime?.visibility = View.GONE
+			} else {
+				if (statusTexts.first != null) {
+					holder?.arrivalTime?.visibility = View.VISIBLE
+					holder?.arrivalTime?.text = statusTexts.first
+					holder?.arrivalStatus?.visibility = View.VISIBLE
+					holder?.arrivalStatus?.text = if (!event.exact) {
+						context?.getString(R.string.arrival_approximate)
+					} else {
+						context?.getString(R.string.arrival)
 					}
+				} else {
+					holder?.arrivalTime?.visibility = View.GONE
+					holder?.arrivalStatus?.visibility = View.GONE
+				}
+				if (statusTexts.second != null) {
+					holder?.departureTime?.visibility = View.VISIBLE
+					holder?.departureTime?.text = statusTexts.second
+					holder?.departureStatus?.visibility = View.VISIBLE
+					holder?.departureStatus?.text = if (!event.exact) {
+						context?.getString(R.string.departure_approximate)
+					} else {
+						context?.getString(R.string.departure)
+					}
+				} else {
+					holder?.departureTime?.visibility = View.GONE
+					holder?.departureStatus?.visibility = View.GONE
 				}
 			}
-
-			holder?.departureTime?.text = departure.statusText(context, showAsTime)
 			holder?.root?.alpha =
-				if (departure.terminusArrival && showingTerminusArrivals == BimbaDeparturesAdapter.TERMINUS_ARRIVAL_GREY_OUT) {
+				if (event.terminusArrival && showingTerminusArrivals == BimbaDeparturesAdapter.TERMINUS_ARRIVAL_GREY_OUT) {
 					.5f
 				} else {
 					1f
@@ -160,8 +202,8 @@
 class BimbaDeparturesAdapter(
 	private val inflater: LayoutInflater,
 	private val context: Context?,
-	private var items: List<DepartureItem>,
-	private val onClickListener: ((Departure) -> Unit),
+	private var items: List<EventItem>,
+	private val onClickListener: ((Event) -> Unit),
 ) :
 	RecyclerView.Adapter<ViewHolder>() {
 
@@ -188,8 +230,8 @@ 		private set
 	private var showAsTime: Boolean = false
 
 	inner class DiffUtilCallback(
-		private val oldDepartures: List<DepartureItem>,
-		private val newDepartures: List<DepartureItem>,
+		private val oldDepartures: List<EventItem>,
+		private val newDepartures: List<EventItem>,
 		private val showAsTimeChanged: Boolean,
 	) : DiffUtil.Callback() {
 		override fun getOldListSize() = oldDepartures.size
@@ -197,23 +239,23 @@
 		override fun getNewListSize() = newDepartures.size
 
 		override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
-			(oldDepartures[oldItemPosition].departure?.ID
-				?: ALERT_ITEM_ID) == (newDepartures[newItemPosition].departure?.ID ?: ALERT_ITEM_ID)
+			(oldDepartures[oldItemPosition].event?.id
+				?: ALERT_ITEM_ID) == (newDepartures[newItemPosition].event?.id ?: ALERT_ITEM_ID)
 
 		override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
 			val oldDeparture = oldDepartures[oldItemPosition]
 			val newDeparture = newDepartures[newItemPosition]
-			return if (oldDeparture.departure != null && newDeparture.departure != null) {
-				!oldDeparture.departure.terminusArrival &&
-						oldDeparture.departure.terminusArrival == newDeparture.departure.terminusArrival &&
-						oldDeparture.departure.exact == newDeparture.departure.exact &&
-						oldDeparture.departure.vehicle.Line == newDeparture.departure.vehicle.Line &&
-						oldDeparture.departure.vehicle.Headsign == newDeparture.departure.vehicle.Headsign &&
-						oldDeparture.departure.statusText(
+			return if (oldDeparture.event != null && newDeparture.event != null) {
+				!oldDeparture.event.terminusArrival &&
+						oldDeparture.event.terminusArrival == newDeparture.event.terminusArrival &&
+						oldDeparture.event.exact == newDeparture.event.exact &&
+						oldDeparture.event.vehicle.Line == newDeparture.event.vehicle.Line &&
+						oldDeparture.event.vehicle.Headsign == newDeparture.event.vehicle.Headsign &&
+						oldDeparture.event.statusText(
 							context,
 							false,
 							lastUpdate
-						) == newDeparture.departure.statusText(context, false) && !showAsTimeChanged
+						) == newDeparture.event.statusText(context, false) && !showAsTimeChanged
 			} else if (oldDeparture.alert.isNotEmpty() && newDeparture.alert.isEmpty()) {
 				oldDeparture.alert == newDeparture.alert
 			} else {
@@ -226,12 +268,12 @@ 	private var departuresPositions: MutableMap = HashMap()
 
 	init {
 		items.forEachIndexed { i, departure ->
-			departuresPositions[departure.departure?.ID ?: ALERT_ITEM_ID] = i
+			departuresPositions[departure.event?.id ?: ALERT_ITEM_ID] = i
 		}
 	}
 
 	override fun getItemViewType(position: Int): Int {
-		return if (items[position].departure != null) {
+		return if (items[position].event != null) {
 			0
 		} else {
 			1
@@ -251,7 +293,7 @@
 	override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 		if (holder is BimbaDepartureViewHolder) {
 			BimbaDepartureViewHolder.bind(
-				items[position].departure!!,
+				items[position].event!!,
 				holder,
 				context,
 				showAsTime,
@@ -265,7 +307,7 @@ 	}
 
 	override fun getItemCount(): Int = items.size
 
-	fun get(id: String): DepartureItem? {
+	fun get(id: String): EventItem? {
 		val position = departuresPositions[id]
 		return if (position == null) {
 			null
@@ -275,7 +317,7 @@ 		}
 	}
 
 	fun update(
-		departures: List<DepartureItem>,
+		departures: List<EventItem>,
 		showAsTime: Boolean,
 		areNewObserved: Boolean = false,
 		leaveAlert: Boolean = false
@@ -287,7 +329,7 @@ 			departures
 		}
 		val newPositions: MutableMap<String, Int> = HashMap()
 		newDepartures.forEachIndexed { i, departure ->
-			newPositions[departure.departure?.ID ?: ALERT_ITEM_ID] = i
+			newPositions[departure.event?.id ?: ALERT_ITEM_ID] = i
 		}
 		val diff = DiffUtil.calculateDiff(
 			DiffUtilCallback(
@@ -312,7 +354,7 @@ 		update(this.items, showAsTime)
 	}
 }
 
-class DepartureBottomSheet(private var departure: Departure) : BottomSheetDialogFragment() {
+class DepartureBottomSheet(private var event: Event) : BottomSheetDialogFragment() {
 	companion object {
 		const val TAG = "DepartureBottomSheet"
 	}
@@ -329,33 +371,69 @@ 		cancelCallback?.let { it() }
 	}
 
 	fun departureID(): String {
-		return departure.ID
+		return event.id
 	}
 
-	fun update(departure: Departure) {
-		this.departure = departure
+	fun update(event: Event) {
+		this.event = event
 		view?.let { context?.let { ctx -> setContent(it, ctx, true) } }
 	}
 
 	private fun setContent(view: View, ctx: Context, updating: Boolean = false) {
 		view.apply {
-			findViewById<TextView>(R.id.time).text = departure.timeString(ctx)
+			if (event.arrivalTime == event.departureTime) {
+				if (!event.exact) {
+					findViewById<TextView>(R.id.arrival_status).apply {
+						visibility = View.VISIBLE
+						text = context.getString(R.string.approximately)
+					}
+				} else {
+					findViewById<TextView>(R.id.arrival_status).visibility = View.GONE
+				}
+				findViewById<TextView>(R.id.arrival_time).apply {
+					text = event.arrivalTimeString(ctx)
+					visibility = View.VISIBLE
+				}
+				findViewById<TextView>(R.id.departure_status).visibility = View.GONE
+				findViewById<TextView>(R.id.departure_time).visibility = View.GONE
+			} else {
+				if (event.arrivalTime != null) {
+					findViewById<TextView>(R.id.arrival_time).visibility = View.VISIBLE
+					findViewById<TextView>(R.id.arrival_time).text = event.arrivalTimeString(ctx)
+					findViewById<TextView>(R.id.arrival_status).visibility = View.VISIBLE
+					findViewById<TextView>(R.id.arrival_status).text = if (!event.exact) {
+						context?.getString(R.string.arrival_approximate)
+					} else {
+						context?.getString(R.string.arrival)
+					}
+				}
+				if (event.departureTime != null) {
+					findViewById<TextView>(R.id.departure_time).visibility = View.VISIBLE
+					findViewById<TextView>(R.id.departure_time).text = event.departureTimeString(ctx)
+					findViewById<TextView>(R.id.departure_status).visibility = View.VISIBLE
+					findViewById<TextView>(R.id.departure_status).text = if (!event.exact) {
+						context?.getString(R.string.departure_approximate)
+					} else {
+						context?.getString(R.string.departure)
+					}
+				}
+			}
 			findViewById<TextView>(R.id.local_time).visibility =
-				if (departure.time.Zone == ZoneId.systemDefault().id) {
+				if (event.timeZone() == ZoneId.systemDefault().id) {
 					View.GONE
 				} else {
 					View.VISIBLE
 				}
 
 			findViewById<ImageView>(R.id.rt_icon).apply {
-				visibility = if (departure.isRealtime) {
+				visibility = if (event.isRealtime) {
 					View.VISIBLE
 				} else {
 					View.GONE
 				}
 			}
 			findViewById<ImageView>(R.id.wheelchair_icon).apply {
-				visibility = if (departure.vehicle.let {
+				visibility = if (event.vehicle.let {
 						it.getCapability(Vehicle.Capability.LOW_FLOOR) || it.getCapability(Vehicle.Capability.LOW_ENTRY) || it.getCapability(
 							Vehicle.Capability.RAMP
 						)
@@ -369,41 +447,41 @@
 			findViewById<TextView>(R.id.line).apply {
 				contentDescription = getString(
 					R.string.vehicle_headsign_content_description,
-					departure.vehicle.Line.name,
-					departure.vehicle.Headsign
+					event.vehicle.Line.name,
+					event.vehicle.Headsign
 				)
 				text = getString(
 					R.string.vehicle_headsign,
-					departure.vehicle.Line.name,
-					departure.vehicle.Headsign
+					event.vehicle.Line.name,
+					event.vehicle.Headsign
 				)
 			}
 
 
-			departure.boardingText(ctx).let {
+			event.boardingText(ctx).let {
 				findViewById<TextView>(R.id.boarding_text).text = it
-					findViewById<ImageView>(R.id.boarding_icon).visibility = if (it == "") {
-						View.GONE
-					} else {
-						View.VISIBLE
-					}
+				findViewById<ImageView>(R.id.boarding_icon).visibility = if (it == "") {
+					View.GONE
+				} else {
+					View.VISIBLE
+				}
 			}
 			UnitSystem.getSelected(requireContext()).let { us ->
 				findViewById<TextView>(R.id.speed_text).apply {
 					text =
-						us.toString(context, us.speedUnit(departure.vehicle.Speed))
+						us.toString(context, us.speedUnit(event.vehicle.Speed))
 					contentDescription =
-						us.speedUnit(departure.vehicle.Speed).contentDescription(requireContext(), us.base)
+						us.speedUnit(event.vehicle.Speed).contentDescription(requireContext(), us.base)
 				}
 			}
 
 			findViewById<LinearLayout>(R.id.congestion).visibility =
-				if (departure.vehicle.congestionLevel == CongestionLevel.UNKNOWN) View.GONE else View.VISIBLE
-			findViewById<TextView>(R.id.congestion_text).text = departure.vehicle.congestion(ctx)
+				if (event.vehicle.congestionLevel == CongestionLevel.UNKNOWN) View.GONE else View.VISIBLE
+			findViewById<TextView>(R.id.congestion_text).text = event.vehicle.congestion(ctx)
 
 			findViewById<LinearLayout>(R.id.occupancy).visibility =
-				if (departure.vehicle.occupancyStatus == OccupancyStatus.UNKNOWN) View.GONE else View.VISIBLE
-			findViewById<TextView>(R.id.occupancy_text).text = departure.vehicle.occupancy(ctx)
+				if (event.vehicle.occupancyStatus == OccupancyStatus.UNKNOWN) View.GONE else View.VISIBLE
+			findViewById<TextView>(R.id.occupancy_text).text = event.vehicle.occupancy(ctx)
 
 			findViewById<ImageView>(R.id.ac).let {
 				TooltipCompat.setTooltipText(
@@ -411,7 +489,7 @@ 					it,
 					getString(R.string.air_condition_content_description)
 				)
 				it.visibility =
-					if (departure.vehicle.getCapability(Vehicle.Capability.AC)) View.VISIBLE else View.GONE
+					if (event.vehicle.getCapability(Vehicle.Capability.AC)) View.VISIBLE else View.GONE
 			}
 
 			findViewById<ImageView>(R.id.bike).let {
@@ -420,7 +498,7 @@ 					it,
 					getString(R.string.bicycles_allowed_content_description)
 				)
 				it.visibility =
-					if (departure.vehicle.getCapability(Vehicle.Capability.BIKE)) {
+					if (event.vehicle.getCapability(Vehicle.Capability.BIKE)) {
 						View.VISIBLE
 					} else {
 						View.GONE
@@ -433,7 +511,7 @@ 					it,
 					getString(R.string.voice_announcements_content_description)
 				)
 				it.visibility =
-					if (departure.vehicle.getCapability(Vehicle.Capability.VOICE)) {
+					if (event.vehicle.getCapability(Vehicle.Capability.VOICE)) {
 						View.VISIBLE
 					} else {
 						View.GONE
@@ -444,7 +522,7 @@ 				TooltipCompat.setTooltipText(
 					ticketImage,
 					getString(R.string.tickets_sold_content_description)
 				)
-				ticketImage.visibility = if (departure.vehicle.let {
+				ticketImage.visibility = if (event.vehicle.let {
 						it.getCapability(Vehicle.Capability.TICKET_DRIVER) || it.getCapability(Vehicle.Capability.TICKET_MACHINE)
 					}) {
 					View.VISIBLE
@@ -458,15 +536,15 @@ 					it,
 					getString(R.string.usb_charging_content_description)
 				)
 				it.visibility =
-					if (departure.vehicle.getCapability(Vehicle.Capability.USB_CHARGING)) {
+					if (event.vehicle.getCapability(Vehicle.Capability.USB_CHARGING)) {
 						View.VISIBLE
 					} else {
 						View.GONE
 					}
 			}
 
-			if (departure.alerts.isNotEmpty()) {
-				findViewById<MaterialTextView>(R.id.alerts_text).text = departure.alerts.map {
+			if (event.alerts.isNotEmpty()) {
+				findViewById<MaterialTextView>(R.id.alerts_text).text = event.alerts.map {
 					it.header.ifEmpty {
 						getString(R.string.alert_header)
 					}
@@ -477,7 +555,7 @@ 					setOnClickListener {
 						MaterialAlertDialogBuilder(context)
 							.setTitle(R.string.alerts)
 							.setPositiveButton(R.string.ok) { _, _ -> }
-							.setMessage(departure.alerts.map { it.description }.filter { it != "" }
+							.setMessage(event.alerts.map { it.description }.filter { it != "" }
 								.joinToString(separator = "\n"))
 							.show()
 					}
@@ -485,14 +563,14 @@ 				}
 			}
 
 			findViewById<MapView>(R.id.map).let { map ->
-				if (departure.vehicle.Position.isZero()) {
+				if (event.vehicle.Position.isZero()) {
 					map.visibility = View.GONE
 					return@let
 				}
 				map.controller.apply {
 					GeoPoint(
-						departure.vehicle.location().latitude,
-						departure.vehicle.location().longitude
+						event.vehicle.location().latitude,
+						event.vehicle.location().longitude
 					).let { geoPoint ->
 						if (updating) {
 							animateTo(
@@ -511,11 +589,11 @@ 				}
 				val marker = Marker(map).apply {
 					position =
 						GeoPoint(
-							departure.vehicle.location().latitude,
-							departure.vehicle.location().longitude
+							event.vehicle.location().latitude,
+							event.vehicle.location().longitude
 						)
 					setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
-					icon = context?.let { ctx -> departure.vehicle.icon(ctx, 2f) }
+					icon = context?.let { ctx -> event.vehicle.icon(ctx, 2f) }
 					setOnClickListener {}
 				}
 				map.overlays.add(marker)




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 0b725721dcfca87b8b52eaa06e4407b36784c410..8ca35eb6214c46644d0d28ee8f635bd4054a37d5 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
@@ -28,6 +28,7 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.lifecycle.ViewModelProvider
 import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.datepicker.MaterialDatePicker
@@ -45,7 +46,7 @@ import xyz.apiote.bimba.czwek.databinding.ActivityDeparturesBinding
 import xyz.apiote.bimba.czwek.departures.BimbaDeparturesAdapter.Companion.TERMINUS_ARRIVAL_GREY_OUT
 import xyz.apiote.bimba.czwek.departures.BimbaDeparturesAdapter.Companion.TERMINUS_ARRIVAL_HIDE
 import xyz.apiote.bimba.czwek.departures.BimbaDeparturesAdapter.Companion.TERMINUS_ARRIVAL_SHOWING_KEY
-import xyz.apiote.bimba.czwek.repo.DepartureItem
+import xyz.apiote.bimba.czwek.repo.EventItem
 import xyz.apiote.bimba.czwek.repo.Favourite
 import xyz.apiote.bimba.czwek.repo.OfflineRepository
 import xyz.apiote.bimba.czwek.repo.Stop
@@ -168,7 +169,7 @@ 			}
 
 		viewModel.linesFilter.observe(this) {
 			// TODO if is before we got departures, do nothing
-			val departures = viewModel.departures.value?.departures ?: emptyList()
+			val departures = viewModel.departures.value?.events ?: emptyList()
 			updateItems(departures
 				.filter { d ->
 					it.values.all { !it } or (it[d.vehicle.Line.name] ?: false)
@@ -177,20 +178,20 @@ 				.filter { d ->
 					viewModel.showingTerminusArrivals != TERMINUS_ARRIVAL_HIDE || !d.terminusArrival
 				}
 				.filter { d ->
-					val t = LocalTime.of(d.time.Hour.toInt(), d.time.Minute.toInt())
+					val t = LocalTime.of(d.filterTime().Hour.toInt(), d.filterTime().Minute.toInt())
 					t >= viewModel.startTime && t <= viewModel.endTime
-				}.map { DepartureItem(it) },
+				}.map { EventItem(it) },
 				null,
 				true
 			)
 		}
 
 		viewModel.departures.observe(this) { stopDepartures ->
-			val items = mutableListOf<DepartureItem>()
+			val items = mutableListOf<EventItem>()
 			if (stopDepartures.alerts.isNotEmpty()) {
-				items.add(DepartureItem(stopDepartures.alerts))
+				items.add(EventItem(stopDepartures.alerts))
 			}
-			items.addAll(stopDepartures.departures
+			items.addAll(stopDepartures.events
 				.filter { d ->
 					viewModel.linesFilter.value?.let { filter ->
 						filter.values.all { !it } or (filter[d.vehicle.Line.name] ?: false)
@@ -200,15 +201,15 @@ 				.filter { d ->
 					viewModel.showingTerminusArrivals != TERMINUS_ARRIVAL_HIDE || !d.terminusArrival
 				}
 				.filter { d ->
-					val t = LocalTime.of(d.time.Hour.toInt(), d.time.Minute.toInt())
+					val t = LocalTime.of(d.filterTime().Hour.toInt(), d.filterTime().Minute.toInt())
 					t >= viewModel.startTime && t <= viewModel.endTime
-				}.map { DepartureItem(it) })
+				}.map { EventItem(it) })
 			updateItems(items, stopDepartures.stop)
 			viewModel.openBottomSheet?.departureID()?.let { adapter.get(it) }
-				?.let { it.departure?.let { departure -> viewModel.openBottomSheet?.update(departure) } }
+				?.let { it.event?.let { departure -> viewModel.openBottomSheet?.update(departure) } }
 
 
-			val lines = stopDepartures.departures.map { it.vehicle.Line.name }.sortedWith { s1, s2 ->
+			val lines = stopDepartures.events.map { it.vehicle.Line.name }.sortedWith { s1, s2 ->
 				val s1n = s1.toIntOrNull()
 				val s2n = s2.toIntOrNull()
 				if (s1n != null && s2n != null) {
@@ -374,6 +375,7 @@ 			}
 		}
 
 		binding.departuresRecycler.layoutManager = LinearLayoutManager(this)
+		binding.departuresRecycler.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
 		binding.departuresRecycler.itemAnimator = null
 		binding.departuresRecycler.addOnScrollListener(
 			object : RecyclerView.OnScrollListener() {
@@ -518,7 +520,7 @@ 		binding.errorText.visibility = View.GONE
 	}
 
 	private fun updateItems(
-		departures: List<DepartureItem>,
+		departures: List<EventItem>,
 		stop: Stop?,
 		leaveAlert: Boolean = false
 	) {




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 bd61194560e6a232a5754bbf33fa60b3ed03dc6f..21dbd04eefa862f93ad0f5ad95a0fc1ef48e80db 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
@@ -20,14 +20,14 @@ import xyz.apiote.bimba.czwek.repo.FeedInfo
 import xyz.apiote.bimba.czwek.repo.OfflineRepository
 import xyz.apiote.bimba.czwek.repo.OnlineRepository
 import xyz.apiote.bimba.czwek.repo.QrLocation
-import xyz.apiote.bimba.czwek.repo.StopDepartures
+import xyz.apiote.bimba.czwek.repo.StopEvents
 import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import java.time.LocalDate
 import java.time.LocalTime
 
 class DeparturesViewModel : ViewModel() {
-	private val _departures = MutableLiveData<StopDepartures>()
-	val departures: LiveData<StopDepartures> = _departures
+	private val _departures = MutableLiveData<StopEvents>()
+	val departures: LiveData<StopEvents> = _departures
 	private val _error = MutableLiveData<Error>()
 	val error: LiveData<Error> = _error
 	var requestedItemsNumber = 12
@@ -64,7 +64,7 @@ 						context,
 						requestedItemsNumber
 					)
 				stopDepartures?.let {
-					if (stopDepartures.departures.isEmpty()) {
+					if (stopDepartures.events.isEmpty()) {
 						val (string, image) = mapHttpError(44)
 						throw TrafficResponseException(44, "", Error(44, string, image))
 					}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Departure.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Departure.kt
deleted file mode 100644
index 38a48a946c2048ad2fa19b5eb4fa169769287adc..0000000000000000000000000000000000000000
--- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Departure.kt
+++ /dev/null
@@ -1,242 +0,0 @@
-// SPDX-FileCopyrightText: Adam Evyčędo
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-package xyz.apiote.bimba.czwek.repo
-
-import android.content.Context
-import android.text.format.DateUtils
-import xyz.apiote.bimba.czwek.R
-import xyz.apiote.bimba.czwek.api.AlertCauseV1
-import xyz.apiote.bimba.czwek.api.AlertEffectV1
-import xyz.apiote.bimba.czwek.api.AlertV1
-import xyz.apiote.bimba.czwek.api.DepartureV1
-import xyz.apiote.bimba.czwek.api.DepartureV2
-import xyz.apiote.bimba.czwek.api.DepartureV3
-import xyz.apiote.bimba.czwek.api.DepartureV4
-import xyz.apiote.bimba.czwek.api.DepartureV5
-import xyz.apiote.bimba.czwek.api.Time
-import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
-import xyz.apiote.bimba.czwek.units.Second
-import xyz.apiote.bimba.czwek.units.TGM
-import xyz.apiote.bimba.czwek.units.UnitSystem
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-import java.time.temporal.ChronoUnit
-
-
-class DepartureItem {
-	private constructor(d: Departure?, a: List<Alert>) {
-		departure = d
-		alert = a
-	}
-
-	constructor(d: Departure) : this(d, emptyList())
-	constructor(a: List<Alert>) : this(null, a)
-
-	val departure: Departure?
-	val alert: List<Alert>
-}
-
-enum class AlertCause {
-	UNKNOWN, OTHER, TECHNICAL_PROBLEM, STRIKE, DEMONSTRATION, ACCIDENT, HOLIDAY, WEATHER, MAINTENANCE,
-	CONSTRUCTION, POLICE_ACTIVITY, MEDICAL_EMERGENCY;
-
-	companion object {
-		fun of(type: AlertCauseV1): AlertCause {
-			return when (type) {
-				AlertCauseV1.UNKNOWN -> valueOf("UNKNOWN")
-				AlertCauseV1.OTHER -> valueOf("OTHER")
-				AlertCauseV1.TECHNICAL_PROBLEM -> valueOf("TECHNICAL_PROBLEM")
-				AlertCauseV1.STRIKE -> valueOf("STRIKE")
-				AlertCauseV1.DEMONSTRATION -> valueOf("DEMONSTRATION")
-				AlertCauseV1.ACCIDENT -> valueOf("ACCIDENT")
-				AlertCauseV1.HOLIDAY -> valueOf("HOLIDAY")
-				AlertCauseV1.WEATHER -> valueOf("WEATHER")
-				AlertCauseV1.MAINTENANCE -> valueOf("MAINTENANCE")
-				AlertCauseV1.CONSTRUCTION -> valueOf("CONSTRUCTION")
-				AlertCauseV1.POLICE_ACTIVITY -> valueOf("POLICE_ACTIVITY")
-				AlertCauseV1.MEDICAL_EMERGENCY -> valueOf("MEDICAL_EMERGENCY")
-			}
-		}
-	}
-}
-
-enum class AlertEffect {
-	UNKNOWN, OTHER, NO_SERVICE, REDUCED_SERVICE, SIGNIFICANT_DELAYS, DETOUR, ADDITIONAL_SERVICE,
-	MODIFIED_SERVICE, STOP_MOVED, NONE, ACCESSIBILITY_ISSUE;
-
-	companion object {
-		fun of(type: AlertEffectV1): AlertEffect {
-			return when (type) {
-				AlertEffectV1.UNKNOWN -> valueOf("UNKNOWN")
-				AlertEffectV1.OTHER -> valueOf("OTHER")
-				AlertEffectV1.NO_SERVICE -> valueOf("NO_SERVICE")
-				AlertEffectV1.REDUCED_SERVICE -> valueOf("REDUCED_SERVICE")
-				AlertEffectV1.SIGNIFICANT_DELAYS -> valueOf("SIGNIFICANT_DELAYS")
-				AlertEffectV1.DETOUR -> valueOf("DETOUR")
-				AlertEffectV1.ADDITIONAL_SERVICE -> valueOf("ADDITIONAL_SERVICE")
-				AlertEffectV1.MODIFIED_SERVICE -> valueOf("MODIFIED_SERVICE")
-				AlertEffectV1.STOP_MOVED -> valueOf("STOP_MOVED")
-				AlertEffectV1.NONE -> valueOf("NONE")
-				AlertEffectV1.ACCESSIBILITY_ISSUE -> valueOf("ACCESSIBILITY_ISSUE")
-			}
-		}
-	}
-}
-
-data class Alert(
-	val header: String,
-	val description: String,
-	val url: String,
-	val cause: AlertCause,
-	val effect: AlertEffect
-) {
-	constructor(a: AlertV1) : this(
-		a.header,
-		a.Description,
-		a.Url,
-		AlertCause.of(a.Cause),
-		AlertEffect.of(a.Effect)
-	)
-}
-
-data class StopDepartures(
-	val departures: List<Departure>,
-	val stop: Stop,
-	val alerts: List<Alert>
-)
-
-data class Departure(
-	val ID: String,
-	val time: Time,
-	val status: ULong,
-	val isRealtime: Boolean,
-	val vehicle: Vehicle,
-	val boarding: UByte,
-	val alerts: List<Alert>,
-	val exact: Boolean,
-	val terminusArrival: Boolean
-) {
-
-	constructor(d: DepartureV1) : this(
-		d.ID,
-		d.time,
-		d.status,
-		d.isRealtime,
-		Vehicle(d.vehicle),
-		d.boarding,
-		emptyList(),
-		true,
-		false
-	)
-
-	constructor(d: DepartureV2) : this(
-		d.ID,
-		d.time,
-		d.status,
-		d.isRealtime,
-		Vehicle(d.vehicle),
-		d.boarding,
-		emptyList(),
-		true,
-		false
-	)
-
-	constructor(d: DepartureV3) : this(
-		d.ID,
-		d.time,
-		d.status.ordinal.toULong(), // TODO VehicleStatus
-		d.isRealtime,
-		Vehicle(d.vehicle),
-		d.boarding,
-		emptyList(),
-		true,
-		false
-	)
-
-	constructor(d: DepartureV4) : this(
-		d.ID,
-		d.time,
-		d.status.ordinal.toULong(), // TODO VehicleStatus
-		d.isRealtime,
-		Vehicle(d.vehicle),
-		d.boarding,
-		d.alerts.map { Alert(it) },
-		true,
-		false
-	)
-
-	constructor(d: DepartureV5) : this(
-		d.ID,
-		d.time,
-		d.status.ordinal.toULong(), // TODO VehicleStatus
-		d.isRealtime,
-		Vehicle(d.vehicle),
-		d.boarding,
-		d.alerts.map { Alert(it) },
-		d.exact,
-		d.terminusArrival
-	)
-
-	fun statusText(context: Context?, showAsTime: Boolean, at: ZonedDateTime? = null): String {
-		val now = at ?: Instant.now().atZone(ZoneId.systemDefault())
-		val departureTime = ZonedDateTime.of(
-			now.year, now.monthValue, now.dayOfMonth,
-			time.Hour.toInt(), time.Minute.toInt(), time.Second.toInt(), 0, ZoneId.of(time.Zone)
-		).plus(time.DayOffset.toLong(), ChronoUnit.DAYS)
-		if (showAsTime) {
-			return departureTime.format(DateTimeFormatter.ofPattern("HH:mm"))
-		}
-		var r = status.toUInt()
-		if (departureTime.isBefore(now) && r < 3u) {
-			r = 0u
-		}
-		return when (r) {
-			0u -> if (context != null && UnitSystem.getSelected(context) is TGM) {
-				val us = UnitSystem.getSelected(context)
-				us.toString(
-					context,
-					us.timeUnit(Second((departureTime.toEpochSecond() - now.toEpochSecond()).toInt()))
-				)
-			} else {
-				DateUtils.getRelativeTimeSpanString(
-					departureTime.toEpochSecond() * 1000,
-					now.toEpochSecond() * 1000,
-					DateUtils.MINUTE_IN_MILLIS,
-					DateUtils.FORMAT_ABBREV_RELATIVE
-				).toString()
-			}
-
-			1u -> context?.getString(R.string.departure_momentarily) ?: "momentarily"
-			2u -> context?.getString(R.string.departure_now) ?: "now"
-			3u -> context?.getString(R.string.departure_departed) ?: "departed"
-			else -> throw UnknownResourceVersionException("VehicleStatus/$r", 1u)
-		}
-	}
-
-	fun timeString(context: Context): String {
-		return when {
-			isRealtime -> context.getString(
-				R.string.at_time_realtime, time.Hour.toInt(), time.Minute.toInt(), time.Second.toInt()
-			)
-
-			exact -> context.getString(R.string.at_time, time.Hour.toInt(), time.Minute.toInt())
-			else -> context.getString(R.string.about_time, time.Hour.toInt(), time.Minute.toInt())
-		}
-	}
-
-	fun boardingText(context: Context): String {
-		// todo [3.x] probably should take into account (on|off)-boarding only, on demand
-		return when {
-			boarding == (0b0000_0000).toUByte() -> context.getString(R.string.no_boarding)
-			boarding == (0b1111_1111).toUByte() -> "" // unknown
-			boarding.and(0b0011_0011u) == (0b0000_0001).toUByte() -> context.getString(R.string.on_boarding)
-			boarding.and(0b0011_0011u) == (0b0001_0000).toUByte() -> context.getString(R.string.off_boarding)
-			boarding.and(0b0011_0011u) == (0b0001_0001).toUByte() -> context.getString(R.string.boarding)
-			else -> context.getString(R.string.on_demand)
-		}
-	}
-}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Event.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Event.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5e70e8f0e8435eec878d87e43e5edcbde64ba262
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Event.kt
@@ -0,0 +1,302 @@
+// SPDX-FileCopyrightText: Adam Evyčędo
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package xyz.apiote.bimba.czwek.repo
+
+import android.content.Context
+import android.text.format.DateUtils
+import xyz.apiote.bimba.czwek.R
+import xyz.apiote.bimba.czwek.api.AlertCauseV1
+import xyz.apiote.bimba.czwek.api.AlertEffectV1
+import xyz.apiote.bimba.czwek.api.AlertV1
+import xyz.apiote.bimba.czwek.api.DepartureV1
+import xyz.apiote.bimba.czwek.api.DepartureV2
+import xyz.apiote.bimba.czwek.api.DepartureV3
+import xyz.apiote.bimba.czwek.api.DepartureV4
+import xyz.apiote.bimba.czwek.api.DepartureV5
+import xyz.apiote.bimba.czwek.api.Time
+import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
+import xyz.apiote.bimba.czwek.units.Second
+import xyz.apiote.bimba.czwek.units.TGM
+import xyz.apiote.bimba.czwek.units.UnitSystem
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoUnit
+
+
+class EventItem {
+	private constructor(d: Event?, a: List<Alert>) {
+		event = d
+		alert = a
+	}
+
+	constructor(d: Event) : this(d, emptyList())
+	constructor(a: List<Alert>) : this(null, a)
+
+	val event: Event?
+	val alert: List<Alert>
+}
+
+enum class AlertCause {
+	UNKNOWN, OTHER, TECHNICAL_PROBLEM, STRIKE, DEMONSTRATION, ACCIDENT, HOLIDAY, WEATHER, MAINTENANCE,
+	CONSTRUCTION, POLICE_ACTIVITY, MEDICAL_EMERGENCY;
+
+	companion object {
+		fun of(type: AlertCauseV1): AlertCause {
+			return when (type) {
+				AlertCauseV1.UNKNOWN -> valueOf("UNKNOWN")
+				AlertCauseV1.OTHER -> valueOf("OTHER")
+				AlertCauseV1.TECHNICAL_PROBLEM -> valueOf("TECHNICAL_PROBLEM")
+				AlertCauseV1.STRIKE -> valueOf("STRIKE")
+				AlertCauseV1.DEMONSTRATION -> valueOf("DEMONSTRATION")
+				AlertCauseV1.ACCIDENT -> valueOf("ACCIDENT")
+				AlertCauseV1.HOLIDAY -> valueOf("HOLIDAY")
+				AlertCauseV1.WEATHER -> valueOf("WEATHER")
+				AlertCauseV1.MAINTENANCE -> valueOf("MAINTENANCE")
+				AlertCauseV1.CONSTRUCTION -> valueOf("CONSTRUCTION")
+				AlertCauseV1.POLICE_ACTIVITY -> valueOf("POLICE_ACTIVITY")
+				AlertCauseV1.MEDICAL_EMERGENCY -> valueOf("MEDICAL_EMERGENCY")
+			}
+		}
+	}
+}
+
+enum class AlertEffect {
+	UNKNOWN, OTHER, NO_SERVICE, REDUCED_SERVICE, SIGNIFICANT_DELAYS, DETOUR, ADDITIONAL_SERVICE,
+	MODIFIED_SERVICE, STOP_MOVED, NONE, ACCESSIBILITY_ISSUE;
+
+	companion object {
+		fun of(type: AlertEffectV1): AlertEffect {
+			return when (type) {
+				AlertEffectV1.UNKNOWN -> valueOf("UNKNOWN")
+				AlertEffectV1.OTHER -> valueOf("OTHER")
+				AlertEffectV1.NO_SERVICE -> valueOf("NO_SERVICE")
+				AlertEffectV1.REDUCED_SERVICE -> valueOf("REDUCED_SERVICE")
+				AlertEffectV1.SIGNIFICANT_DELAYS -> valueOf("SIGNIFICANT_DELAYS")
+				AlertEffectV1.DETOUR -> valueOf("DETOUR")
+				AlertEffectV1.ADDITIONAL_SERVICE -> valueOf("ADDITIONAL_SERVICE")
+				AlertEffectV1.MODIFIED_SERVICE -> valueOf("MODIFIED_SERVICE")
+				AlertEffectV1.STOP_MOVED -> valueOf("STOP_MOVED")
+				AlertEffectV1.NONE -> valueOf("NONE")
+				AlertEffectV1.ACCESSIBILITY_ISSUE -> valueOf("ACCESSIBILITY_ISSUE")
+			}
+		}
+	}
+}
+
+data class Alert(
+	val header: String,
+	val description: String,
+	val url: String,
+	val cause: AlertCause,
+	val effect: AlertEffect
+) {
+	constructor(a: AlertV1) : this(
+		a.header,
+		a.Description,
+		a.Url,
+		AlertCause.of(a.Cause),
+		AlertEffect.of(a.Effect)
+	)
+}
+
+data class StopEvents(
+	val events: List<Event>,
+	val stop: Stop,
+	val alerts: List<Alert>
+)
+
+data class Event(
+	val id: String,
+	val arrivalTime: Time?,
+	val departureTime: Time?,
+	val status: ULong,
+	val isRealtime: Boolean,
+	val vehicle: Vehicle,
+	val boarding: UByte,
+	val alerts: List<Alert>,
+	val exact: Boolean,
+	val terminusArrival: Boolean  // TODO origin, middle, terminus; if origin -> only departure, if terminus -> only arrival
+) {
+
+	constructor(d: DepartureV1) : this(
+		d.ID,
+		d.time,
+		d.time,
+		d.status,
+		d.isRealtime,
+		Vehicle(d.vehicle),
+		d.boarding,
+		emptyList(),
+		true,
+		false
+	)
+
+	constructor(d: DepartureV2) : this(
+		d.ID,
+		d.time,
+		d.time,
+		d.status,
+		d.isRealtime,
+		Vehicle(d.vehicle),
+		d.boarding,
+		emptyList(),
+		true,
+		false
+	)
+
+	constructor(d: DepartureV3) : this(
+		d.ID,
+		d.time,
+		d.time,
+		d.status.ordinal.toULong(), // TODO VehicleStatus
+		d.isRealtime,
+		Vehicle(d.vehicle),
+		d.boarding,
+		emptyList(),
+		true,
+		false
+	)
+
+	constructor(d: DepartureV4) : this(
+		d.ID,
+		d.time,
+		d.time,
+		d.status.ordinal.toULong(), // TODO VehicleStatus
+		d.isRealtime,
+		Vehicle(d.vehicle),
+		d.boarding,
+		d.alerts.map { Alert(it) },
+		true,
+		false
+	)
+
+	constructor(d: DepartureV5) : this(
+		d.ID,
+		d.time,
+		d.time,
+		d.status.ordinal.toULong(), // TODO VehicleStatus
+		d.isRealtime,
+		Vehicle(d.vehicle),
+		d.boarding,
+		d.alerts.map { Alert(it) },
+		d.exact,
+		d.terminusArrival
+	)
+
+	fun timeZone() = (arrivalTime ?: departureTime)!!.Zone
+
+	fun filterTime() = (arrivalTime ?: departureTime)!!
+
+	fun statusText(
+		context: Context?,
+		showAsTime: Boolean,
+		at: ZonedDateTime? = null
+	): Pair<String?, String?> {
+		val now = at ?: Instant.now().atZone(ZoneId.systemDefault())
+		return Pair(
+			statusText(context, showAsTime, now, arrivalTime, R.string.departure_arrived),
+			statusText(context, showAsTime, now, departureTime, R.string.departure_departed)
+		)
+	}
+
+	private fun statusText(
+		context: Context?,
+		showAsTime: Boolean,
+		now: ZonedDateTime,
+		time: Time?,
+		pastString: Int
+	): String? {
+		val r = status.toUInt()
+		return time?.let {
+			ZonedDateTime.of(
+				now.year,
+				now.monthValue,
+				now.dayOfMonth,
+				it.Hour.toInt(),
+				it.Minute.toInt(),
+				it.Second.toInt(),
+				0,
+				ZoneId.of(
+					it.Zone
+				)
+			)
+				.plus(it.DayOffset.toLong(), ChronoUnit.DAYS)
+				.let {
+					if (showAsTime) {
+						it.format(DateTimeFormatter.ofPattern("HH:mm"))
+					} else {
+						when {
+							// TODO why this condition
+							r == 0u || (it.isBefore(now) && r < 3u) -> if (context != null && UnitSystem.getSelected(
+									context
+								) is TGM
+							) {
+								val us = UnitSystem.getSelected(context)
+								us.toString(
+									context,
+									us.timeUnit(Second((it.toEpochSecond() - now.toEpochSecond()).toInt()))
+								)
+							} else {
+								DateUtils.getRelativeTimeSpanString(
+									it.toEpochSecond() * 1000,
+									now.toEpochSecond() * 1000,
+									DateUtils.MINUTE_IN_MILLIS,
+									DateUtils.FORMAT_ABBREV_RELATIVE
+								).toString()
+							}
+
+							r == 1u -> context?.getString(R.string.departure_momentarily) ?: "momentarily"
+							r == 2u -> context?.getString(R.string.departure_now) ?: "now"
+							r == 3u -> context?.getString(pastString) ?: "passed"
+							else -> throw UnknownResourceVersionException("VehicleStatus/$r", 1u)
+						}
+					}
+				}
+		}
+	}
+
+	fun departureTimeString(context: Context): String? = timeString(context, departureTime)
+
+	fun arrivalTimeString(context: Context): String? = timeString(context, arrivalTime)
+
+	private fun timeString(context: Context, time: Time?): String? {
+		return when {
+			time == null -> null
+			isRealtime -> context.getString(
+				R.string.at_time_realtime,
+				time.Hour.toInt(),
+				time.Minute.toInt(),
+				time.Second.toInt()
+			)
+
+			exact -> context.getString(
+				R.string.at_time,
+				time.Hour.toInt(),
+				time.Minute.toInt()
+			)
+
+			else -> context.getString(
+				R.string.about_time,
+				time.Hour.toInt(),
+				time.Minute.toInt()
+			)
+		}
+
+	}
+
+	fun boardingText(context: Context): String {
+		// todo [3.x] probably should take into account (on|off)-boarding only, on demand
+		return when {
+			boarding == (0b0000_0000).toUByte() -> context.getString(R.string.no_boarding)
+			boarding == (0b1111_1111).toUByte() -> "" // unknown
+			boarding.and(0b0011_0011u) == (0b0000_0001).toUByte() -> context.getString(R.string.on_boarding)
+			boarding.and(0b0011_0011u) == (0b0001_0000).toUByte() -> context.getString(R.string.off_boarding)
+			boarding.and(0b0011_0011u) == (0b0001_0001).toUByte() -> context.getString(R.string.boarding)
+			else -> context.getString(R.string.on_demand)
+		}
+	}
+}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt
index a337b0178c144f066ffea44ebd3898c475d850c0..e612503b10f270a49961d1482475da55fb3f47db 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt
@@ -36,7 +36,7 @@ 		stop: String,
 		date: LocalDate?,
 		context: Context,
 		limit: Int?
-	): StopDepartures?
+	): StopEvents?
 
 	suspend fun getLocatablesIn(
 		context: Context,




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt
index b11a5dfcccc9048e0390e0dfe9ad530e875a7668..309d6ec0f00248d543db51e92389dfddb1457a3d 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt
@@ -200,7 +200,7 @@ 		stop: String,
 		date: LocalDate?,
 		context: Context,
 		limit: Int?
-	): StopDepartures? {
+	): StopEvents? {
 		TODO("Not yet implemented")
 	}
 




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt
index 89f7f8d5ccc84cde130f7c6a92ded75a16e30170..0b47623ff0b1f191b90a9e19f308f9a50878117f 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt
@@ -114,7 +114,7 @@ 		stop: String,
 		date: LocalDate?,
 		context: Context,
 		limit: Int?
-	): StopDepartures? {
+	): StopEvents? {
 		return if (feedID == "transitous") {
 			getTransitousDepartures(context, stop, date, limit)
 		} else {
@@ -137,28 +137,28 @@ 				}
 			} else {
 				when (val response =
 					withContext(Dispatchers.IO) { DeparturesResponse.unmarshal(result.stream!!) }) {
-					is DeparturesResponseDev -> StopDepartures(
-						response.departures.map { Departure(it) },
+					is DeparturesResponseDev -> StopEvents(
+						response.departures.map { Event(it) },
 						Stop(response.stop),
 						response.alerts.map { Alert(it) })
 
-					is DeparturesResponseV4 -> StopDepartures(
-						response.departures.map { Departure(it) },
+					is DeparturesResponseV4 -> StopEvents(
+						response.departures.map { Event(it) },
 						Stop(response.stop),
 						response.alerts.map { Alert(it) })
 
-					is DeparturesResponseV3 -> StopDepartures(
-						response.departures.map { Departure(it) },
+					is DeparturesResponseV3 -> StopEvents(
+						response.departures.map { Event(it) },
 						Stop(response.stop),
 						response.alerts.map { Alert(it) })
 
-					is DeparturesResponseV2 -> StopDepartures(
-						response.departures.map { Departure(it) },
+					is DeparturesResponseV2 -> StopEvents(
+						response.departures.map { Event(it) },
 						Stop(response.stop),
 						response.alerts.map { Alert(it) })
 
-					is DeparturesResponseV1 -> StopDepartures(
-						response.departures.map { Departure(it) },
+					is DeparturesResponseV1 -> StopEvents(
+						response.departures.map { Event(it) },
 						Stop(response.stop),
 						response.alerts.map { Alert(it) })
 




diff --git a/app/src/main/res/drawable/radar.xml b/app/src/main/res/drawable/radar.xml
index f68d13754fc956d4f9ac08166860576029d28b09..376de31d4cfec3e89591559016392ec6588e29ca 100644
--- a/app/src/main/res/drawable/radar.xml
+++ b/app/src/main/res/drawable/radar.xml
@@ -4,8 +4,15 @@
 SPDX-License-Identifier: Apache-2.0
 -->
 
-<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="M19.74,18.33C21.15,16.6 22,14.4 22,12c0,-5.52 -4.48,-10 -10,-10S2,6.48 2,12s4.48,10 10,10c2.4,0 4.6,-0.85 6.33,-2.26c0.27,-0.22 0.53,-0.46 0.78,-0.71c0.03,-0.03 0.05,-0.06 0.07,-0.08C19.38,18.75 19.57,18.54 19.74,18.33zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8s8,3.59 8,8c0,1.85 -0.63,3.54 -1.69,4.9l-1.43,-1.43c0.69,-0.98 1.1,-2.17 1.1,-3.46c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6s2.69,6 6,6c1.3,0 2.51,-0.42 3.49,-1.13l1.42,1.42C15.54,19.37 13.85,20 12,20zM13.92,12.51c0.17,-0.66 0.02,-1.38 -0.49,-1.9l-0.02,-0.02c-0.77,-0.77 -2,-0.78 -2.78,-0.04c-0.01,0.01 -0.03,0.02 -0.05,0.04c-0.78,0.78 -0.78,2.05 0,2.83l0.02,0.02c0.52,0.51 1.25,0.67 1.91,0.49l1.51,1.51c-0.6,0.36 -1.29,0.58 -2.04,0.58c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4s4,1.79 4,4c0,0.73 -0.21,1.41 -0.56,2L13.92,12.51z" />
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+	android:width="24dp"
+	android:height="24dp"
+	android:tint="?attr/colorOnSurface"
+	android:viewportWidth="24"
+	android:viewportHeight="24">
+
+	<path
+		android:fillColor="@android:color/white"
+		android:pathData="M15.44,0.59l-3.18,3.18c-0.78,0.78 -0.78,2.05 0,2.83l1.24,1.24l-0.71,0.71L11.55,7.3c-0.78,-0.78 -2.05,-0.78 -2.83,0L7.3,8.72c-0.78,0.78 -0.78,2.05 0,2.83l1.24,1.24l-0.71,0.71L6.6,12.25c-0.78,-0.78 -2.05,-0.78 -2.83,0l-3.18,3.18c-0.78,0.78 -0.78,2.05 0,2.83l3.54,3.54c0.78,0.78 2.05,0.78 2.83,0l3.18,-3.18c0.78,-0.78 0.78,-2.05 0,-2.83l-1.24,-1.24l0.71,-0.71l1.24,1.24c0.78,0.78 2.05,0.78 2.83,0l1.41,-1.41c0.78,-0.78 0.78,-2.05 0,-2.83L13.84,9.6l0.71,-0.71l1.24,1.24c0.78,0.78 2.05,0.78 2.83,0l3.18,-3.18c0.78,-0.78 0.78,-2.05 0,-2.83l-3.54,-3.54C17.48,-0.2 16.22,-0.2 15.44,0.59zM6.6,19.32l-1.06,1.06L2,16.85l1.06,-1.06L6.6,19.32zM8.72,17.2l-1.06,1.06l-3.54,-3.54l1.06,-1.06L8.72,17.2zM18.26,7.66L17.2,8.72l-3.54,-3.54l1.06,-1.06L18.26,7.66zM20.38,5.54L19.32,6.6l-3.54,-3.54L16.85,2L20.38,5.54zM14,21l0,2c4.97,0 9,-4.03 9,-9l-2,0C21,17.87 17.87,21 14,21zM14,17l0,2c2.76,0 5,-2.24 5,-5l-2,0C17,15.66 15.66,17 14,17z" />
+
 </vector>




diff --git a/app/src/main/res/layout/departure.xml b/app/src/main/res/layout/departure.xml
index b2514262349aef0d448fed775a6116b4042ece6d..c3d8f14918808281ed2d6ce28a9ac3855f866213 100644
--- a/app/src/main/res/layout/departure.xml
+++ b/app/src/main/res/layout/departure.xml
@@ -7,63 +7,100 @@ SPDX-License-Identifier: GPL-3.0-or-later
 -->
 
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:app="http://schemas.android.com/apk/res-auto"
-	xmlns:tool="http://schemas.android.com/tools"
-	android:id="@+id/departure"
-	android:layout_width="match_parent"
-	android:layout_height="wrap_content">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tool="http://schemas.android.com/tools"
+    android:id="@+id/departure"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/line_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/departure_line"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/departure_line"
+        tool:ignore="ContentDescription"
+        tool:srcCompat="@drawable/bus_black" />
+
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/arrival_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tool:text="approx. arr." />
+
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/arrival_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/arrival_status"
+        tool:text="1hr" />
 
-	<ImageView
-		android:id="@+id/line_icon"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:layout_marginStart="8dp"
-		app:layout_constraintBottom_toTopOf="@+id/departure_headsign"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toTopOf="@+id/departure_time"
-		tool:srcCompat="@drawable/bus_black"
-		tool:ignore="ContentDescription" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/departure_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/arrival_time"
+        tool:text="approx. dep." />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/departure_time"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:layout_marginTop="8dp"
-		android:layout_marginEnd="8dp"
-		android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintTop_toTopOf="parent"
-		tool:text="1hr" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/departure_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginBottom="8dp"
+        android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/departure_status"
+        tool:text="1hr" />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/departure_line"
-		android:layout_width="0dp"
-		android:layout_height="wrap_content"
-		android:layout_marginStart="8dp"
-		android:layout_marginTop="8dp"
-		android:layout_marginEnd="8dp"
-		android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
-		app:layout_constraintStart_toEndOf="@+id/line_icon"
-		app:layout_constraintTop_toTopOf="parent"
-		app:layout_constraintEnd_toStartOf="@id/departure_time"
-		tool:text="Circle" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/departure_line"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+        app:layout_constraintEnd_toStartOf="@id/arrival_time"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toEndOf="@+id/line_icon"
+        app:layout_constraintTop_toTopOf="parent"
+        tool:text="Circle" />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/departure_headsign"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:textAppearance="@style/TextAppearance.Material3.BodySmall"
-		app:layout_constraintStart_toStartOf="@+id/departure_line"
-		app:layout_constraintTop_toBottomOf="@+id/departure_line"
-		tool:text="» Tower Hill" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/departure_headsign"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material3.BodySmall"
+        app:layout_constraintStart_toStartOf="@+id/departure_line"
+        app:layout_constraintTop_toBottomOf="@+id/departure_line"
+        tool:text="» Tower Hill" />
 
-	<ImageView
-		android:id="@+id/time_status"
-		android:layout_width="12dp"
-		android:layout_height="12dp"
-		android:layout_marginEnd="8dp"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintTop_toBottomOf="@+id/departure_time"
-		tool:ignore="ContentDescription"
-		tool:srcCompat="@drawable/inexact" />
+    <ImageView
+        android:id="@+id/event_status"
+        android:layout_width="15dp"
+        android:layout_height="11dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginBottom="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/departure_headsign"
+        app:layout_constraintVertical_bias="0.0"
+        tool:ignore="ContentDescription"
+        tool:srcCompat="@drawable/radar" />
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file




diff --git a/app/src/main/res/layout/departure_bottom_sheet.xml b/app/src/main/res/layout/departure_bottom_sheet.xml
index 3b6e92fa314f2d9967ae05c9200666b9989f8bbb..f2bb7dc23101df9ee440b0a8997d2ebbfbe753ce 100644
--- a/app/src/main/res/layout/departure_bottom_sheet.xml
+++ b/app/src/main/res/layout/departure_bottom_sheet.xml
@@ -7,282 +7,316 @@ SPDX-License-Identifier: GPL-3.0-or-later
 -->
 
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:app="http://schemas.android.com/apk/res-auto"
-	xmlns:tool="http://schemas.android.com/tools"
-	android:layout_width="match_parent"
-	android:layout_height="wrap_content"
-	android:paddingBottom="16dp">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tool="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="16dp">
 
-	<com.google.android.material.bottomsheet.BottomSheetDragHandleView
-		android:id="@+id/drag_handle"
-		android:layout_width="match_parent"
-		android:layout_height="wrap_content"
-		app:layout_constraintTop_toTopOf="parent" />
+    <com.google.android.material.bottomsheet.BottomSheetDragHandleView
+        android:id="@+id/drag_handle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent" />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/time"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:layout_marginTop="0dp"
-		android:textAppearance="@style/TextAppearance.Material3.DisplaySmall"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toBottomOf="@+id/drag_handle"
-		tool:text="at 12:10:30" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/arrival_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="0dp"
+        android:textAppearance="@style/TextAppearance.Material3.BodySmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/drag_handle"
+        tool:text="approx. arr." />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/local_time"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		app:layout_constraintTop_toBottomOf="@+id/time"
-		app:layout_constraintEnd_toEndOf="@+id/time"
-		android:text="@string/local_time"
-		android:visibility="gone"
-		app:layout_constraintStart_toStartOf="@+id/time" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/arrival_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="0dp"
+        android:textAppearance="@style/TextAppearance.Material3.DisplaySmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/arrival_status"
+        tool:text="at 12:10:30" />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/offset"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:layout_marginStart="4dp"
-		android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
-		android:visibility="gone"
-		app:layout_constraintBaseline_toBaselineOf="@+id/time"
-		app:layout_constraintStart_toEndOf="@+id/time"
-		tool:text="(+2 min)" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/departure_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:visibility="gone"
+        android:textAppearance="@style/TextAppearance.Material3.BodySmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/arrival_time"
+        tool:text="approx. dep." />
 
-	<ImageView
-		android:id="@+id/rt_icon"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:layout_marginStart="16dp"
-		android:contentDescription="@string/realtime_content_description"
-		app:layout_constraintBottom_toBottomOf="@+id/time"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toTopOf="@+id/time"
-		app:srcCompat="@drawable/radar" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/departure_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="0dp"
+        android:visibility="gone"
+        android:textAppearance="@style/TextAppearance.Material3.DisplaySmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/departure_status"
+        tool:text="at 12:10:30" />
 
-	<ImageView
-		android:id="@+id/wheelchair_icon"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:layout_marginStart="8dp"
-		android:contentDescription="@string/wheelchair_content_description"
-		app:layout_constraintStart_toEndOf="@id/rt_icon"
-		app:layout_constraintTop_toTopOf="@+id/rt_icon"
-		app:srcCompat="@drawable/wheelchair" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/local_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/local_time"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="@+id/departure_time"
+        app:layout_constraintStart_toStartOf="@+id/departure_time"
+        app:layout_constraintTop_toBottomOf="@+id/departure_time" />
 
-	<com.google.android.material.textview.MaterialTextView
-		android:id="@+id/line"
-		android:layout_width="0dp"
-		android:layout_height="wrap_content"
-		android:layout_marginStart="8dp"
-		android:layout_marginTop="8dp"
-		android:layout_marginEnd="8dp"
-		android:textAlignment="center"
-		android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toBottomOf="@id/local_time" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/offset"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="4dp"
+        android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
+        android:visibility="gone"
+        app:layout_constraintBaseline_toBaselineOf="@+id/arrival_time"
+        app:layout_constraintStart_toEndOf="@+id/arrival_time"
+        tool:text="(+2 min)" />
 
-	<androidx.constraintlayout.helper.widget.Flow
-		android:id="@+id/info"
-		android:layout_width="0dp"
-		android:layout_height="wrap_content"
-		android:layout_marginStart="48dp"
-		android:layout_marginTop="48dp"
-		android:layout_marginEnd="48dp"
-		app:constraint_referenced_ids="boarding,speed,congestion,occupancy"
-		app:flow_horizontalGap="4dp"
-		app:flow_horizontalStyle="spread_inside"
-		app:flow_verticalGap="4dp"
-		app:flow_wrapMode="chain"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toBottomOf="@+id/line" />
+    <ImageView
+        android:id="@+id/rt_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="16dp"
+        android:contentDescription="@string/realtime_content_description"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/drag_handle"
+        app:srcCompat="@drawable/radar" />
 
-	<LinearLayout
-		android:id="@+id/boarding"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:gravity="center_vertical">
+    <ImageView
+        android:id="@+id/wheelchair_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="8dp"
+        android:contentDescription="@string/wheelchair_content_description"
+        app:layout_constraintStart_toEndOf="@id/rt_icon"
+        app:layout_constraintTop_toTopOf="@+id/rt_icon"
+        app:srcCompat="@drawable/wheelchair" />
 
-		<ImageView
-			android:id="@+id/boarding_icon"
-			android:layout_width="16dp"
-			android:layout_height="16dp"
-			android:layout_marginEnd="8dp"
-			android:importantForAccessibility="no"
-			app:srcCompat="@drawable/transfer" />
+    <com.google.android.material.textview.MaterialTextView
+        android:id="@+id/line"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAlignment="center"
+        android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/local_time" />
 
-		<com.google.android.material.textview.MaterialTextView
-			android:id="@+id/boarding_text"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_marginEnd="8dp"
-			android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
-			tool:text="on demand" />
-	</LinearLayout>
+    <androidx.constraintlayout.helper.widget.Flow
+        android:id="@+id/info"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="48dp"
+        android:layout_marginTop="48dp"
+        android:layout_marginEnd="48dp"
+        app:constraint_referenced_ids="boarding,speed,congestion,occupancy"
+        app:flow_horizontalGap="4dp"
+        app:flow_horizontalStyle="spread_inside"
+        app:flow_verticalGap="4dp"
+        app:flow_wrapMode="chain"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/line" />
 
-	<LinearLayout
-		android:id="@+id/speed"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:gravity="center_vertical">
+    <LinearLayout
+        android:id="@+id/boarding"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical">
 
-		<ImageView
-			android:id="@+id/speed_icon"
-			android:layout_width="16dp"
-			android:layout_height="16dp"
-			android:layout_marginEnd="8dp"
-			android:importantForAccessibility="no"
-			app:srcCompat="@drawable/speed" />
+        <ImageView
+            android:id="@+id/boarding_icon"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginEnd="8dp"
+            android:importantForAccessibility="no"
+            app:srcCompat="@drawable/transfer" />
 
-		<com.google.android.material.textview.MaterialTextView
-			android:id="@+id/speed_text"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_marginEnd="8dp"
-			android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
-			tool:text="10 Vl" />
-	</LinearLayout>
+        <com.google.android.material.textview.MaterialTextView
+            android:id="@+id/boarding_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
+            tool:text="on demand" />
+    </LinearLayout>
 
-	<LinearLayout
-		android:id="@+id/congestion"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:gravity="center_vertical">
+    <LinearLayout
+        android:id="@+id/speed"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical">
+
+        <ImageView
+            android:id="@+id/speed_icon"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginEnd="8dp"
+            android:importantForAccessibility="no"
+            app:srcCompat="@drawable/speed" />
 
-		<ImageView
-			android:id="@+id/congestion_icon"
-			android:layout_width="16dp"
-			android:layout_height="16dp"
-			android:layout_marginEnd="8dp"
-			android:importantForAccessibility="no"
-			app:srcCompat="@drawable/traffic" />
+        <com.google.android.material.textview.MaterialTextView
+            android:id="@+id/speed_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
+            tool:text="10 Vl" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/congestion"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical">
+
+        <ImageView
+            android:id="@+id/congestion_icon"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginEnd="8dp"
+            android:importantForAccessibility="no"
+            app:srcCompat="@drawable/traffic" />
 
-		<com.google.android.material.textview.MaterialTextView
-			android:id="@+id/congestion_text"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_marginEnd="8dp"
-			android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
-			tool:text="smooth traffic" />
-	</LinearLayout>
+        <com.google.android.material.textview.MaterialTextView
+            android:id="@+id/congestion_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
+            tool:text="smooth traffic" />
+    </LinearLayout>
 
-	<LinearLayout
-		android:id="@+id/occupancy"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:gravity="center_vertical">
+    <LinearLayout
+        android:id="@+id/occupancy"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical">
 
-		<ImageView
-			android:id="@+id/occupancy_icon"
-			android:layout_width="16dp"
-			android:layout_height="16dp"
-			android:layout_marginEnd="8dp"
-			android:importantForAccessibility="no"
-			app:srcCompat="@drawable/crowd" />
+        <ImageView
+            android:id="@+id/occupancy_icon"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginEnd="8dp"
+            android:importantForAccessibility="no"
+            app:srcCompat="@drawable/crowd" />
 
-		<com.google.android.material.textview.MaterialTextView
-			android:id="@+id/occupancy_text"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_marginEnd="8dp"
-			android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
-			tool:text="empty vehicle" />
-	</LinearLayout>
+        <com.google.android.material.textview.MaterialTextView
+            android:id="@+id/occupancy_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
+            tool:text="empty vehicle" />
+    </LinearLayout>
 
-	<androidx.constraintlayout.helper.widget.Flow
-		android:id="@+id/capabilities"
-		android:layout_width="0dp"
-		android:layout_height="wrap_content"
-		android:layout_marginStart="8dp"
-		android:layout_marginTop="16dp"
-		android:layout_marginEnd="8dp"
-		app:constraint_referenced_ids="ac,bike,voice,ticket,usb"
-		app:flow_horizontalGap="4dp"
-		app:flow_horizontalStyle="packed"
-		app:flow_verticalGap="4dp"
-		app:flow_wrapMode="chain"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toBottomOf="@+id/info" />
+    <androidx.constraintlayout.helper.widget.Flow
+        android:id="@+id/capabilities"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="8dp"
+        app:constraint_referenced_ids="ac,bike,voice,ticket,usb"
+        app:flow_horizontalGap="4dp"
+        app:flow_horizontalStyle="packed"
+        app:flow_verticalGap="4dp"
+        app:flow_wrapMode="chain"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/info" />
 
-	<ImageView
-		android:id="@+id/ac"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:contentDescription="@string/air_condition_content_description"
-		app:srcCompat="@drawable/ac"
-		tool:ignore="MissingConstraints" />
+    <ImageView
+        android:id="@+id/ac"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:contentDescription="@string/air_condition_content_description"
+        app:srcCompat="@drawable/ac"
+        tool:ignore="MissingConstraints" />
 
-	<ImageView
-		android:id="@+id/bike"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:contentDescription="@string/bicycles_allowed_content_description"
-		app:srcCompat="@drawable/bike"
-		tool:ignore="MissingConstraints" />
+    <ImageView
+        android:id="@+id/bike"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:contentDescription="@string/bicycles_allowed_content_description"
+        app:srcCompat="@drawable/bike"
+        tool:ignore="MissingConstraints" />
 
-	<ImageView
-		android:id="@+id/voice"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:contentDescription="@string/voice_announcements_content_description"
-		app:srcCompat="@drawable/voice"
-		tool:ignore="MissingConstraints" />
+    <ImageView
+        android:id="@+id/voice"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:contentDescription="@string/voice_announcements_content_description"
+        app:srcCompat="@drawable/voice"
+        tool:ignore="MissingConstraints" />
 
-	<ImageView
-		android:id="@+id/ticket"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:contentDescription="@string/tickets_sold_content_description"
-		app:srcCompat="@drawable/ticket"
-		tool:ignore="MissingConstraints" />
+    <ImageView
+        android:id="@+id/ticket"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:contentDescription="@string/tickets_sold_content_description"
+        app:srcCompat="@drawable/ticket"
+        tool:ignore="MissingConstraints" />
 
-	<ImageView
-		android:id="@+id/usb"
-		android:layout_width="24dp"
-		android:layout_height="24dp"
-		android:contentDescription="@string/usb_charging_content_description"
-		app:srcCompat="@drawable/usb"
-		tool:ignore="MissingConstraints" />
+    <ImageView
+        android:id="@+id/usb"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:contentDescription="@string/usb_charging_content_description"
+        app:srcCompat="@drawable/usb"
+        tool:ignore="MissingConstraints" />
 
-	<LinearLayout
-		android:layout_marginTop="8dp"
-		android:id="@+id/alerts"
-		android:layout_width="match_parent"
-		android:layout_height="wrap_content"
-		android:gravity="center_vertical"
-		android:visibility="gone"
-		android:background="@color/safety"
-		app:layout_constraintTop_toBottomOf="@+id/capabilities">
+    <LinearLayout
+        android:id="@+id/alerts"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:background="@color/safety"
+        android:gravity="center_vertical"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@+id/capabilities">
 
-		<ImageView
-			android:layout_marginStart="8dp"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			app:tint="@color/black"
-			android:importantForAccessibility="no"
-			android:src="@drawable/warning" />
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:importantForAccessibility="no"
+            android:src="@drawable/warning"
+            app:tint="@color/black" />
 
-		<com.google.android.material.textview.MaterialTextView
-			android:id="@+id/alerts_text"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:layout_margin="8dp"
-			android:padding="8dp"
-			android:textColor="@color/black"
-			tool:text="Severe stops on Metropolitan line towards Tower Hill through Victoria" />
-	</LinearLayout>
+        <com.google.android.material.textview.MaterialTextView
+            android:id="@+id/alerts_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:padding="8dp"
+            android:textColor="@color/black"
+            tool:text="Severe stops on Metropolitan line towards Tower Hill through Victoria" />
+    </LinearLayout>
 
-	<org.osmdroid.views.MapView
-		android:id="@+id/map"
-		android:layout_width="match_parent"
-		android:layout_height="250dp"
-		android:layout_margin="16dp"
-		app:layout_constraintEnd_toEndOf="parent"
-		app:layout_constraintStart_toStartOf="parent"
-		app:layout_constraintTop_toBottomOf="@+id/alerts" />
+    <org.osmdroid.views.MapView
+        android:id="@+id/map"
+        android:layout_width="match_parent"
+        android:layout_height="250dp"
+        android:layout_margin="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/alerts" />
 </androidx.constraintlayout.widget.ConstraintLayout>




diff --git a/app/src/main/res/layout/favourite.xml b/app/src/main/res/layout/favourite.xml
index 7d47ac03dc672895ee185d4c471ffb19cf4847b7..c4175c5cfe973887027f17ba67804556823382f4 100644
--- a/app/src/main/res/layout/favourite.xml
+++ b/app/src/main/res/layout/favourite.xml
@@ -7,10 +7,10 @@  	xmlns:app="http://schemas.android.com/apk/res-auto"
 	xmlns:tool="http://schemas.android.com/tools"
 	android:id="@+id/favourite"
-	android:layout_marginTop="8dp"
-	android:layout_marginBottom="8dp"
 	android:layout_width="match_parent"
-	android:layout_height="wrap_content">
+	android:layout_height="wrap_content"
+	android:layout_marginTop="8dp"
+	android:layout_marginBottom="8dp">
 
 	<androidx.constraintlayout.widget.ConstraintLayout
 		android:layout_width="match_parent"
@@ -45,18 +45,60 @@ 			android:layout_height="24dp"
 			android:layout_marginStart="8dp"
 			app:layout_constraintBottom_toTopOf="@+id/departure_headsign"
 			app:layout_constraintStart_toStartOf="parent"
-			app:layout_constraintTop_toTopOf="@+id/departure_time"
+			app:layout_constraintTop_toTopOf="@+id/departure_line"
 			tool:ignore="ContentDescription"
 			tool:srcCompat="@drawable/bus_black" />
 
 		<com.google.android.material.textview.MaterialTextView
-			android:id="@+id/departure_time"
+			android:id="@+id/arrival_status"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_marginTop="8dp"
+			android:layout_marginEnd="8dp"
+			android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintTop_toTopOf="parent"
+			tool:text="approx. arr." />
+
+		<com.google.android.material.textview.MaterialTextView
+			android:id="@+id/arrival_time"
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
 			android:layout_marginEnd="8dp"
 			android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
 			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintTop_toBottomOf="@+id/arrival_status"
+			tool:text="1hr" />
+
+		<com.google.android.material.textview.MaterialTextView
+			android:id="@+id/arrival_full_time"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_marginTop="4dp"
+			android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
+			app:layout_constraintEnd_toEndOf="@+id/arrival_time"
+			app:layout_constraintTop_toBottomOf="@+id/arrival_time"
+			tool:text="18:55" />
+
+		<com.google.android.material.textview.MaterialTextView
+			android:id="@+id/departure_status"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_marginTop="8dp"
+			android:layout_marginEnd="8dp"
+			android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
+			app:layout_constraintEnd_toEndOf="parent"
 			app:layout_constraintTop_toTopOf="@+id/departure_line"
+			tool:text="approx. dep." />
+
+		<com.google.android.material.textview.MaterialTextView
+			android:id="@+id/departure_time"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_marginEnd="8dp"
+			android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintTop_toBottomOf="@+id/departure_status"
 			tool:text="1hr" />
 
 		<com.google.android.material.textview.MaterialTextView




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e8c8b70d098ddbdba4dd709f21c267dd8a8458e0..d7eabb6448290335cfad6b1b52b8943562ede311 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -149,9 +149,7 @@ 	Server
 	<string name="bimba_server_token_hint">Token</string>
 	<string name="realtime_content_description">departure is realtime</string>
 	<!-- cf timepoint field in https://gtfs.org/schedule/reference/#stop_timestxt -->
-	<string name="exact_content_description">departure time is exact from schedule</string>
-	<!-- cf timepoint field in https://gtfs.org/schedule/reference/#stop_timestxt -->
-	<string name="inexact_content_description">departure time is approximate from schedule</string>
+	<string name="schedule_content_description">departure time is from schedule</string>
 	<string name="wheelchair_content_description">vehicle is wheelchair accessible</string>
 	<string name="air_condition_content_description">air conditioning</string>
 	<string name="bicycles_allowed_content_description">bicycles allowed</string>
@@ -287,5 +285,11 @@ 	link to Matrix channel
 	<string name="email_button_description">link to email</string>
 	<string name="transitous_description">A community-run provider-neutral international public transport routing service. Coverage is available at https://transitous.org/sources/</string>
 	<string name="transitous_attribution">Transitous (https://transitous.org) API provided by Spline (https://routing.spline.de). Localities (https://github.com/public-transport/transitous/tree/main/feeds) maintained by the community.</string>
-    <string name="local_time">local time</string>
+	<string name="local_time">local time</string>
+    <string name="departure_arrived">arrived</string>
+	<string name="arrival_approximate">approx. arr.</string>
+	<string name="arrival">arrival</string>
+	<string name="departure_approximate">approx. dep.</string>
+	<string name="departure">departure</string>
+	<string name="approximately">approximately</string>
 </resources>




diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 042048b10e8ca72c5956032013ac943358cfae06..1f990cc3b25e064ea38ef5e14bc97f87aa2297e5 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -58,8 +58,8 @@ 		9sp
 	</style>
 
 	<style name="Theme.Bimba.Splash" parent="Theme.SplashScreen">
-		<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
-		<item name="windowSplashScreenIconBackgroundColor">@color/ic_launcher_background</item>
+		<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
+		<item name="windowSplashScreenBackground">?attr/colorSurface</item>
 		<item name="postSplashScreenTheme">@style/Theme.Bimba</item>
 	</style>
 




diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml
index ed15c05948b2e42e983638a24f3c885597f30c96..bdc076567a58b017c5a152746e2ba04f6e14a2b6 100644
--- a/app/src/main/res/values-en-rGB/strings.xml
+++ b/app/src/main/res/values-en-rGB/strings.xml
@@ -147,9 +147,7 @@ 	Server
 	<string name="bimba_server_token_hint">Token</string>
 	<string name="realtime_content_description">departure is realtime</string>
 	<!-- cf timepoint field in https://gtfs.org/schedule/reference/#stop_timestxt -->
-	<string name="exact_content_description">departure time is exact from schedule</string>
-	<!-- cf timepoint field in https://gtfs.org/schedule/reference/#stop_timestxt -->
-	<string name="inexact_content_description">departure time is approximate from schedule</string>
+	<string name="schedule_content_description">departure time is from schedule</string>
 	<string name="wheelchair_content_description">vehicle is wheelchair accessible</string>
 	<string name="air_condition_content_description">air conditioning</string>
 	<string name="bicycles_allowed_content_description">bicycles allowed</string>
@@ -283,4 +281,13 @@ 	show
 	<string name="terminus_arrival_showing">Terminus arrivals</string>
 	<string name="matrix_button_description">link to Matrix channel</string>
 	<string name="email_button_description">link to email</string>
+	<string name="transitous_description">A community-run provider-neutral international public transport routing service. Coverage is available at https://transitous.org/sources/</string>
+	<string name="transitous_attribution">Transitous (https://transitous.org) API provided by Spline (https://routing.spline.de). Localities (https://github.com/public-transport/transitous/tree/main/feeds) maintained by the community.</string>
+	<string name="local_time">local time</string>
+    <string name="departure_arrived">arrived</string>
+	<string name="arrival_approximate">approx. arr.</string>
+	<string name="arrival">arrival</string>
+	<string name="departure_approximate">approx. dep.</string>
+	<string name="departure">departure</string>
+	<string name="approximately">approximately</string>
 </resources>




diff --git a/app/src/main/res/values-en-rUS/strings.xml b/app/src/main/res/values-en-rUS/strings.xml
index 1f2aa2c99cc598244676fb0917da77ac392dfb6f..075852ab7b5dd2b4fc0fca4e326a543a1ea2f949 100644
--- a/app/src/main/res/values-en-rUS/strings.xml
+++ b/app/src/main/res/values-en-rUS/strings.xml
@@ -103,8 +103,7 @@ 	Results for ‘%1$s’
 	<string name="bimba_server_address_hint">Server</string>
 	<string name="bimba_server_token_hint">Token</string>
 	<string name="realtime_content_description">departure is realtime</string>
-	<string name="exact_content_description">departure time is exact from schedule</string>
-	<string name="inexact_content_description">departure time is approximate</string>
+	<string name="schedule_content_description">departure time is from schedule</string>
 	<string name="wheelchair_content_description">vehicle is wheelchair accessible</string>
 	<string name="air_condition_content_description">air conditioning</string>
 	<string name="bicycles_allowed_content_description">bicycles allowed</string>
@@ -280,4 +279,13 @@ 	Terminus arrivals
 	<string name="matrix_button_description">link to Matrix channel</string>
 	<string name="email_button_description">link to email</string>
 	<string name="no_email_app">No email app installed</string>
+	<string name="transitous_description">A community-run provider-neutral international public transport routing service. Coverage is available at https://transitous.org/sources/</string>
+	<string name="transitous_attribution">Transitous (https://transitous.org) API provided by Spline (https://routing.spline.de). Localities (https://github.com/public-transport/transitous/tree/main/feeds) maintained by the community.</string>
+	<string name="local_time">local time</string>
+    <string name="departure_arrived">arrived</string>
+	<string name="arrival_approximate">approx. arr.</string>
+	<string name="arrival">arrival</string>
+	<string name="departure_approximate">approx. dep.</string>
+	<string name="departure">departure</string>
+	<string name="approximately">approximately</string>
 </resources>
\ No newline at end of file




diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index ea505321830dc85be8e4569db6f02aed29708d58..0480a5fed0a434e65ffaebf0d90a926db77025fe 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -31,7 +31,7 @@         %1$s dunatim
         <item quantity="other">%1$s dunatimmi</item>
     </plurals>
     <string name="speed_in_m_per_s">%1$s m/s</string>
-    <string name="time_in_tm">%1$s %2$s tm</string>
+    <string name="time_in_tm">%1$s %2$sTm</string>
     <string name="speed_in_km_per_h">%1$s km/h</string>
     <plurals name="speed_in_vl_cd">
         <item quantity="one">%1$d vlos</item>
@@ -70,10 +70,8 @@     %1$s «» %2$s
     <string name="results_for">Otsingutulemused: „%1$s“</string>
     <string name="bimba_server_address_hint">Server</string>
     <string name="server_rate_limited_question">See server kasutab päringumahu piiranguid ning ligipääsutunnus on sisestamata. Kas sa soovid jätkata?</string>
-    <string name="inexact_content_description">umbkaudne väljumise aeg sõiduplaani järgi</string>
-    <string name="server_private_question">See on privaatne server ning ligipääsutunnus on sisestamata</string>
+	<string name="server_private_question">See on privaatne server ning ligipääsutunnus on sisestamata</string>
     <string name="realtime_content_description">väljumine on reaalajas</string>
-    <string name="exact_content_description">väljumise aeg on sõiduplaani järgi</string>
     <string name="voice_announcements_content_description">kasutusel on häälteated</string>
     <string name="tickets_sold_content_description">transpordivahendis müüakse sõidupileteid</string>
     <string name="usb_charging_content_description">USB-põhised laadimispordid on olemas</string>
@@ -109,7 +107,7 @@     
         <item quantity="one">%1$d miil</item>
         <item quantity="other">%1$d miili</item>
     </plurals>
-    <string name="distance_in_gf">%1$s %2$s gf</string>
+    <string name="distance_in_gf">%1$s %2$sGf</string>
     <plurals name="distance_in_gf_cd">
         <item quantity="one">%1$d grafut</item>
         <item quantity="other">%1$d grafutit</item>
@@ -123,7 +121,7 @@         %1$s grafut
         <item quantity="other">%1$s grafutit</item>
     </plurals>
     <string name="time_in_s">%1$s sek</string>
-    <string name="time_in_tm_past">%1$s %2$s tm tagasi</string>
+    <string name="time_in_tm_past">%1$s %2$sTm tagasi</string>
     <plurals name="time_in_tm_cd">
         <item quantity="one">%1$d tim</item>
         <item quantity="other">%1$d timmi</item>




diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 4d562ae17b6049e59da601a2d07bf785f34e9ba3..604046478c874a3e78a2d33e137b169e1a3cbd7c 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -203,8 +203,6 @@     %1$s et %2$s
     <string name="time_in_s">%1$s s</string>
     <string name="map_attribution">© contributeurs d’&lt;a href=https://www.openstreetmap.org/copyright&gt;OpenStreetMap&lt;/a&gt;</string>
     <string name="no_email_app">Aucune application d’e-mail installée</string>
-    <string name="exact_content_description">L\'heure de départ est exacte d\'après l\'horaire planifié</string>
-    <string name="inexact_content_description">l\'heure de départ est approximative d\'après l\'horaire planifié</string>
     <string name="favourite_content_description">Enregistrer comme favori</string>
     <string name="unfiltered">Non filtré</string>
     <string name="cannot_save_favourite">Impossible d’enregistrer le favori</string>
@@ -244,4 +242,4 @@     La requête contient un code short plus mais il n\'y a pas de données de géocodage présentes. Téléchargez les données de géocodage ou activez leur mise à jour automatique dans les paramètres.
     <string name="congestion_congestion">ralentissements</string>
     <string name="about_time">environ %1$02d:%2$02d</string>
     <string name="italics">italique</string>
-</resources>
\ No newline at end of file
+</resources>




diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index d29d58e37fb7dea6fd60716bf2a9e18b7c6d2aee..9e162712580cf6ac01c38c1dec5c8a8337d52f9f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -48,7 +48,7 @@     za moment
     <string name="departure_departed">odjechał</string>
     <string name="departure_now">teraz</string>
     <string name="at_time">o %1$02d:%2$02d</string>
-    <string name="about_time">about %1$02d:%2$02d</string>
+    <string name="about_time">około %1$02d:%2$02d</string>
     <string name="at_time_realtime">o %1$02d:%2$02d:%3$02d</string>
     <string name="on_demand">na żądanie</string>
     <string name="no_boarding">brak</string>
@@ -64,8 +64,7 @@     Wyniki dla „%1$s”
     <string name="bimba_server_address_hint">Serwer</string>
     <string name="bimba_server_token_hint">Żeton</string>
     <string name="realtime_content_description">odjazd w czasie rzeczywistym</string>
-    <string name="exact_content_description">czas odjazdu jest dokładny z rozkładu</string>
-    <string name="inexact_content_description">czas odjazdu jest przybliżony</string>
+    <string name="schedule_content_description">czas odjazdu jest z rozkładu</string>
     <string name="wheelchair_content_description">pojazd ma niską podłogę</string>
     <string name="air_condition_content_description">klimatyzacja</string>
     <string name="bicycles_allowed_content_description">przewóz rowerów dozwolony</string>
@@ -301,4 +300,12 @@     Przyjazdy na pętle
     <string name="matrix_button_description">link do kanału na Matrixie</string>
     <string name="email_button_description">link do e-maila</string>
     <string name="no_email_app">Brak aplikacji e-mail</string>
+    <string name="transitous_attribution">API Transitous (https://transitous.org) dostarczane przez Spline (https://routing.spline.de). Lokalizacje (https://github.com/public-transport/transitous/tree/main/feeds) utrzymywane przez społeczność.</string>
+    <string name="local_time">czas lokalny</string>
+    <string name="departure_arrived">przyjechał</string>
+    <string name="arrival_approximate">przyb. przyj.</string>
+    <string name="arrival">przyjazd</string>
+    <string name="departure_approximate">przyb. odj.</string>
+    <string name="departure">odjazd</string>
+    <string name="approximately">w przybliżeniu</string>
 </resources>
\ No newline at end of file




diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml
index 1b04dfb059a09665e1aa20100b1a0e4b5f19d4cb..f07ff67b177ed5e11ed481990ae1e34133dc4e3c 100644
--- a/app/src/main/res/values-v31/themes.xml
+++ b/app/src/main/res/values-v31/themes.xml
@@ -58,12 +58,12 @@ 		9sp
 	</style>
 
 	<style name="Theme.Bimba.Splash" parent="Theme.SplashScreen">
-		<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
-		<item name="windowSplashScreenIconBackgroundColor">@color/ic_launcher_background</item>
+		<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
+		<item name="windowSplashScreenBackground">?attr/colorSurface</item>
 		<item name="postSplashScreenTheme">@style/Theme.Bimba</item>
 	</style>
 
 	<style name="Preference.SwitchPreferenceCompat" parent="@style/Preference.SwitchPreferenceCompat.Material" tool:ignore="ResourceCycle">
 		<item name="widgetLayout">@layout/preferences_switch_material</item>
 	</style>
-</resources>
\ No newline at end of file
+</resources>




diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
index 52ded369d35deb8e357f2bba5c042a0adc2de048..c590b8026cb06e551b4fc892f4f8c1065b4166e6 100644
--- a/app/src/main/res/xml/locales_config.xml
+++ b/app/src/main/res/xml/locales_config.xml
@@ -8,9 +8,11 @@ -->
 
 <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
     <locale android:name="en"/>
-    <locale android:name="pl"/>
-    <locale android:name="it"/>
     <locale android:name="de"/>
-    <locale android:name="fr"/>
+    <locale android:name="en-rGB"/>
     <locale android:name="en-rUS"/>
+    <locale android:name="et"/>
+    <locale android:name="fr"/>
+    <locale android:name="it"/>
+    <locale android:name="pl"/>
 </locale-config>
\ No newline at end of file




diff --git a/bimba.svg b/bimba.svg
index 8bfa5182d6af0ad1a4cfa32f9383bcb8c1844c9a..42fb98d50eb985b418012cbfa8a92189e645c834 100644
--- a/bimba.svg
+++ b/bimba.svg
@@ -4,14 +4,15 @@ SPDX-FileCopyrightText: https://github.com/tebriz159
 
 SPDX-License-Identifier: GPL-3.0-or-later
 -->
+
 <svg
-   viewBox="0 0 91.999998 92"
+   viewBox="0 0 47.999999 48"
    id="vector"
    version="1.1"
    sodipodi:docname="bimba.svg"
-   width="92"
-   height="92"
-   inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
+   width="48"
+   height="48"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
    inkscape:export-filename="bimba.png"
    inkscape:export-xdpi="534.26086"
    inkscape:export-ydpi="534.26086"
@@ -48,80 +49,83 @@      inkscape:window-width="1504"
      inkscape:window-height="1002"
      id="namedview172"
      showgrid="false"
-     fit-margin-top="8"
-     fit-margin-left="8"
-     fit-margin-bottom="8"
-     fit-margin-right="8"
-     inkscape:zoom="8.5108696"
-     inkscape:cx="46"
-     inkscape:cy="-26.965517"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
+     fit-margin-top="4"
+     fit-margin-left="4"
+     fit-margin-bottom="4"
+     fit-margin-right="4"
+     inkscape:zoom="8.05"
+     inkscape:cx="26.459627"
+     inkscape:cy="19.006211"
+     inkscape:window-x="56"
+     inkscape:window-y="50"
      inkscape:window-maximized="0"
      inkscape:current-layer="vector"
      inkscape:showpageshadow="0"
      inkscape:pagecheckerboard="0"
      inkscape:deskcolor="#505050" />
-  <rect
-     id="rect178"
-     width="92"
-     height="92"
-     x="0"
-     y="0"
-     style="display:inline;fill:#3a3a3b;fill-opacity:1;stroke-width:0.436372"
-     ry="0" />
   <g
-     id="group_1"
-     transform="matrix(0.16904335,0,0,0.16904335,-7.9999769,-8)"
-     style="display:inline">
+     id="g1252"
+     transform="matrix(0.43478261,0,0,0.43478261,4,4)">
+    <path
+       fill="none"
+       stroke="#000000"
+       stroke-width="0.645279"
+       d="M 46,92 C 66.156946,92 77.36246,92 84.681245,84.681247 92,77.362462 92,66.15695 92,46.000001 92,25.843058 92,14.637536 84.681245,7.3187613 77.36246,0 66.156946,0 46,0 25.843059,0 14.637536,0 7.3187622,7.3187613 0,14.637536 0,25.843058 0,46.000001 0,66.15695 0,77.362462 7.3187622,84.681247 14.637536,92 25.843059,92 46,92 Z"
+       id="path922"
+       style="fill:#3a3a3b;fill-opacity:1;stroke:none" />
     <g
-       id="group"
-       transform="translate(173.03255,173.03255)">
-      <path
-         id="path"
-         d="m 62.962,292.5 v 0 c -3.7,-0.9 -5.7,-3.8 -4.3,-6.5 l 32.3,-62.9 c 1.3,-2.6 5.5,-4 9.2,-3 v 0 c 3.7,0.9 5.7,3.8 4.3,6.5 l -32.3,62.9 c -1.3,2.6 -5.4,4 -9.2,3 z"
-         inkscape:connector-curvature="0"
-         style="fill:#54af39" />
-      <path
-         id="path_1"
-         d="m 229.662,292.5 v 0 c 3.7,-0.9 5.7,-3.8 4.3,-6.5 l -32.3,-62.9 c -1.3,-2.6 -5.5,-4 -9.2,-3 v 0 c -3.7,0.9 -5.7,3.8 -4.3,6.5 l 32.3,62.9 c 1.3,2.6 5.5,4 9.2,3 z"
-         inkscape:connector-curvature="0"
-         style="fill:#54af39" />
-      <path
-         id="path_2"
-         d="m 151.362,36.9 -0.6,0.2 c -1.8,0.7 -3.8,-0.3 -4.5,-2.1 l -9.6,-26.7 c -0.7,-1.8 0.3,-3.8 2.1,-4.5 l 0.6,-0.2 c 1.8,-0.7 3.8,0.3 4.5,2.1 l 9.6,26.7 c 0.7,1.9 -0.3,3.9 -2.1,4.5 z"
-         inkscape:connector-curvature="0"
-         style="fill:#54af39" />
-      <path
-         id="path_3"
-         d="m 180.662,3.8 v 0 c 0,2.1 -1.7,3.8 -3.8,3.8 h -61 c -2.1,0 -3.8,-1.7 -3.8,-3.8 v 0 c 0,-2.1 1.7,-3.8 3.8,-3.8 h 61 c 2.1,0 3.8,1.7 3.8,3.8 z"
-         inkscape:connector-curvature="0"
-         style="fill:#54af39" />
-      <path
-         id="path_4"
-         d="m 218.762,236.7 h -144.9 c -13.3,0 -24,-10.8 -24,-24 v -108 c 0,-41.3 33.5,-74.9 74.9,-74.9 h 43.3 c 41.3,0 74.9,33.5 74.9,74.9 v 108 c -0.1,13.2 -10.9,24 -24.2,24 z"
-         inkscape:connector-curvature="0"
-         style="fill:#54af39" />
-      <path
-         id="path_5"
-         d="m 212.562,146.2 h -132.5 c -5.6,0 -10.2,-4.5 -10.2,-10.2 v -34.9 c 0,-16.9 13.7,-30.6 30.6,-30.6 h 91.7 c 16.9,0 30.6,13.7 30.6,30.6 V 136 c -0.1,5.7 -4.6,10.2 -10.2,10.2 z"
-         inkscape:connector-curvature="0"
-         style="fill:#ffffff" />
-      <path
-         id="path_6"
-         d="m 161.462,55.5 h -30.3 c -3.2,0 -5.7,-2.6 -5.7,-5.7 v 0 c 0,-3.2 2.6,-5.7 5.7,-5.7 h 30.3 c 3.2,0 5.7,2.6 5.7,5.7 v 0 c 0,3.1 -2.6,5.7 -5.7,5.7 z"
-         inkscape:connector-curvature="0"
-         style="fill:#ffffff" />
-      <path
-         id="path_7"
-         d="m 87.062,191.9 m -14.8,0 c 0,-3.924 1.56,-7.691 4.335,-10.465 2.774,-2.775 6.541,-4.335 10.465,-4.335 3.924,0 7.691,1.56 10.465,4.335 2.775,2.774 4.335,6.541 4.335,10.465 0,3.924 -1.56,7.691 -4.335,10.465 -2.774,2.775 -6.541,4.335 -10.465,4.335 -3.924,0 -7.691,-1.56 -10.465,-4.335 -2.775,-2.774 -4.335,-6.541 -4.335,-10.465"
-         inkscape:connector-curvature="0"
-         style="fill:#ffffff" />
-      <path
-         id="path_8"
-         d="m 205.662,191.9 m -14.8,0 c 0,-3.924 1.56,-7.691 4.335,-10.465 2.774,-2.775 6.541,-4.335 10.465,-4.335 3.924,0 7.691,1.56 10.465,4.335 2.775,2.774 4.335,6.541 4.335,10.465 0,3.924 -1.56,7.691 -4.335,10.465 -2.774,2.775 -6.541,4.335 -10.465,4.335 -3.924,0 -7.691,-1.56 -10.465,-4.335 -2.775,-2.774 -4.335,-6.541 -4.335,-10.465"
-         inkscape:connector-curvature="0"
-         style="fill:#ffffff" />
+       id="group_1"
+       transform="matrix(0.16904335,0,0,0.16904335,-7.9999769,-8)"
+       style="display:inline">
+      <g
+         id="group"
+         transform="translate(173.03255,173.03255)">
+        <path
+           id="path"
+           d="m 62.962,292.5 v 0 c -3.7,-0.9 -5.7,-3.8 -4.3,-6.5 l 32.3,-62.9 c 1.3,-2.6 5.5,-4 9.2,-3 v 0 c 3.7,0.9 5.7,3.8 4.3,6.5 l -32.3,62.9 c -1.3,2.6 -5.4,4 -9.2,3 z"
+           inkscape:connector-curvature="0"
+           style="fill:#54af39" />
+        <path
+           id="path_1"
+           d="m 229.662,292.5 v 0 c 3.7,-0.9 5.7,-3.8 4.3,-6.5 l -32.3,-62.9 c -1.3,-2.6 -5.5,-4 -9.2,-3 v 0 c -3.7,0.9 -5.7,3.8 -4.3,6.5 l 32.3,62.9 c 1.3,2.6 5.5,4 9.2,3 z"
+           inkscape:connector-curvature="0"
+           style="fill:#54af39" />
+        <path
+           id="path_2"
+           d="m 151.362,36.9 -0.6,0.2 c -1.8,0.7 -3.8,-0.3 -4.5,-2.1 l -9.6,-26.7 c -0.7,-1.8 0.3,-3.8 2.1,-4.5 l 0.6,-0.2 c 1.8,-0.7 3.8,0.3 4.5,2.1 l 9.6,26.7 c 0.7,1.9 -0.3,3.9 -2.1,4.5 z"
+           inkscape:connector-curvature="0"
+           style="fill:#54af39" />
+        <path
+           id="path_3"
+           d="m 180.662,3.8 v 0 c 0,2.1 -1.7,3.8 -3.8,3.8 h -61 c -2.1,0 -3.8,-1.7 -3.8,-3.8 v 0 c 0,-2.1 1.7,-3.8 3.8,-3.8 h 61 c 2.1,0 3.8,1.7 3.8,3.8 z"
+           inkscape:connector-curvature="0"
+           style="fill:#54af39" />
+        <path
+           id="path_4"
+           d="m 218.762,236.7 h -144.9 c -13.3,0 -24,-10.8 -24,-24 v -108 c 0,-41.3 33.5,-74.9 74.9,-74.9 h 43.3 c 41.3,0 74.9,33.5 74.9,74.9 v 108 c -0.1,13.2 -10.9,24 -24.2,24 z"
+           inkscape:connector-curvature="0"
+           style="fill:#54af39" />
+        <path
+           id="path_5"
+           d="m 212.562,146.2 h -132.5 c -5.6,0 -10.2,-4.5 -10.2,-10.2 v -34.9 c 0,-16.9 13.7,-30.6 30.6,-30.6 h 91.7 c 16.9,0 30.6,13.7 30.6,30.6 V 136 c -0.1,5.7 -4.6,10.2 -10.2,10.2 z"
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff" />
+        <path
+           id="path_6"
+           d="m 161.462,55.5 h -30.3 c -3.2,0 -5.7,-2.6 -5.7,-5.7 v 0 c 0,-3.2 2.6,-5.7 5.7,-5.7 h 30.3 c 3.2,0 5.7,2.6 5.7,5.7 v 0 c 0,3.1 -2.6,5.7 -5.7,5.7 z"
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff" />
+        <path
+           id="path_7"
+           d="m 87.062,191.9 m -14.8,0 c 0,-3.924 1.56,-7.691 4.335,-10.465 2.774,-2.775 6.541,-4.335 10.465,-4.335 3.924,0 7.691,1.56 10.465,4.335 2.775,2.774 4.335,6.541 4.335,10.465 0,3.924 -1.56,7.691 -4.335,10.465 -2.774,2.775 -6.541,4.335 -10.465,4.335 -3.924,0 -7.691,-1.56 -10.465,-4.335 -2.775,-2.774 -4.335,-6.541 -4.335,-10.465"
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff" />
+        <path
+           id="path_8"
+           d="m 205.662,191.9 m -14.8,0 c 0,-3.924 1.56,-7.691 4.335,-10.465 2.774,-2.775 6.541,-4.335 10.465,-4.335 3.924,0 7.691,1.56 10.465,4.335 2.775,2.774 4.335,6.541 4.335,10.465 0,3.924 -1.56,7.691 -4.335,10.465 -2.774,2.775 -6.541,4.335 -10.465,4.335 -3.924,0 -7.691,-1.56 -10.465,-4.335 -2.775,-2.774 -4.335,-6.541 -4.335,-10.465"
+           inkscape:connector-curvature="0"
+           style="fill:#ffffff" />
+      </g>
     </g>
   </g>
   <g




diff --git a/build.gradle.kts b/build.gradle.kts
index 7a3bc819960b47c4f4e7c762aa8cc8e828d14f11..86f0ddbf3403895e1c7ff8b91dd0849b8edda743 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,8 +4,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later
 
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 plugins {
-    id("com.android.application") version "8.7.0" apply false
-    id("com.android.library") version "8.7.0" apply false
+    id("com.android.application") version "8.7.2" apply false
+    id("com.android.library") version "8.7.2" apply false
     kotlin("android") version "2.0.10" apply false
     kotlin("jvm") version "1.7.20" apply false
     kotlin("plugin.parcelize") version "1.8.20" apply false




diff --git a/metadata/en/changelogs/32.txt b/metadata/en/changelogs/32.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f4add26464b804086eccfaebbaf8250a4b6d8452
--- /dev/null
+++ b/metadata/en/changelogs/32.txt
@@ -0,0 +1,2 @@
+* Added basic Transitous support
+* Fixed phantom localities names in search results




diff --git a/metadata/en/changelogs/33.txt b/metadata/en/changelogs/33.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2373678058a8b01c40301f6d459695a67eb52f91
--- /dev/null
+++ b/metadata/en/changelogs/33.txt
@@ -0,0 +1,5 @@
+* updated dependencies
+* prepared for separate arrival and departure times
+* improve styling
+* added Estonian language (Priit Jõerüüt)
+* improve translations (Lars K, Nicolas DERIVE, stormax181iq)




diff --git a/metadata/en-US/changelogs/32.txt b/metadata/en-US/changelogs/32.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f4add26464b804086eccfaebbaf8250a4b6d8452
--- /dev/null
+++ b/metadata/en-US/changelogs/32.txt
@@ -0,0 +1,2 @@
+* Added basic Transitous support
+* Fixed phantom localities names in search results




diff --git a/metadata/en-US/changelogs/33.txt b/metadata/en-US/changelogs/33.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2373678058a8b01c40301f6d459695a67eb52f91
--- /dev/null
+++ b/metadata/en-US/changelogs/33.txt
@@ -0,0 +1,5 @@
+* updated dependencies
+* prepared for separate arrival and departure times
+* improve styling
+* added Estonian language (Priit Jõerüüt)
+* improve translations (Lars K, Nicolas DERIVE, stormax181iq)




diff --git a/metadata/pl-PL/changelogs/32.txt b/metadata/pl-PL/changelogs/32.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4eaba3d3347c664e684ced97d5c921f52eda0670
--- /dev/null
+++ b/metadata/pl-PL/changelogs/32.txt
@@ -0,0 +1,2 @@
+* Dodano podstawową obsługę Transitous
+* Naprawiono fantomowe nazwy lokalizacji w wynikach wyszukiwania




diff --git a/metadata/pl-PL/changelogs/33.txt b/metadata/pl-PL/changelogs/33.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b61d6517e93169f3833d5e9fa8430770d139962
--- /dev/null
+++ b/metadata/pl-PL/changelogs/33.txt
@@ -0,0 +1,5 @@
+* zaktualizowano zależności
+* przygotowano pod oddzielne czasy przyjazdu i odjazdu
+* poprawiono ostylowanie
+* dodano język estoński (Priit Jõerüüt)
+* poprawiono tłumaczenia (Lars K, Nicolas DERIVE, stormax181iq)




diff --git a/release.sh b/release.sh
index 62122ff378c7bd472d9e463b2b145ef1637b4669..7b8c6671ca16a510eb4014d883fdcc84505f215c 100755
--- a/release.sh
+++ b/release.sh
@@ -62,13 +62,21 @@ 	latestCIstarted=$(echo "$latestCI" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+\+[0-9]{2}:[0-9]{2}' | head -n1)
 	while [ "$latestCIstatus" != 'OK' ] && [ "$retry" = "1" ]
 	do
 		echo "latest CI started at $latestCIstarted result is $latestCIstatus, not OK"
-		echo "retry? [y/N]"
+		echo "retry? [y/N/s]"
 		read -r decision
-		if [ "$decision" != 'y' ]
+		if [ "$decision" = 'N' ]
 		then
 			retry="0"
 			exit 1
 		fi
+		if [ "$decision" = 's' ]
+		then
+			retry="0"
+			break
+		fi
+		latestCI=$(curl https://ci.apiote.xyz/toys/czwek-commitly/latest 2>/dev/null)
+		latestCIstatus=$(echo "$latestCI" | grep '<h2' | sed 's/<h2[^>]*>//' | sed 's|</h2>||' | grep -oE '[A-Z]+')
+		latestCIstarted=$(echo "$latestCI" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+\+[0-9]{2}:[0-9]{2}' | head -n1)
 	done
 
 	currentVersionName=$(grep -Eo 'versionName = "[0-9\.]+"' app/build.gradle.kts | cut -d '=' -f2 | tr -d ' "')
@@ -90,7 +98,7 @@ 	sed -i "s/versionCode = $currentVersionCode/versionCode = $newVersionCode/" app/build.gradle.kts
 
 	git shortlog "v${currentVersionName}..HEAD" >> "metadata/en-US/changelogs/$newVersionCode.txt"
 
-	echo "time to update changelogs"
+	echo "time to update changelogs and README"
 elif [ $phase -eq 1 ]
 then
 	newVersionName=$(grep -Eo 'versionName = "[0-9\.]+"' app/build.gradle.kts | cut -d '=' -f2 | tr -d ' "')
@@ -106,6 +114,7 @@ 		fi
 	fi
 	git add app/build.gradle.kts
 	git add metadata/
+	git add README.adoc
 	git commit -S -m "release version $newVersionName ($newVersionCode)" || true
 	echo 'pushing …'
 	git push