Bimba.git

commit e5119fad29a5efcfc4c404387455eadd27b245fb

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

Merge branch 'develop'

%!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/README.adoc b/README.adoc
index 0e385ff83cfed168c84862a4776cf1669e2959ad..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.7.0 2024-10-15
+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 2048280d5c542d1f7157d718aa9dc8d64c8fb584..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 = 32
-		versionName = "3.7.0"
+		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 {
@@ -39,8 +39,8 @@ 		resValue("string", "applicationId", applicationId)
 	}
 
 	compileOptions {
-		sourceCompatibility = JavaVersion.VERSION_17
-		targetCompatibility = JavaVersion.VERSION_17
+		sourceCompatibility = JavaVersion.VERSION_21
+		targetCompatibility = JavaVersion.VERSION_21
 		isCoreLibraryDesugaringEnabled = true
 	}
 
@@ -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 f0881e76ecbc33476956161aa61f7919fde2a3d0..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>
@@ -288,4 +286,10 @@ 	link to email
 	<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/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 e39bfc51ee91e735e1e5a29982850c5b76cfddd5..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>
@@ -286,4 +284,10 @@ 	link to email
 	<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 e41b22e601cc5d006112aa47cb3f0a9ea341921a..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>
@@ -283,4 +282,10 @@ 	No email app installed
 	<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
new file mode 100644
index 0000000000000000000000000000000000000000..0480a5fed0a434e65ffaebf0d90a926db77025fe
--- /dev/null
+++ b/app/src/main/res/values-et/strings.xml
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="error_403">Sinu sisestatud ligipääsutunnus pole õige</string>
+    <string name="error_404">Otsitavat ei leidu</string>
+    <string name="error_50x">Serveris tekkis viga. Palun proovi hiljem uuesti</string>
+    <string name="error_unknown">Tekkis tundmatu viga</string>
+    <string name="error_connecting">Ühenduse loomisel serveriga tekkis viga. Palun proovi hiljem uuesti</string>
+    <string name="no_departures">Väljumisi pole</string>
+    <string name="error_gps">Praegust asukohta ei õnnestu tuvastada</string>
+    <string name="vehicle_headsign"><annotation decoration="apply"><annotation arg="0">%1$s</annotation></annotation> » <annotation arg="1">%2$s</annotation></string>
+    <plurals name="distance_in_m_cd">
+        <item quantity="one">%1$d meeter</item>
+        <item quantity="other">%1$d meetrit</item>
+    </plurals>
+    <string name="distance_in_two_units_cd">%1$s ja %2$s</string>
+    <plurals name="distance_in_yd_cd">
+        <item quantity="one">%1$d jard</item>
+        <item quantity="other">%1$d jardi</item>
+    </plurals>
+    <plurals name="distance_in_ft_cd">
+        <item quantity="one">%1$d jalg</item>
+        <item quantity="other">%1$d jalga</item>
+    </plurals>
+    <string name="distance_in_ft">%1$s jlg</string>
+    <plurals name="time_in_s_cd">
+        <item quantity="one">%1$d sekund</item>
+        <item quantity="other">%1$d sekundit</item>
+    </plurals>
+    <plurals name="time_in_2tm_12_cd">
+        <item quantity="one">%1$s dunatim</item>
+        <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$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>
+        <item quantity="other">%1$d vlos</item>
+    </plurals>
+    <string name="speed_in_mi_per_h">%1$s mt</string>
+    <string name="speed_in_vl">%1$s Vl</string>
+    <plurals name="speed_in_m_per_s_cd">
+        <item quantity="one">%1$d meeter sekundis</item>
+        <item quantity="other">%1$d meetrit sekundis</item>
+    </plurals>
+    <plurals name="speed_in_km_per_h_cd">
+        <item quantity="one">%1$d kilomeeter tunnis</item>
+        <item quantity="other">%1$d kilomeetrit tunnis</item>
+    </plurals>
+    <plurals name="speed_in_mi_per_h_cd">
+        <item quantity="one">%1$d miili tunnis</item>
+        <item quantity="other">%1$d miili tunnis</item>
+    </plurals>
+    <plurals name="speed_in_vl_12_cd">
+        <item quantity="one">%1$s vlos</item>
+        <item quantity="other">%1$s vlos</item>
+    </plurals>
+    <string name="congestion_stop_and_go">liigub ja peatub</string>
+    <string name="no_email_app">Nutiseadmes ei leidu ühtegi e-postirakendust</string>
+    <string name="occupancy_standing_only">vaid seisukohad</string>
+    <string name="congestion_jams">suur ummik</string>
+    <string name="occupancy_many_seats">palju vabu kohti</string>
+    <string name="occupancy_full">täiesti täis</string>
+    <string name="occupancy_few_seats">mõned vabad kohad</string>
+    <string name="about_time">umbes %1$02d:%2$02d</string>
+    <string name="departure_momentarily">just nüüd</string>
+    <string name="off_boarding">mahatulek</string>
+    <string name="line_headsign">» %1$s</string>
+    <string name="line_headsigns">%1$s «» %2$s</string>
+    <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="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="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>
+    <string name="open_in_maps_app">Ava kaardirakenduses</string>
+    <string name="onboarding_advanced">Keerukamalt</string>
+    <string name="seatbelts_everyone">Palun kinnitage turvavööd!</string>
+    <string name="onboarding_simple">Lihtsalt</string>
+    <string name="error">Viga</string>
+    <string name="cancel">Katkesta</string>
+    <string name="rate_limit">Päringute piir</string>
+    <string name="title_servers">Serverid</string>
+    <string name="title_cities">Piirkonnad</string>
+    <string name="app_name">Bimba</string>
+    <string name="title_home">Avaleht</string>
+    <string name="title_map">Kaart</string>
+    <string name="title_journey">Reis</string>
+    <string name="home_fab_description">GPSi ikoon</string>
+    <string name="search_placeholder">peatus, liin või OLC kood</string>
+    <string name="save">Salvesta</string>
+    <string name="error_401">Selle serveri kasutamiseks on vajalik ligipääsutunnus</string>
+    <string name="error_429">Päringud on liiga sagedased. Palun proovi hiljem uuesti</string>
+    <string name="error_offline">Sa poel võrgus. Palun loo nutiseadmes võrguühendus</string>
+    <string name="waiting_position">Ootame asukoha tuvastamist</string>
+    <string name="distance_in_m">%1$s m</string>
+    <string name="distance_in_km">%1$s km</string>
+    <plurals name="distance_in_km_cd">
+        <item quantity="one">%1$d kilomeeter</item>
+        <item quantity="other">%1$d kilomeetrit</item>
+    </plurals>
+    <string name="distance_in_yd">%1$s jd</string>
+    <string name="distance_in_mi">%1$s mi</string>
+    <plurals name="distance_in_mi_cd">
+        <item quantity="one">%1$d miil</item>
+        <item quantity="other">%1$d miili</item>
+    </plurals>
+    <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>
+    </plurals>
+    <plurals name="distance_in_3gf_12_cd">
+        <item quantity="one">%1$s trinagrafut</item>
+        <item quantity="other">%1$s trinagrafutit</item>
+    </plurals>
+    <plurals name="distance_in_gf_12_cd">
+        <item quantity="one">%1$s grafut</item>
+        <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$sTm tagasi</string>
+    <plurals name="time_in_tm_cd">
+        <item quantity="one">%1$d tim</item>
+        <item quantity="other">%1$d timmi</item>
+    </plurals>
+    <plurals name="time_in_4tm_12_cd">
+        <item quantity="one">%1$s kvadratim</item>
+        <item quantity="other">%1$s kvadratimmi</item>
+    </plurals>
+    <string name="congestion_unknown">teadmata</string>
+    <string name="congestion_smooth">sujuv</string>
+    <string name="congestion_congestion">väike ummik</string>
+    <string name="occupancy_unknown">teadmata</string>
+    <string name="occupancy_empty">tühi</string>
+    <string name="occupancy_crowded">peaaegu täis</string>
+    <string name="occupancy_wont_let">uusi reisijaid ei peale võeta</string>
+    <string name="no_map_app">Nutiseadmes ei leidu ühtegi kaardirakendust</string>
+    <string name="departure_headsign_content_description">%1$s suunas</string>
+    <string name="departure_departed">väljunud</string>
+    <string name="departure_now">nüüd</string>
+    <string name="at_time">kell %1$02d:%2$02d</string>
+    <string name="at_time_realtime">kell %1$02d:%2$02d:%3$02d</string>
+    <string name="on_demand">nõudepeatus</string>
+    <string name="no_boarding">pealeminekut pole</string>
+    <string name="on_boarding">pealeminek</string>
+    <string name="boarding">võid peale minna</string>
+    <string name="line_headsign_content_description">suunaga %1$s</string>
+    <string name="line_headsigns_content_description">%1$s ja %2$s vahel</string>
+    <string name="stops_nearby">Peatub läheduses</string>
+    <string name="bimba_server_token_hint">Ligipääsutunnus</string>
+    <string name="wheelchair_content_description">sõidukisse pääseb ratastooliga</string>
+    <string name="air_condition_content_description">kliimaseade</string>
+    <string name="show_departures">Näita väljumisi</string>
+    <string name="stop_content_description">peatus</string>
+    <string name="onboarding_question">Kuidas sa soovid alustada?</string>
+    <string name="onboarding_simple_action">vali piirkonnad</string>
+    <string name="onboarding_advanced_action">vali server</string>
+    <string name="last_update">Viimane uuendus: %1$s</string>
+    <string name="title_feeds">Sõiduplaanid</string>
+    <string name="error_url">Sisestatud võrguaadress on vigane</string>
+    <string name="error_traffic_spec">Serveri verifitseerimine ei õnnestu</string>
+    <string name="stops_near_code">Peatub %1$s lähedal</string>
+    <string name="stop_stub_on_demand_in_zone">Nõudepeatus tsoonis %1$s</string>
+    <string name="stop_stub_on_demand">Nõudepeatus</string>
+    <string name="stop_stub_in_zone">Peatus tsoonis %1$s</string>
+    <string name="vehicle_headsign_content_description">%1$s suunaga %2$s</string>
+    <string name="departure_headsign">» %1$s</string>
+    <string name="title_activity_results">Tulemused</string>
+    <string name="continue_">Jätka</string>
+    <string name="error_400">Rakendus tegi vigase päringu</string>
+    <string name="bicycles_allowed_content_description">jalgrattaid võib kaasa võtta</string>
+    <string name="choose_server">Vali serveri variant</string>
+    <string name="ok">Sobib</string>
+    <string name="no_location_access">Puudub ligipääs asukohale</string>
+    <string name="no_location_message">Õigus tuvastada asukohta on vajalik lähimate peatuste leidmiseks ja praeguse asukoha näitamiseks kaardil. Kõik muud funktsionaalsused toimivad ka ilma asukohta teadmata. Sa võid süsteemi seadistustest alati asukoha kasutamise õiguse sisse või välja lülitada - just nii nagu sulle oluline on.</string>
+    <string name="app_description">Avatud lähtekoodil põhinev kaaslane reisijatele - sõiduplaan sinu taskus.</string>
+    <string name="code_button_description">lähtekoodi link</string>
+    <string name="use_online_feed">Kasuta veebipõhist andmevoogu</string>
+    <string name="current_timetable_validity">Praeguse sõiduplaani kehtivus: %1$s kuni %2$s</string>
+    <string name="filter_localities">filtreeri piirkondi</string>
+    <string name="title_select_line">Vali liin</string>
+    <string name="more">Veel</string>
+    <string name="units_locale_based">Lokaadikohased</string>
+    <string name="units_imperial">Inglise süsteemi mõõtühikud (UK)</string>
+    <string name="units_tgm12">TGM (kaheteistkümnendsüsteemis)</string>
+    <string name="units_tgm10">TGM (kümnendsüsteemis)</string>
+    <string name="error_geocoding">Linna ei leidu</string>
+    <string name="cities_channel_name">Linnade uuendamise kanal</string>
+    <string name="cities_channel_description">Teavitus, mis näitab geokodeerimiseks vajalike andmete uuenduse edenemist</string>
+    <string name="updating_geocoding_data">Uuendame geokodeerimise andmeid</string>
+    <string name="zero">null</string>
+    <string name="one">üks</string>
+    <string name="two">kaks</string>
+    <string name="three">kolm</string>
+    <string name="four">neli</string>
+    <string name="five">viis</string>
+    <string name="send">Saada</string>
+    <string name="discard">Loobu</string>
+    <string name="send_with_comment">Saada kommentaariga</string>
+    <string name="local_time">kohalik aeg</string>
+    <string name="title_about">Rakenduse teave</string>
+    <string name="translation_button_description">rakenduse tõlkimise liidese link</string>
+    <string name="information_may_be_outdated">Informatsioon võib olla aegunud</string>
+    <string name="mastodon_button_description">Mastodoni link</string>
+    <string name="website_button_description">veebisaidi link</string>
+    <string name="error_406">Rakenduse versioon ei ühildu serveriga</string>
+    <string name="error_41">See piirkond pole serveri poolt toetatud</string>
+    <string name="title_select_date">Vali väljumise kuupäev</string>
+    <string name="clear_date_selection">Tühjenda</string>
+    <string name="title_filter">Filtreeri</string>
+    <string name="filtered">Filtreeritud</string>
+    <string name="title_filter_byline">Filtreeri liini alusel</string>
+    <string name="title_filter_bytime">Filtreeri aja alusel</string>
+    <string name="favourite">Lemmik</string>
+    <string name="title_select_time_start">Vali algusaeg</string>
+    <string name="title_select_time_end">Vali lõpuaeg</string>
+    <string name="alert_header">Olekute uuendused</string>
+    <string name="map_attribution"><![CDATA[© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>i kaasautorid]]></string>
+    <string name="favourite_content_description">Salvesta lemmikuna</string>
+    <string name="unfiltered">Filtreerimata</string>
+    <string name="cannot_save_favourite">Lemmiku salvestamine ei õnnestunud</string>
+    <string name="units_title">Mõõtühikud</string>
+    <string name="units_metric">Meetermõõdustik (SI)</string>
+    <string name="error_44">Rohkem väljumisi pole</string>
+    <string name="loading">laadime…</string>
+    <string name="favourite_deleted">Lemmik on kustutatud</string>
+    <string name="undo">Võta tegevus tagasi</string>
+    <string name="units_customary">USA kohandatud mõõtühikud</string>
+    <string name="title_settings">Seadistused</string>
+    <string name="no_geocoding_data">Geokodeerimise andmed puuduvad</string>
+    <string name="saving_cities_list">salvestame linnade loendit</string>
+    <string name="downloading_cities_list">laadime alla linnade loendit</string>
+    <string name="finished_updating_geocoding_data">Geokodeerimise andmete uuendamine on lõppenud</string>
+    <string name="updating_geocoding_data_failed">Geokodeerimise andmete uuendamine ei õnnestunud</string>
+    <string name="six">kuus</string>
+    <string name="seven">seitse</string>
+    <string name="nine">üheksa</string>
+    <string name="ten">kümme</string>
+    <string name="eight">kaheksa</string>
+    <string name="email_button_description">e-posti link</string>
+    <string name="alerts">Hoiatused</string>
+    <string name="hide">peida</string>
+    <string name="line_decorations">Liini nime kujundus</string>
+    <string name="show">näita</string>
+    <string name="matrix_button_description">Matrixi kanali link</string>
+    <string name="transitous_attribution">Transitouse (https://transitous.org) APIt vahendab Spline (https://routing.spline.de). Piirkondade (https://github.com/public-transport/transitous/tree/main/feeds) andmekogu haldab kogukond.</string>
+    <string name="credits">Yellowcircle8 kirjatüüp (https://git.apiote.xyz/yellowcircle8.git) põhineb Railway Sansil © Greg Fleming, OFL-1.1 https://github.com/davelab6/Railway-Sans\n\n Mastodoni ikoon (https://github.com/mastodon/joinmastodon) © Mastodoni kaasautorid, AGPL-3.0-or-later\n\n Bimba logo autor on https://github.com/tebriz159\n\n Material kujunduse ikoonid © Google, Apache-2.0\n\n Kaardiandmed © OpenStreetMapi kaasautorid (https://www.openstreetmap.org/copyright), ODbL-1.0\n\n OLC-koodide geokodeerimiseks vajalik linnade loend © Geonames (https://geonames.org), CC BY\n\n Matrixi logo ™/® Matrix.org</string>
+    <string name="trin">trin</string>
+    <string name="hes">hes</string>
+    <string name="quen">quen</string>
+    <string name="neen">neen</string>
+    <string name="ak">ak</string>
+    <string name="transitous_description">Kogukonna hallatav ja teenusepakkujatest sõltumatu rahvusvaheline ühistranspordi teekonnamootor. Teeninduspiirkonnad on kirjas lehel https://transitous.org/sources/</string>
+    <string name="stop_from_qr_code">Peatuse QR-kood</string>
+    <string name="no_geocoding_data_description">Päringus on OLC-kood ehk pluss-kood, aga geokodeerimiseks vajalik andmekogu puudub. Palun laadi vajalik andmekogu alla või luba seadistustest automaatsed uuendused.</string>
+    <string name="elv">elv</string>
+    <string name="zen">zen</string>
+    <string name="duna">duna</string>
+    <string name="quedra">quedra</string>
+    <string name="filtered_stop_question">Kas sa soovid valitud liinide kombinatsiooni salvestada lemmikuna?</string>
+    <string name="sev">sev</string>
+    <string name="dex">dex</string>
+    <string name="lef">lef</string>
+    <string name="none">puudub</string>
+    <string name="italics">kaldkiri</string>
+    <string name="colour">värv</string>
+    <string name="acra_notification_channel">Rakenduse kokkujooksmiste teavituskanal</string>
+    <string name="acra_notification_channel_description">Teavitused rakenduse kokkujooksmiste kohta, sealhulgas vastava aruande saatmise võimalus</string>
+    <string name="acra_notification_title">Bimba on kokku jooksnud</string>
+    <string name="acra_notification_text">Bimba teekonnale on ette jäänud ootamatu takistus ja rakendus on lõpetanud töö. Kas sa soovid selle kohta veateate saata?</string>
+    <string name="acra_notification_comment">Kommentaar on lisatud veateatele</string>
+    <string name="filtered_departures">Filtreeritud väljumised</string>
+    <string name="grey_out">märgitud halliks</string>
+    <string name="terminus_arrival_showing">Saabumised lõpp-punkti</string>
+</resources>
\ No newline at end of file




diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index f229828d8dc9371528c4c32a6f7ab654b2860eec..604046478c874a3e78a2d33e137b169e1a3cbd7c 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -33,7 +33,7 @@     Arrêts à proximité
     <string name="line_headsigns_content_description">entre %1$s et %2$s</string>
     <string name="line_headsign_content_description">vers %1$s</string>
     <string name="line_headsigns">%1$s «» %2$s</string>
-    <string name="bimba_server_token_hint">Jeton</string>
+    <string name="bimba_server_token_hint">Jeton d\'identification</string>
     <string name="tickets_sold_content_description">Tickets vendus à bord</string>
     <string name="usb_charging_content_description">Charge USB</string>
     <string name="voice_announcements_content_description">Annonces vocales</string>
@@ -63,17 +63,17 @@     Montrer les départs
     <string name="realtime_content_description">Départ en temps réel</string>
     <string name="open_in_maps_app">Ouvrir dans une application de cartes</string>
     <string name="departure_now">Maintenant</string>
-    <string name="results_for">Résultats pour ‘%1$s’</string>
+    <string name="results_for">Résultats pour « %1$s »</string>
     <string name="at_time_realtime">à %1$02d:%2$02d:%3$02d</string>
     <string name="wheelchair_content_description">Le véhicule est accessible pour les fauteuils roulants</string>
-    <string name="departure_momentarily">Prochainement</string>
+    <string name="departure_momentarily">dans un instant</string>
     <string name="error_offline">Vous êtes hors ligne. Connectez-vous à Internet</string>
     <string name="off_boarding">Débarquement</string>
     <string name="error_gps">Impossible d\'obtenir la position actuelle</string>
     <string name="speed_in_km_per_h">%1$s km/h</string>
     <string name="occupancy_unknown">Inconnu</string>
     <string name="occupancy_few_seats">Peu de sièges</string>
-    <string name="occupancy_crowded">Encombré</string>
+    <string name="occupancy_crowded">bondé</string>
     <string name="occupancy_empty">Vide</string>
     <string name="occupancy_many_seats">Nombreux sièges</string>
     <string name="occupancy_full">Rempli</string>
@@ -93,16 +93,16 @@     Arrêt dans la zone %1$s
     <string name="stop_stub_on_demand">Arrêt à la demande</string>
     <string name="departure_headsign">» %1$s</string>
     <string name="translation_button_description">Lien vers le service de traduction</string>
-    <string name="app_description">Assistant de transport public libre ; un horaire dans votre poche.</string>
+    <string name="app_description">Assistant de transport public libre ; une fiche horaire dans votre poche.</string>
     <string name="error_41">Cette localité n\'est pas couverte par le serveur</string>
-    <string name="current_timetable_validity">Horraire valide : du %1$s au %2$s</string>
+    <string name="current_timetable_validity">Horaire valide : du %1$s au %2$s</string>
     <string name="filter_localities">Filtrer les localités</string>
     <string name="stop_from_qr_code">Code QR d\'arrêt</string>
     <string name="title_filter_byline">Filtrer par ligne</string>
     <string name="title_filter_bytime">Filtrer par horaire</string>
     <string name="title_select_time_end">Sélectionner l\'heure d\'arrivée</string>
     <string name="title_select_time_start">Sélectionner l\'heure de départ</string>
-    <string name="credits">Police yellowcircle8 (https://git.apiote.xyz/yellowcircle8.git) basée sur Railway Sans © Greg Fleming, OFL-1.1 https://github.com/davelab6/Railway-Sans\n\n Icône Mastodon (https://github.com/mastodon/joinmastodon) © Mastodon contributors, AGPL-3.0-or-later\n\n Logo Bimba créé par https://github.com/tebriz159\n\n Icônes Material © Google, Apache-2.0\n\n Données cartographiques © OpenStreetMap contributors, ODbL-1.0</string>
+    <string name="credits">Police yellowcircle8 (https://git.apiote.xyz/yellowcircle8.git) basée sur Railway Sans © Greg Fleming, OFL-1.1 https://github.com/davelab6/Railway-Sans\n\n Icône Mastodon (https://github.com/mastodon/joinmastodon) © contributeurs de Mastodon, AGPL-3.0-or-later\n\n Logo Bimba créé par https://github.com/tebriz159\n\n Icônes Material © Google, Apache-2.0\n\n Données cartographiques © contributeurs d’OpenStreetMap, ODbL-1.0\n\n Liste des villes utilisées pour le géocodage des codes short plus © Geonames (https://geonames.org), CC BY\n\n Logo Matrix™/® Matrix.org</string>
     <string name="error_400">L\'application a effectué une requête mal formulée</string>
     <string name="error_404">Pas trouvé</string>
     <string name="error_429">Limite dépassée. Réessayez plus tard</string>
@@ -112,4 +112,134 @@     En voiture tout le monde !
     <string name="onboarding_simple">Simple</string>
     <string name="occupancy_wont_let">embarquement refusé</string>
     <string name="choose_server">Choix du type de serveur</string>
-</resources>
\ No newline at end of file
+    <string name="distance_in_m">%1$s m</string>
+    <plurals name="distance_in_km_cd">
+        <item quantity="one">%1$d kilomètre</item>
+        <item quantity="many">%1$d kilomètres</item>
+        <item quantity="other">%1$d kilomètres</item>
+    </plurals>
+    <string name="distance_in_ft">%1$s ft</string>
+    <plurals name="time_in_s_cd">
+        <item quantity="one">%1$d seconde</item>
+        <item quantity="many">%1$d secondes</item>
+        <item quantity="other">%1$d secondes</item>
+    </plurals>
+    <plurals name="speed_in_km_per_h_cd">
+        <item quantity="one">%1$d kilomètre par heure</item>
+        <item quantity="many">%1$d kilomètres par heure</item>
+        <item quantity="other">%1$d kilomètres par heure</item>
+    </plurals>
+    <string name="favourite">Favori</string>
+    <string name="filtered">Filtré</string>
+    <string name="error_44">Plus de départ</string>
+    <string name="three">trois</string>
+    <string name="distance_in_gf">%1$s%2$sGf</string>
+    <string name="ten">dix</string>
+    <string name="cities_channel_description">Notifications indiquant l’avancement de la mise à jour des données locales de géocodage</string>
+    <string name="saving_cities_list">Enregistrement de la liste des villes</string>
+    <string name="updating_geocoding_data">Mise à jour des données de géocodage</string>
+    <string name="downloading_cities_list">téléchargement de la liste des villes</string>
+    <string name="two">deux</string>
+    <string name="elv">onze</string>
+    <string name="none">aucun</string>
+    <string name="hide">cacher</string>
+    <string name="colour">couleur</string>
+    <string name="show">montrer</string>
+    <string name="grey_out">griser</string>
+    <string name="filtered_departures">Départs filtrés</string>
+    <string name="local_time">heure locale</string>
+    <string name="speed_in_m_per_s">%1$s m/s</string>
+    <string name="speed_in_mi_per_h">%1$s mph</string>
+    <string name="server_rate_limited_question">Ce serveur est à taux limité et aucun jeton n’a été fourni. Voulez-vous continuer ?</string>
+    <string name="no_geocoding_data">Pas de données de géocodage</string>
+    <string name="error_geocoding">Ville introuvable</string>
+    <string name="four">quatre</string>
+    <string name="five">cinq</string>
+    <string name="six">six</string>
+    <string name="seven">sept</string>
+    <string name="eight">huit</string>
+    <string name="nine">neuf</string>
+    <plurals name="distance_in_m_cd">
+        <item quantity="one">%1$d mètre</item>
+        <item quantity="many">%1$d mètres</item>
+        <item quantity="other">%1$d mètres</item>
+    </plurals>
+    <plurals name="distance_in_yd_cd">
+        <item quantity="one">%1$d yard</item>
+        <item quantity="many">%1$d yards</item>
+        <item quantity="other">%1$d yards</item>
+    </plurals>
+    <plurals name="distance_in_ft_cd">
+        <item quantity="one">%1$d pied</item>
+        <item quantity="many">%1$d pieds</item>
+        <item quantity="other">%1$d pieds</item>
+    </plurals>
+    <plurals name="distance_in_mi_cd">
+        <item quantity="one">%1$d mille</item>
+        <item quantity="many">%1$d milles</item>
+        <item quantity="other">%1$d milles</item>
+    </plurals>
+    <plurals name="speed_in_m_per_s_cd">
+        <item quantity="one">%1$d mètre par seconde</item>
+        <item quantity="many">%1$d mètres par seconde</item>
+        <item quantity="other">%1$d mètres par seconde</item>
+    </plurals>
+    <plurals name="speed_in_mi_per_h_cd">
+        <item quantity="one">%1$d mille par heure</item>
+        <item quantity="many">%1$d milles par heure</item>
+        <item quantity="other">%1$d milles par heure</item>
+    </plurals>
+    <string name="congestion_stop_and_go">en accordéon</string>
+    <string name="onboarding_question">Comment souhaitez-vous commencer ?</string>
+    <string name="rate_limit">Taux limite</string>
+    <string name="error_traffic_spec">Impossible de vérifier le serveur</string>
+    <string name="error_url">URL non conforme fournie</string>
+    <string name="clear_date_selection">Effacer</string>
+    <string name="alert_header">Mise à jour du statut</string>
+    <string name="distance_in_km">%1$s km</string>
+    <string name="distance_in_yd">%1$s yd</string>
+    <string name="distance_in_mi">%1$s mi</string>
+    <string name="distance_in_two_units_cd">%1$s et %2$s</string>
+    <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="favourite_content_description">Enregistrer comme favori</string>
+    <string name="unfiltered">Non filtré</string>
+    <string name="cannot_save_favourite">Impossible d’enregistrer le favori</string>
+    <string name="loading">chargement…</string>
+    <string name="favourite_deleted">Favori supprimé</string>
+    <string name="undo">Annuler</string>
+    <string name="units_title">Système d’unités</string>
+    <string name="units_locale_based">Basé sur le système</string>
+    <string name="units_metric">Métrique (SI)</string>
+    <string name="units_imperial">Impérial (Royaume-Uni)</string>
+    <string name="units_customary">Américain (États-Unis)</string>
+    <string name="units_tgm10">TGM (base 10)</string>
+    <string name="units_tgm12">TGM (base 12)</string>
+    <string name="title_settings">Paramètres</string>
+    <string name="cities_channel_name">Fil de mise à jour des villes</string>
+    <string name="finished_updating_geocoding_data">Mise à jour des données de géocodage terminée</string>
+    <string name="updating_geocoding_data_failed">Mise à jour des données de géocodage échouée</string>
+    <string name="zero">zéro</string>
+    <string name="one">un</string>
+    <string name="filtered_stop_question">Voulez-vous enregistrer un favori filtré avec des lignes sélectionnées ?</string>
+    <string name="line_decorations">Décorations du nom de la ligne</string>
+    <string name="acra_notification_channel">Fil des rapports de problèmes</string>
+    <string name="acra_notification_channel_description">Notifications indiquant les problèmes et permettant d\'envoyer des rapports de problèmes</string>
+    <string name="acra_notification_title">Bimba s’est arrêté</string>
+    <string name="acra_notification_text">Un blocage inattendu s\'est dressé sur le chemin de Bimba. Voulez-vous envoyer un rapport ?</string>
+    <string name="send">Envoyer</string>
+    <string name="discard">Supprimer</string>
+    <string name="send_with_comment">Envoyer avec commentaire</string>
+    <string name="acra_notification_comment">Commentaire ajouté au rapport de problème</string>
+    <string name="alerts">Alertes</string>
+    <string name="terminus_arrival_showing">Arrivées au terminus</string>
+    <string name="matrix_button_description">lien vers le salon Matrix</string>
+    <string name="email_button_description">lien vers le courriel</string>
+    <string name="transitous_description">Un service de transport public international, neutre sur le plan des fournisseurs et géré par la communauté. La couverture est disponible à l\'adresse suivante : https://transitous.org/sources/</string>
+    <string name="transitous_attribution">API Transitous (https://transitous.org) fournie par Spline (https://routing.spline.de). Localités (https://github.com/public-transport/transitous/tree/main/feeds) maintenues par la communauté.</string>
+    <string name="no_geocoding_data_description">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>
+    <string name="congestion_congestion">ralentissements</string>
+    <string name="about_time">environ %1$02d:%2$02d</string>
+    <string name="italics">italique</string>
+</resources>




diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index a583c09cc1089d2f2f972a9b0596380c0a308b7e..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>
@@ -303,4 +302,10 @@     link do e-maila
     <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/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/fruchtfleisch/build.gradle.kts b/fruchtfleisch/build.gradle.kts
index 014557871091925f8b0568606aea1e74ec7837d9..df25428f756486a6fc35db26cf06914a5427ee12 100644
--- a/fruchtfleisch/build.gradle.kts
+++ b/fruchtfleisch/build.gradle.kts
@@ -15,8 +15,8 @@     //implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.10")
 }
 
 java {
-    sourceCompatibility = JavaVersion.VERSION_17
-    targetCompatibility = JavaVersion.VERSION_17
+    sourceCompatibility = JavaVersion.VERSION_21
+    targetCompatibility = JavaVersion.VERSION_21
 }
 
 tasks.withType<Test> {




diff --git a/metadata/de-DE/short_description.txt b/metadata/de-DE/short_description.txt
new file mode 100644
index 0000000000000000000000000000000000000000..98c1bbc4e65617a72f837c7687302eb60410e04a
--- /dev/null
+++ b/metadata/de-DE/short_description.txt
@@ -0,0 +1 @@
+Open Source ÖPNV-Begleiter; der Fahrplan für deine Tasche.




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/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/fr-FR/changelogs/25.txt b/metadata/fr-FR/changelogs/25.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9be56df711665b506f2ae6436dd212effaeaa0c0
--- /dev/null
+++ b/metadata/fr-FR/changelogs/25.txt
@@ -0,0 +1,11 @@
+Changements en version 3.3
+* Ajout de la sélection de la date et du filtrage par heure
+* Ajout du filtrage des départs par ligne
+* Ajout d’alertes affichées sur les arrêts et les départs
+* Correction de la version paysage de l’écran "à propos"
+* Correction des capacités des véhicules sur la carte
+* Changement des localités cachées vers un plus faible contraste
+* Correction de l’affichage des lignes autres que 2
+* Correction de l’enregistrement du cache des localités
+* Mise à jour des dépendances
+* Mise à jour des méthodes obsolètes dans le code




diff --git a/metadata/fr-FR/changelogs/26.txt b/metadata/fr-FR/changelogs/26.txt
new file mode 100644
index 0000000000000000000000000000000000000000..256b9c9bbb7dab7d340faf7ac1382dcaa7ccac03
--- /dev/null
+++ b/metadata/fr-FR/changelogs/26.txt
@@ -0,0 +1,13 @@
+Changements en version 3.3.1
+* Correction d’un problème en zoomant trop la carte
+Changements en version 3.3
+* Ajout de la sélection de la date et du filtrage par heure
+* Ajout du filtrage des départs par ligne
+* Ajout d’alertes affichées sur les arrêts et les départs
+* Correction de la version paysage de l’écran "à propos"
+* Correction des capacités des véhicules sur la carte
+* Changement des localités cachées vers un plus faible contraste
+* Correction de l’affichage des lignes autres que 2
+* Correction de l’enregistrement du cache des localités
+* Mise à jour des dépendances
+* Mise à jour des méthodes obsolètes dans le code




diff --git a/metadata/fr-FR/changelogs/27.txt b/metadata/fr-FR/changelogs/27.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d6059cb8f810bcc312f793b418e8ffbce730f4b9
--- /dev/null
+++ b/metadata/fr-FR/changelogs/27.txt
@@ -0,0 +1,16 @@
+Changements en version 3.3.2
+* Ajout d’une attribution Openstreetmap correcte
+* Changement du zoom minimum de la carte
+Changements en version 3.3.1
+* Correction d’un problème en zoomant trop la carte
+Changements en version 3.3
+* Ajout de la sélection de la date et du filtrage par heure
+* Ajout du filtrage des départs par ligne
+* Ajout d’alertes affichées sur les arrêts et les départs
+* Correction de la version paysage de l’écran "à propos"
+* Correction des capacités des véhicules sur la carte
+* Changement des localités cachées vers un plus faible contraste
+* Correction de l’affichage des lignes autres que 2
+* Correction de l’enregistrement du cache des localités
+* Mise à jour des dépendances
+* Mise à jour des méthodes obsolètes dans le code




diff --git a/metadata/fr-FR/changelogs/28.txt b/metadata/fr-FR/changelogs/28.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8f891b044de8d78a478943ad9d1f702704d159eb
--- /dev/null
+++ b/metadata/fr-FR/changelogs/28.txt
@@ -0,0 +1,8 @@
+Changements en version 3.4
+* Ajout d’un compte à rebours pour montrer la prochaine mise à jour des départs
+* Correction des filtres et feuilles de fond persistants lors de changements d’écran
+* Changement de la disposition des alertes au-dessus des départs
+* Correction des résultats de recherche fantômes
+* Correction de la mise en page des paysages et du bord à bord
+* Ajout simple des favoris
+* Ajout de la traduction française (par ArnaudDvs)




diff --git a/metadata/fr-FR/changelogs/29.txt b/metadata/fr-FR/changelogs/29.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b8f0db227ada8de170f9e609f90f6f329a0ed361
--- /dev/null
+++ b/metadata/fr-FR/changelogs/29.txt
@@ -0,0 +1,4 @@
+Changes in version 3.5:
+* ajout d’une recherche via Plus Codes courts
+* ajouts de flèches indiquant l’arrêt le plus proche
+* ajout des unités (vitesse, distance, temps)




diff --git a/metadata/fr-FR/changelogs/30.txt b/metadata/fr-FR/changelogs/30.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cd16155f45cd1f6bd43a5c2e73a44b5f5f899270
--- /dev/null
+++ b/metadata/fr-FR/changelogs/30.txt
@@ -0,0 +1,5 @@
+Changements en version 3.6:
+* Changements dans les paramètres pour pouvoir montrer les lignes en italique
+* Les problèmes/crashs peuvent être signalés
+* La page « à propos » affiche plus de points de contact
+* Correction de la mise à jour des favoris et des noms géographiques sur les anciennes versions d’Android




diff --git a/metadata/fr-FR/changelogs/31.txt b/metadata/fr-FR/changelogs/31.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cd16155f45cd1f6bd43a5c2e73a44b5f5f899270
--- /dev/null
+++ b/metadata/fr-FR/changelogs/31.txt
@@ -0,0 +1,5 @@
+Changements en version 3.6:
+* Changements dans les paramètres pour pouvoir montrer les lignes en italique
+* Les problèmes/crashs peuvent être signalés
+* La page « à propos » affiche plus de points de contact
+* Correction de la mise à jour des favoris et des noms géographiques sur les anciennes versions d’Android




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 528c6853c80625ca544bd4addf3b535280ea079b..7b8c6671ca16a510eb4014d883fdcc84505f215c 100755
--- a/release.sh
+++ b/release.sh
@@ -98,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 ' "')
@@ -114,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