Bimba.git

commit 002fee30a2996c72ac92e19d52b40bd32b587d35

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

split responses to own files

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


diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/Structs.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/Structs.kt
index d5f464c24cfb86ce7970768c513624b7a9d16df1..8e241060fe512c6bf1277c7891519edee95a67eb 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/api/Structs.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/Structs.kt
@@ -1,16 +1,12 @@
 package xyz.apiote.bimba.czwek.api
 
-import android.graphics.*
 import android.os.Parcelable
 import android.util.Log
 import kotlinx.parcelize.Parcelize
 import org.yaml.snakeyaml.Yaml
+import xyz.apiote.bimba.czwek.api.structs.VehicleStatusV1
 import xyz.apiote.fruchtfleisch.Reader
 import java.io.InputStream
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle
-import java.util.*
 import kotlin.reflect.KClass
 
 class TrafficFormatException(override val message: String) : IllegalArgumentException()
@@ -110,32 +106,6 @@ 		}
 	}
 }
 
-data class FeedInfoV1(
-	val name: String,
-	val id: String,
-	val attribution: String,
-	val description: String,
-	val lastUpdate: ZonedDateTime
-) {
-	companion object {
-		fun unmarshal(stream: InputStream): FeedInfoV1 {
-			val reader = Reader(stream)
-			return FeedInfoV1(
-				reader.readString(),
-				reader.readString(),
-				reader.readString(),
-				reader.readString(),
-				ZonedDateTime.parse(reader.readString(), DateTimeFormatter.ISO_DATE_TIME)
-			)
-		}
-	}
-
-	fun formatDate(): String {
-		return lastUpdate.format(
-			DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.getDefault())
-		)
-	}
-}
 
 enum class AlertCauseV1 {
 	UNKNOWN, OTHER, TECHNICAL_PROBLEM, STRIKE, DEMONSTRATION, ACCIDENT, HOLIDAY, WEATHER, MAINTENANCE,
@@ -447,7 +417,7 @@
 data class DepartureV3(
 	val ID: String,
 	val time: Time,
-	val status: ULong,
+	val status: VehicleStatusV1,
 	val isRealtime: Boolean,
 	val vehicle: VehicleV3,
 	val boarding: UByte
@@ -458,7 +428,7 @@ 		fun unmarshal(stream: InputStream): DepartureV3 {
 			val reader = Reader(stream)
 			val id = reader.readString()
 			val time = Time.unmarshal(stream)
-			val status = reader.readUInt().toULong()
+			val status = VehicleStatusV1.of(reader.readUInt().toULong().toUInt())
 			val isRealtime = reader.readBoolean()
 			val vehicle = VehicleV3.unmarshal(stream)
 			val boarding = reader.readU8()
@@ -575,6 +545,7 @@ 			)
 		}
 	}
 }
+
 data class LineV2(
 	val name: String,
 	val colour: ColourV1,




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Departures.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Departures.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c7550c6c654c18366c2268986b7724389bd821f4
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Departures.kt
@@ -0,0 +1,135 @@
+package xyz.apiote.bimba.czwek.api.responses
+
+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.StopV1
+import xyz.apiote.bimba.czwek.api.StopV2
+import xyz.apiote.bimba.czwek.api.UnknownResponseVersion
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+
+interface DeparturesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): DeparturesResponse {
+			val reader = Reader(stream)
+			return when (val v = reader.readUInt().toULong()) {
+				0UL -> DeparturesResponseDev.unmarshal(stream)
+				1UL -> DeparturesResponseV1.unmarshal(stream)
+				2UL -> DeparturesResponseV2.unmarshal(stream)
+				3UL -> DeparturesResponseV3.unmarshal(stream)
+				else -> throw UnknownResponseVersion("Departures", v)
+			}
+		}
+	}
+}
+
+data class DeparturesResponseDev(
+	val alerts: List<AlertV1>,
+	val departures: List<DepartureV3>,
+	val stop: StopV2
+) : DeparturesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): DeparturesResponseDev {
+			val alerts = mutableListOf<AlertV1>()
+			val departures = mutableListOf<DepartureV3>()
+
+			val reader = Reader(stream)
+			val alertsNum = reader.readUInt().toULong()
+			for (i in 0UL until alertsNum) {
+				val alert = AlertV1.unmarshal(stream)
+				alerts.add(alert)
+			}
+			val departuresNum = reader.readUInt().toULong()
+			for (i in 0UL until departuresNum) {
+				val departure = DepartureV3.unmarshal(stream)
+				departures.add(departure)
+			}
+
+			return DeparturesResponseDev(alerts, departures, StopV2.unmarshal(stream))
+		}
+	}
+}
+
+data class DeparturesResponseV3(
+	val alerts: List<AlertV1>,
+	val departures: List<DepartureV3>,
+	val stop: StopV2
+) : DeparturesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): DeparturesResponseDev {
+			val alerts = mutableListOf<AlertV1>()
+			val departures = mutableListOf<DepartureV3>()
+
+			val reader = Reader(stream)
+			val alertsNum = reader.readUInt().toULong()
+			for (i in 0UL until alertsNum) {
+				val alert = AlertV1.unmarshal(stream)
+				alerts.add(alert)
+			}
+			val departuresNum = reader.readUInt().toULong()
+			for (i in 0UL until departuresNum) {
+				val departure = DepartureV3.unmarshal(stream)
+				departures.add(departure)
+			}
+
+			return DeparturesResponseDev(alerts, departures, StopV2.unmarshal(stream))
+		}
+	}
+}
+
+data class DeparturesResponseV2(
+	val alerts: List<AlertV1>,
+	val departures: List<DepartureV2>,
+	val stop: StopV2
+) : DeparturesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): DeparturesResponseV2 {
+			val alerts = mutableListOf<AlertV1>()
+			val departures = mutableListOf<DepartureV2>()
+
+			val reader = Reader(stream)
+			val alertsNum = reader.readUInt().toULong()
+			for (i in 0UL until alertsNum) {
+				val alert = AlertV1.unmarshal(stream)
+				alerts.add(alert)
+			}
+			val departuresNum = reader.readUInt().toULong()
+			for (i in 0UL until departuresNum) {
+				val departure = DepartureV2.unmarshal(stream)
+				departures.add(departure)
+			}
+
+			return DeparturesResponseV2(alerts, departures, StopV2.unmarshal(stream))
+		}
+	}
+}
+
+data class DeparturesResponseV1(
+	val alerts: List<AlertV1>,
+	val departures: List<DepartureV1>,
+	val stop: StopV1
+) : DeparturesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): DeparturesResponseV1 {
+			val alerts = mutableListOf<AlertV1>()
+			val departures = mutableListOf<DepartureV1>()
+
+			val reader = Reader(stream)
+			val alertsNum = reader.readUInt().toULong()
+			for (i in 0UL until alertsNum) {
+				val alert = AlertV1.unmarshal(stream)
+				alerts.add(alert)
+			}
+			val departuresNum = reader.readUInt().toULong()
+			for (i in 0UL until departuresNum) {
+				val departure = DepartureV1.unmarshal(stream)
+				departures.add(departure)
+			}
+
+			return DeparturesResponseV1(alerts, departures, StopV1.unmarshal(stream))
+		}
+	}
+}
+




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Error.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Error.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a99569153a1cf725565fe7f642aa1c3830629753
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Error.kt
@@ -0,0 +1,13 @@
+package xyz.apiote.bimba.czwek.api.responses
+
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+
+data class ErrorResponse(val field: String, val message: String) {
+	companion object {
+		fun unmarshal(stream: InputStream): ErrorResponse {
+			val reader = Reader(stream)
+			return ErrorResponse(reader.readString(), reader.readString())
+		}
+	}
+}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Feeds.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Feeds.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4be8cc6c620b0f7b40124e8449a6c78ee0a1172f
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Feeds.kt
@@ -0,0 +1,69 @@
+package xyz.apiote.bimba.czwek.api.responses
+
+import xyz.apiote.bimba.czwek.api.UnknownResponseVersion
+import xyz.apiote.bimba.czwek.api.structs.FeedInfoV1
+import xyz.apiote.bimba.czwek.api.structs.FeedInfoV2
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+
+interface FeedsResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): FeedsResponse {
+			val reader = Reader(stream)
+			return when (val v = reader.readUInt().toULong()) {
+				0UL -> FeedsResponseDev.unmarshal(stream)
+				1UL -> FeedsResponseV1.unmarshal(stream)
+				2UL -> FeedsResponseV2.unmarshal(stream)
+				else -> throw UnknownResponseVersion("Feeds", v)
+			}
+		}
+	}
+}
+
+data class FeedsResponseDev(
+	val feeds: List<FeedInfoV2>
+) : FeedsResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): FeedsResponseDev {
+			val feeds = mutableListOf<FeedInfoV2>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				feeds.add(FeedInfoV2.unmarshal(stream))
+			}
+			return FeedsResponseDev(feeds)
+		}
+	}
+}
+
+data class FeedsResponseV2(
+	val feeds: List<FeedInfoV2>
+) : FeedsResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): FeedsResponseDev {
+			val feeds = mutableListOf<FeedInfoV2>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				feeds.add(FeedInfoV2.unmarshal(stream))
+			}
+			return FeedsResponseDev(feeds)
+		}
+	}
+}
+
+data class FeedsResponseV1(
+	val feeds: List<FeedInfoV1>
+) : FeedsResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): FeedsResponseV1 {
+			val feeds = mutableListOf<FeedInfoV1>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				feeds.add(FeedInfoV1.unmarshal(stream))
+			}
+			return FeedsResponseV1(feeds)
+		}
+	}
+}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Line.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Line.kt
new file mode 100644
index 0000000000000000000000000000000000000000..632f3e835cf4e3a7889c5ca883ca33215d2f75f0
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Line.kt
@@ -0,0 +1,51 @@
+package xyz.apiote.bimba.czwek.api.responses
+
+import xyz.apiote.bimba.czwek.api.LineV1
+import xyz.apiote.bimba.czwek.api.LineV2
+import xyz.apiote.bimba.czwek.api.UnknownResponseVersion
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+
+interface LineResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LineResponse {
+			val reader = Reader(stream)
+			return when (val v = reader.readUInt().toULong()) {
+				0UL -> LineResponseDev.unmarshal(stream)
+				1UL -> LineResponseV1.unmarshal(stream)
+				2UL -> LineResponseV2.unmarshal(stream)
+				else -> throw UnknownResponseVersion("Line", v)
+			}
+		}
+	}
+}
+
+data class LineResponseDev(
+	val line: LineV2
+) : LineResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LineResponseDev {
+			return LineResponseDev(LineV2.unmarshal(stream))
+		}
+	}
+}
+
+data class LineResponseV2(
+	val line: LineV2
+) : LineResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LineResponseDev {
+			return LineResponseDev(LineV2.unmarshal(stream))
+		}
+	}
+}
+
+data class LineResponseV1(
+	val line: LineV1
+) : LineResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LineResponseV1 {
+			return LineResponseV1(LineV1.unmarshal(stream))
+		}
+	}
+}
\ No newline at end of file




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Locatables.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Locatables.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5a882417b23eb02afae8e45e5523a24c3f7f7923
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Locatables.kt
@@ -0,0 +1,125 @@
+package xyz.apiote.bimba.czwek.api.responses
+
+import xyz.apiote.bimba.czwek.api.LocatableV1
+import xyz.apiote.bimba.czwek.api.LocatableV2
+import xyz.apiote.bimba.czwek.api.LocatableV3
+import xyz.apiote.bimba.czwek.api.StopV1
+import xyz.apiote.bimba.czwek.api.StopV2
+import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
+import xyz.apiote.bimba.czwek.api.UnknownResponseVersion
+import xyz.apiote.bimba.czwek.api.VehicleV1
+import xyz.apiote.bimba.czwek.api.VehicleV2
+import xyz.apiote.bimba.czwek.api.VehicleV3
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+
+interface LocatablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LocatablesResponse {
+			val reader = Reader(stream)
+			return when (val v = reader.readUInt().toULong()) {
+				0UL -> LocatablesResponseDev.unmarshal(stream)
+				1UL -> LocatablesResponseV1.unmarshal(stream)
+				2UL -> LocatablesResponseV2.unmarshal(stream)
+				3UL -> LocatablesResponseV3.unmarshal(stream)
+				else -> throw UnknownResponseVersion("Locatables", v)
+			}
+		}
+	}
+}
+
+data class LocatablesResponseDev(val locatables: List<LocatableV3>) : LocatablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LocatablesResponseDev {
+			val locatables = mutableListOf<LocatableV3>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						locatables.add(StopV2.unmarshal(stream))
+					}
+
+					1UL -> {
+						locatables.add(VehicleV3.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Locatable/$r", 0u)
+					}
+				}
+			}
+			return LocatablesResponseDev(locatables)
+		}
+	}
+}
+
+data class LocatablesResponseV3(val locatables: List<LocatableV3>) : LocatablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LocatablesResponseDev {
+			val locatables = mutableListOf<LocatableV3>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						locatables.add(StopV2.unmarshal(stream))
+					}
+
+					1UL -> {
+						locatables.add(VehicleV3.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Locatable/$r", 0u)
+					}
+				}
+			}
+			return LocatablesResponseDev(locatables)
+		}
+	}
+}
+
+data class LocatablesResponseV2(val locatables: List<LocatableV2>) : LocatablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LocatablesResponseV2 {
+			val locatables = mutableListOf<LocatableV2>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> locatables.add(StopV2.unmarshal(stream))
+					1UL -> locatables.add(VehicleV2.unmarshal(stream))
+					else -> throw UnknownResourceVersionException("Locatable/$r", 2u)
+				}
+			}
+			return LocatablesResponseV2(locatables)
+		}
+	}
+}
+
+data class LocatablesResponseV1(val locatables: List<LocatableV1>) : LocatablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): LocatablesResponseV1 {
+			val locatables = mutableListOf<LocatableV1>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						locatables.add(StopV1.unmarshal(stream))
+					}
+
+					1UL -> {
+						locatables.add(VehicleV1.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Locatable/$r", 1u)
+					}
+				}
+			}
+			return LocatablesResponseV1(locatables)
+		}
+	}
+}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Queryables.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Queryables.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a1d7685bab36b61532f1b8cd7a2270ded20402ab
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/Queryables.kt
@@ -0,0 +1,128 @@
+package xyz.apiote.bimba.czwek.api.responses
+
+import xyz.apiote.bimba.czwek.api.LineV1
+import xyz.apiote.bimba.czwek.api.LineV2
+import xyz.apiote.bimba.czwek.api.QueryableV1
+import xyz.apiote.bimba.czwek.api.QueryableV2
+import xyz.apiote.bimba.czwek.api.QueryableV3
+import xyz.apiote.bimba.czwek.api.StopV1
+import xyz.apiote.bimba.czwek.api.StopV2
+import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
+import xyz.apiote.bimba.czwek.api.UnknownResponseVersion
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+
+interface QueryablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): QueryablesResponse {
+			val reader = Reader(stream)
+			return when (val v = reader.readUInt().toULong()) {
+				0UL -> QueryablesResponseDev.unmarshal(stream)
+				1UL -> QueryablesResponseV1.unmarshal(stream)
+				2UL -> QueryablesResponseV2.unmarshal(stream)
+				3UL -> QueryablesResponseV3.unmarshal(stream)
+				else -> throw UnknownResponseVersion("Queryables", v)
+			}
+		}
+	}
+}
+
+data class QueryablesResponseDev(val queryables: List<QueryableV3>) : QueryablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): QueryablesResponseDev {
+			val queryables = mutableListOf<QueryableV3>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						queryables.add(StopV2.unmarshal(stream))
+					}
+
+					1UL -> {
+						queryables.add(LineV2.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Queryable/$r", 0u)
+					}
+				}
+			}
+			return QueryablesResponseDev(queryables)
+		}
+	}
+}
+
+data class QueryablesResponseV3(val queryables: List<QueryableV3>) : QueryablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): QueryablesResponseDev {
+			val queryables = mutableListOf<QueryableV3>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						queryables.add(StopV2.unmarshal(stream))
+					}
+
+					1UL -> {
+						queryables.add(LineV2.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Queryable/$r", 0u)
+					}
+				}
+			}
+			return QueryablesResponseDev(queryables)
+		}
+	}
+}
+
+data class QueryablesResponseV2(val queryables: List<QueryableV2>) : QueryablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): QueryablesResponseV2 {
+			val queryables = mutableListOf<QueryableV2>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						queryables.add(StopV2.unmarshal(stream))
+					}
+
+					1UL -> {
+						queryables.add(LineV1.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Queryable/$r", 2u)
+					}
+				}
+			}
+			return QueryablesResponseV2(queryables)
+		}
+	}
+}
+
+data class QueryablesResponseV1(val queryables: List<QueryableV1>) : QueryablesResponse {
+	companion object {
+		fun unmarshal(stream: InputStream): QueryablesResponseV1 {
+			val queryables = mutableListOf<QueryableV1>()
+			val reader = Reader(stream)
+			val n = reader.readUInt().toULong()
+			for (i in 0UL until n) {
+				when (val r = reader.readUInt().toULong()) {
+					0UL -> {
+						queryables.add(StopV1.unmarshal(stream))
+					}
+
+					else -> {
+						throw UnknownResourceVersionException("Queryable/$r", 1u)
+					}
+				}
+			}
+			return QueryablesResponseV1(queryables)
+		}
+	}
+}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/UnknownResponseVersion.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/UnknownResponseVersion.kt
index 69c025fe2fd34428e9fc83e19518143de890e22a..17bed9db82471ae34057063a788995bbd2bb305c 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/UnknownResponseVersion.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/responses/UnknownResponseVersion.kt
@@ -1,17 +1,5 @@
-package xyz.apiote.bimba.czwek.api
-
-import xyz.apiote.fruchtfleisch.Reader
-import java.io.InputStream
+package xyz.apiote.bimba.czwek.api.responses
 
 class UnknownResponseVersion(resource: String, version: ULong) :
 	Exception("Unknown resource $resource in version $version")
 
-
-data class ErrorResponse(val field: String, val message: String) {
-	companion object {
-		fun unmarshal(stream: InputStream): ErrorResponse {
-			val reader = Reader(stream)
-			return ErrorResponse(reader.readString(), reader.readString())
-		}
-	}
-}
\ No newline at end of file




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/FeedInfo.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/FeedInfo.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dbb1d190f1bb13814e43abe7b6fa38192334811b
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/FeedInfo.kt
@@ -0,0 +1,73 @@
+package xyz.apiote.bimba.czwek.api.structs
+
+import xyz.apiote.fruchtfleisch.Reader
+import java.io.InputStream
+import java.time.LocalDate
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+import java.util.Locale
+
+data class FeedInfoV2(
+	val name: String,
+	val id: String,
+	val attribution: String,
+	val description: String,
+	val lastUpdate: LocalDate,
+	val qrHost: String,
+	val qrIn: QrLocationV1,
+	val qrSelector: String,
+	val validSince: LocalDate,
+	val validTill: LocalDate
+) {
+	companion object {
+		fun unmarshal(stream: InputStream): FeedInfoV2 {
+			val reader = Reader(stream)
+			return FeedInfoV2(
+				reader.readString(),
+				reader.readString(),
+				reader.readString(),
+				reader.readString(),
+				LocalDate.parse(reader.readString(), DateTimeFormatter.ISO_LOCAL_DATE),
+				reader.readString(),
+				QrLocationV1.of(reader.readUInt().toULong().toUInt()),
+				reader.readString(),
+				LocalDate.parse(reader.readString(), DateTimeFormatter.BASIC_ISO_DATE),
+				LocalDate.parse(reader.readString(), DateTimeFormatter.BASIC_ISO_DATE)
+			)
+		}
+	}
+
+	fun formatDate(): String {
+		return lastUpdate.format(
+			DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.getDefault())
+		)
+	}
+}
+
+data class FeedInfoV1(
+	val name: String,
+	val id: String,
+	val attribution: String,
+	val description: String,
+	val lastUpdate: ZonedDateTime
+) {
+	companion object {
+		fun unmarshal(stream: InputStream): FeedInfoV1 {
+			val reader = Reader(stream)
+			return FeedInfoV1(
+				reader.readString(),
+				reader.readString(),
+				reader.readString(),
+				reader.readString(),
+				ZonedDateTime.parse(reader.readString(), DateTimeFormatter.ISO_DATE_TIME)
+			)
+		}
+	}
+
+	fun formatDate(): String {
+		return lastUpdate.format(
+			DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.getDefault())
+		)
+	}
+}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/QrLocation.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/QrLocation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a5832f576f20dad87c30c9779e61c0668c396535
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/QrLocation.kt
@@ -0,0 +1,19 @@
+package xyz.apiote.bimba.czwek.api.structs
+
+import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
+
+enum class QrLocationV1 {
+	UNKNOWN, NONE, PATH, QUERY;
+
+	companion object {
+		fun of(type: UInt): QrLocationV1 {
+			return when (type) {
+				0u -> UNKNOWN
+				1u -> NONE
+				2u -> PATH
+				3u -> QUERY
+				else -> throw UnknownResourceVersionException("QrLocation/$type", 1u)
+			}
+		}
+	}
+}
\ No newline at end of file




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/VehicleStatus.kt b/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/VehicleStatus.kt
new file mode 100644
index 0000000000000000000000000000000000000000..31786bc674352d1eb844571693af190d0c309557
--- /dev/null
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/api/structs/VehicleStatus.kt
@@ -0,0 +1,19 @@
+package xyz.apiote.bimba.czwek.api.structs
+
+import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
+
+enum class VehicleStatusV1 {
+	IN_TRANSIT, INCOMING, AT_STOP, DEPARTED;
+
+	companion object {
+		fun of(type: UInt): VehicleStatusV1 {
+			return when (type) {
+				0u -> IN_TRANSIT
+				1u -> INCOMING
+				2u -> AT_STOP
+				3u -> DEPARTED
+				else -> throw UnknownResourceVersionException("VehicleStatus/$type", 1u)
+			}
+		}
+	}
+}
\ No newline at end of file




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 4120a5f52b1467770aa4e7fee92f54bbe6c6d5d7..425bc76d6e4fb37baf9542700a0c3ac0dbba6926 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
@@ -12,9 +12,9 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.launch
-import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import xyz.apiote.bimba.czwek.repo.OnlineRepository
 import xyz.apiote.bimba.czwek.repo.Queryable
+import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 
 class HomeViewModel : ViewModel() {
 	private val mutableQueryables = MutableLiveData<List<Queryable>>()




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 bacd215fbb6a60598adbcc17ffb0d8696b05c5d0..5f7769362b300b79de6ccc2ed96e6da4b93d3194 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
@@ -22,11 +22,11 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 import kotlinx.coroutines.launch
 import xyz.apiote.bimba.czwek.R
 import xyz.apiote.bimba.czwek.departures.DeparturesActivity
-import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import xyz.apiote.bimba.czwek.repo.Locatable
 import xyz.apiote.bimba.czwek.repo.OnlineRepository
 import xyz.apiote.bimba.czwek.repo.Position
 import xyz.apiote.bimba.czwek.repo.Stop
+import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import xyz.apiote.bimba.czwek.repo.Vehicle
 
 class MapViewModel : ViewModel() {




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 b5d8fa38ecb1f8bd03fc66e5799258041b5cd1d8..b79c2b84160b8eedae05ce6257f669dcbd1736e1 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
@@ -13,14 +13,16 @@ import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.view.WindowCompat
 import androidx.recyclerview.widget.LinearLayoutManager
-import kotlinx.coroutines.*
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.launch
 import xyz.apiote.bimba.czwek.R
-import xyz.apiote.bimba.czwek.api.*
+import xyz.apiote.bimba.czwek.api.Error
 import xyz.apiote.bimba.czwek.databinding.ActivityDeparturesBinding
 import xyz.apiote.bimba.czwek.repo.Departure
-import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import xyz.apiote.bimba.czwek.repo.OnlineRepository
 import xyz.apiote.bimba.czwek.repo.Stop
+import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 
 class DeparturesActivity : AppCompatActivity() {
 	private var _binding: ActivityDeparturesBinding? = null




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 100c4dd181f5b5324e3567b2a8b78a40b6ee3fb4..3818af899b26a665e8572aca47aedd33e5c30142 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
@@ -4,25 +4,9 @@ import android.content.Context
 import android.net.ConnectivityManager
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
-import xyz.apiote.bimba.czwek.api.DeparturesResponse
-import xyz.apiote.bimba.czwek.api.DeparturesResponseDev
-import xyz.apiote.bimba.czwek.api.DeparturesResponseV1
-import xyz.apiote.bimba.czwek.api.DeparturesResponseV2
-import xyz.apiote.bimba.czwek.api.ErrorResponse
-import xyz.apiote.bimba.czwek.api.LineResponse
-import xyz.apiote.bimba.czwek.api.LineResponseDev
-import xyz.apiote.bimba.czwek.api.LineResponseV1
 import xyz.apiote.bimba.czwek.api.LineV1
 import xyz.apiote.bimba.czwek.api.LineV2
-import xyz.apiote.bimba.czwek.api.LocatablesResponse
-import xyz.apiote.bimba.czwek.api.LocatablesResponseDev
-import xyz.apiote.bimba.czwek.api.LocatablesResponseV1
-import xyz.apiote.bimba.czwek.api.LocatablesResponseV2
 import xyz.apiote.bimba.czwek.api.PositionV1
-import xyz.apiote.bimba.czwek.api.QueryablesResponse
-import xyz.apiote.bimba.czwek.api.QueryablesResponseDev
-import xyz.apiote.bimba.czwek.api.QueryablesResponseV1
-import xyz.apiote.bimba.czwek.api.QueryablesResponseV2
 import xyz.apiote.bimba.czwek.api.Server
 import xyz.apiote.bimba.czwek.api.StopV1
 import xyz.apiote.bimba.czwek.api.StopV2
@@ -30,6 +14,26 @@ import xyz.apiote.bimba.czwek.api.UnknownResourceException
 import xyz.apiote.bimba.czwek.api.VehicleV1
 import xyz.apiote.bimba.czwek.api.VehicleV2
 import xyz.apiote.bimba.czwek.api.VehicleV3
+import xyz.apiote.bimba.czwek.api.responses.DeparturesResponse
+import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseDev
+import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseV1
+import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseV2
+import xyz.apiote.bimba.czwek.api.responses.DeparturesResponseV3
+import xyz.apiote.bimba.czwek.api.responses.ErrorResponse
+import xyz.apiote.bimba.czwek.api.responses.LineResponse
+import xyz.apiote.bimba.czwek.api.responses.LineResponseDev
+import xyz.apiote.bimba.czwek.api.responses.LineResponseV1
+import xyz.apiote.bimba.czwek.api.responses.LineResponseV2
+import xyz.apiote.bimba.czwek.api.responses.LocatablesResponse
+import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseDev
+import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseV1
+import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseV2
+import xyz.apiote.bimba.czwek.api.responses.LocatablesResponseV3
+import xyz.apiote.bimba.czwek.api.responses.QueryablesResponse
+import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseDev
+import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseV1
+import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseV2
+import xyz.apiote.bimba.czwek.api.responses.QueryablesResponseV3
 
 // todo [3.2] in Repository check if responses are BARE or HTML
 
@@ -58,7 +62,7 @@ 					response.departures.map { Departure(it) },
 					Stop(response.stop),
 					response.alerts.map { Alert(it) })
 
-				is DeparturesResponseV1 -> StopDepartures(
+				is DeparturesResponseV3 -> StopDepartures(
 					response.departures.map { Departure(it) },
 					Stop(response.stop),
 					response.alerts.map { Alert(it) })
@@ -68,6 +72,11 @@ 					response.departures.map { Departure(it) },
 					Stop(response.stop),
 					response.alerts.map { Alert(it) })
 
+				is DeparturesResponseV1 -> StopDepartures(
+					response.departures.map { Departure(it) },
+					Stop(response.stop),
+					response.alerts.map { Alert(it) })
+
 				else -> null
 			}
 		}
@@ -103,10 +112,10 @@ 						else -> throw UnknownResourceException("locatables", it::class)
 					}
 				}
 
-				is LocatablesResponseV1 -> response.locatables.map {
+				is LocatablesResponseV3 -> response.locatables.map {
 					when (it) {
-						is StopV1 -> Stop(it)
-						is VehicleV1 -> Vehicle(it)
+						is StopV2 -> Stop(it)
+						is VehicleV3 -> Vehicle(it)
 						else -> throw UnknownResourceException("locatables", it::class)
 					}
 				}
@@ -119,6 +128,14 @@ 						else -> throw UnknownResourceException("locatables", it::class)
 					}
 				}
 
+				is LocatablesResponseV1 -> response.locatables.map {
+					when (it) {
+						is StopV1 -> Stop(it)
+						is VehicleV1 -> Vehicle(it)
+						else -> throw UnknownResourceException("locatables", it::class)
+					}
+				}
+
 				else -> null
 			}
 		}
@@ -140,6 +157,7 @@ 			return when (val response =
 				withContext(Dispatchers.IO) { LineResponse.unmarshal(result.stream!!) }) {
 				is LineResponseDev -> Line(response.line)
 				is LineResponseV1 -> Line(response.line)
+				is LineResponseV2 -> Line(response.line)
 				else -> null
 			}
 		}
@@ -200,6 +218,14 @@ 				is QueryablesResponseV2 -> response.queryables.map {
 					when (it) {
 						is StopV2 -> Stop(it)
 						is LineV1 -> Line(it)
+						else -> throw UnknownResourceException("queryablesV2", it::class)
+					}
+				}
+
+				is QueryablesResponseV3 -> response.queryables.map {
+					when (it) {
+						is StopV2 -> Stop(it)
+						is LineV2 -> Line(it)
 						else -> throw UnknownResourceException("queryablesV2", it::class)
 					}
 				}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
index 32ee74575ac55e9ae63bd0e6fd8f3cd53e78fc95..dac98247e3f28bb2da348941ef7ebaaecf27e4a2 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
@@ -14,14 +14,16 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.view.WindowCompat
 import androidx.recyclerview.widget.LinearLayoutManager
-import kotlinx.coroutines.*
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.launch
 import xyz.apiote.bimba.czwek.R
-import xyz.apiote.bimba.czwek.api.*
+import xyz.apiote.bimba.czwek.api.Error
 import xyz.apiote.bimba.czwek.databinding.ActivityResultsBinding
-import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 import xyz.apiote.bimba.czwek.repo.OnlineRepository
 import xyz.apiote.bimba.czwek.repo.Position
 import xyz.apiote.bimba.czwek.repo.Queryable
+import xyz.apiote.bimba.czwek.repo.TrafficResponseException
 
 class ResultsActivity : AppCompatActivity(), LocationListener {
 	enum class Mode {