Bimba.git

commit 6f38b586f615a021d17d794b1517d692eedb5be7

Author: Adam <git@apiote.xyz>

line icons and colours

 app/build.gradle | 6 
 app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt | 10 
 app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt | 170 ++-
 app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt | 4 
 app/src/main/java/ml/adamsprogs/bimba/search/Results.kt | 2 
 app/src/main/res/layout/activity_departures.xml | 2 
 app/src/main/res/layout/departure.xml | 4 


diff --git a/app/build.gradle b/app/build.gradle
index 3d4a926b038c33d79e7f9a00f2117c12261ca0c8..6d64a8752a2e95ce35b413458486c016d695a374 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,12 +4,12 @@     id 'org.jetbrains.kotlin.android'
 }
 
 android {
-    compileSdk 31
+    compileSdk 33
 
     defaultConfig {
         applicationId "ml.adamsprogs.bimba"
         minSdk 21
-        targetSdk 31
+        targetSdk 33
         versionCode 1
         versionName "1.0"
 
@@ -36,7 +36,7 @@ }
 
 dependencies {
     implementation 'androidx.core:core-ktx:1.8.0'
-    implementation 'androidx.appcompat:appcompat:1.4.0'
+    implementation 'androidx.appcompat:appcompat:1.5.0'
     implementation 'com.google.android.material:material:1.6.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'




diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt
index bd01ca6f68bc9c0da07283037872c1f0d7be1f18..30173fc3e7147a28c49168583853b62f3d1aab17 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt
@@ -44,16 +44,16 @@
 			val reader = Reader(stream)
 			val alertsNum = reader.readUInt()
 			for (i in 0UL until alertsNum) {
-				val alert = Alert.unmarshall(stream)
+				val alert = Alert.unmarshal(stream)
 				alerts.add(alert)
 			}
 			val departuresNum = reader.readUInt()
 			for (i in 0UL until departuresNum) {
-				val departure = Departure.unmarshall(stream)
+				val departure = Departure.unmarshal(stream)
 				departures.add(departure)
 			}
 
-			return DeparturesSuccess(alerts, departures, Stop.unmarshall(stream))
+			return DeparturesSuccess(alerts, departures, Stop.unmarshal(stream))
 		}
 	}
 }
@@ -86,10 +86,10 @@ 			val itemsNum = reader.readUInt()
 			for (i in 0UL until itemsNum) {
 				when (reader.readUInt()) {
 					0UL -> {
-						items.add(Stop.unmarshall(stream))
+						items.add(Stop.unmarshal(stream))
 					}
 					1UL -> {
-						items.add(Line.unmarshall(stream))
+						items.add(Line.unmarshal(stream))
 					}
 					else -> {
 						TODO("throw unknown tag")




diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt
index 25bb90e968345856ed89aef2b161fab580d2c40d..afa8914e6904f3a2179502fd69572a4f08a9679b 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt
@@ -2,8 +2,8 @@ package ml.adamsprogs.bimba.api
 
 import android.content.Context
 import android.graphics.*
-import android.graphics.drawable.Drawable
 import android.util.Log
+import androidx.annotation.ColorInt
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.graphics.drawable.toBitmap
 import ml.adamsprogs.bimba.R
@@ -20,7 +20,7 @@ 	val Cause: UInt,
 	val Effect: UInt
 ) {
 	companion object {
-		fun unmarshall(stream: InputStream): Alert {
+		fun unmarshal(stream: InputStream): Alert {
 			val reader = Reader(stream)
 			val header = reader.readString()
 			val description = reader.readString()
@@ -40,7 +40,7 @@ 	val DayOffset: Byte,
 	val Zone: String
 ) {
 	companion object {
-		fun unmarshall(stream: InputStream): Time {
+		fun unmarshal(stream: InputStream): Time {
 			val reader = Reader(stream)
 			return Time(
 				reader.readUInt().toUInt(),
@@ -53,6 +53,27 @@ 		}
 	}
 }
 
+data class Colour(val R: UByte, val G: UByte, val B: UByte) {
+	companion object {
+		fun unmarshal(stream: InputStream): Colour {
+			val reader = Reader(stream)
+			return Colour(
+				reader.readU8(),
+				reader.readU8(),
+				reader.readU8()
+			)
+		}
+	}
+
+	fun toInt(): Int {
+		var rgb = 0xff
+		rgb = (rgb shl 8) + R.toInt()
+		rgb = (rgb shl 8) + G.toInt()
+		rgb = (rgb shl 8) + B.toInt()
+		return rgb
+	}
+}
+
 data class Vehicle(
 	val Position: String,
 	val Capabilities: UByte,
@@ -61,7 +82,7 @@ 	val CongestionLevel: UByte,
 	val OccupancyStatus: UByte
 ) {
 	companion object {
-		fun unmarshall(stream: InputStream): Vehicle {
+		fun unmarshal(stream: InputStream): Vehicle {
 			val reader = Reader(stream)
 			return Vehicle(
 				reader.readString(),
@@ -74,8 +95,32 @@ 		}
 	}
 }
 
+data class LineStub(
+	val colour: Colour,
+	val type: LineType,
+	val name: String
+) : LineAbstract {
+	companion object {
+		fun unmarshal(stream: InputStream): LineStub {
+			val reader = Reader(stream)
+			val colour = Colour.unmarshal(stream)
+			val type = reader.readUInt()
+			val name = reader.readString()
+			return LineStub(name = name, colour = colour, type = LineType(type.toUInt()))
+		}
+	}
+
+	fun icon(context: Context): Bitmap {
+		return super.icon(context, type, colour)
+	}
+
+	fun textColour(): Int {
+		return super.textColour(colour)
+	}
+}
+
 data class Departure(
-	val line: String,
+	val line: LineStub,
 	val headsign: String,
 	val time: Time,
 	val status: UByte,
@@ -85,15 +130,15 @@ 	val vehicle: Vehicle,
 	val boarding: UByte
 ) {
 	companion object {
-		fun unmarshall(stream: InputStream): Departure {
+		fun unmarshal(stream: InputStream): Departure {
 			val reader = Reader(stream)
-			val line = reader.readString()
+			val line = LineStub.unmarshal(stream)
 			val headsign = reader.readString()
-			val time = Time.unmarshall(stream)
+			val time = Time.unmarshal(stream)
 			val status = reader.readU8()
 			val isRealtime = reader.readBoolean()
 			val stopOrder = reader.readString()
-			val vehicle = Vehicle.unmarshall(stream)
+			val vehicle = Vehicle.unmarshal(stream)
 			val boarding = reader.readU8()
 			return Departure(line, headsign, time, status, isRealtime, stopOrder, vehicle, boarding)
 		}
@@ -117,7 +162,7 @@ 		return result
 	}
 
 	companion object {
-		fun unmarshall(stream: InputStream): Stop {
+		fun unmarshal(stream: InputStream): Stop {
 			val reader = Reader(stream)
 			val code = reader.readString()
 			val zone = reader.readString()
@@ -125,7 +170,7 @@ 			val position = reader.readString()
 			val chOptionsNum = reader.readUInt()
 			val changeOptions = mutableListOf<ChangeOption>()
 			for (i in 0UL until chOptionsNum) {
-				changeOptions.add(ChangeOption.unmarshall(stream))
+				changeOptions.add(ChangeOption.unmarshal(stream))
 			}
 			val name = reader.readString()
 			return Stop(
@@ -139,34 +184,22 @@ 		}
 	}
 }
 
-data class Line(
-	val colour: Int,
-	val type: LineType,
-	val headsignsThere: List<String>,
-	val headsignsBack: List<String>,
-	val graphThere: LineGraph,
-	val graphBack: LineGraph,
-	val name: String
-) : Item {
-	override fun toString(): String {
-		return "$name ($type) [${textColour()}/$colour]\n→ [${headsignsThere.joinToString()}]\n→ [${headsignsBack.joinToString()}]\n"
-	}
-
-	fun textColour(): Int {
-		val black = relativeLuminance(0x000000) + .05
-		val white = relativeLuminance(0xffffff) + .05
-		val colour = relativeLuminance(this.colour) + .05
-		return if (white / colour > colour / black) {
-			0xffffff
+interface LineAbstract {
+	fun textColour(c: Colour): Int {
+		val black = relativeLuminance(Colour(0u, 0u, 0u)) + .05
+		val white = relativeLuminance(Colour(255u, 255u, 255u)) + .05
+		val colour = relativeLuminance(c) + .05
+		return if ((white / colour) > (colour / black)) {
+			Color.WHITE
 		} else {
-			0x000000
+			Color.BLACK
 		}
 	}
 
-	private fun relativeLuminance(colour: Int): Double {
-		val r = fromSRGB((colour / 0xffff).toDouble() / 0xff)
-		val g = fromSRGB((colour / 0xff).and(0xff).toDouble() / 0xff)
-		val b = fromSRGB(colour.and(0xff).toDouble() / 0xff)
+	private fun relativeLuminance(colour: Colour): Double {
+		val r = fromSRGB(colour.R.toDouble() / 0xff)
+		val g = fromSRGB(colour.G.toDouble() / 0xff)
+		val b = fromSRGB(colour.B.toDouble() / 0xff)
 		return 0.2126 * r + 0.7152 * g + 0.0722 * b
 	}
 
@@ -178,30 +211,31 @@ 			((part + 0.055) / 1.055).pow(2.4)
 		}
 	}
 
-	fun icon(context: Context): Bitmap {
+	fun icon(context: Context, type: LineType, colour: Colour): Bitmap {
+		// fixme dimensions
 		val drawingBitmap = Bitmap.createBitmap(
 			2400,
 			2400,
 			Bitmap.Config.ARGB_8888
 		)
 		val canvas = Canvas(drawingBitmap)
-		val squirclePaint = Paint().apply {
 
-		}
-		canvas.drawPath(getSquirclePath(80, 80, 2240), Paint().apply { color = textColour() })
-		canvas.drawPath(getSquirclePath(160, 160, 2080), Paint().apply { color = colour })
+		canvas.drawPath(getSquirclePath(80, 80, 1120), Paint().apply { color = textColour(colour) })
+		canvas.drawPath(getSquirclePath(160, 160, 1040), Paint().apply { color = colour.toInt() })
 		val iconID = when (type) {
 			LineType.BUS -> R.drawable.ic_bus_black_24dp
 			LineType.TRAM -> R.drawable.ic_tram_black_24dp
-			LineType.UNKNOWN -> R.drawable.ic_square_white_24dp
+			LineType.UNKNOWN -> R.drawable.ic_square_white_24dp // todo generic vehicle
 		}
-		val icon = AppCompatResources.getDrawable(context, iconID)
-			?.toBitmap(1920, 1920, Bitmap.Config.ARGB_8888)
-		canvas.drawBitmap(icon!!, 240f, 240f, Paint().apply { color = textColour() })
+		val icon = AppCompatResources.getDrawable(context, iconID)?.mutate()
+			?.apply {
+				setTint(textColour(colour))
+			}?.toBitmap(1920, 1920, Bitmap.Config.ARGB_8888)
+		canvas.drawBitmap(icon!!, 240f, 240f, Paint())
 		return drawingBitmap
 	}
 
-	private fun getSquirclePath(left: Int, top: Int, radius: Int): Path {
+	private fun getSquirclePath(left: Int, top: Int, radius: Int): Path {  // fixme draws beyond canvas
 		val radiusToPow = (radius * radius * radius).toDouble()
 		val path = Path()
 		path.moveTo(-radius.toFloat(), 0f)
@@ -219,12 +253,33 @@ 		matrix.postTranslate((left + radius).toFloat(), (top + radius).toFloat())
 		path.transform(matrix)
 		return path
 	}
+}
+
+data class Line(
+	val colour: Colour,
+	val type: LineType,
+	val headsignsThere: List<String>,
+	val headsignsBack: List<String>,
+	val graphThere: LineGraph,
+	val graphBack: LineGraph,
+	val name: String
+) : Item, LineAbstract {
+	override fun toString(): String {
+		return "$name ($type) [${textColour()}/$colour]\n→ [${headsignsThere.joinToString()}]\n→ [${headsignsBack.joinToString()}]\n"
+	}
+
+	fun icon(context: Context): Bitmap {
+		return super.icon(context, type, colour)
+	}
+
+	fun textColour(): Int {
+		return super.textColour(colour)
+	}
 
 	companion object {
-		fun unmarshall(stream: InputStream): Line {
+		fun unmarshal(stream: InputStream): Line {
 			val reader = Reader(stream)
-			val textColour = reader.readInt().toInt()
-			val colour = reader.readInt().toInt()
+			val colour = Colour.unmarshal(stream)
 			val type = reader.readUInt()
 			val headsignsThereNum = reader.readUInt()
 			val headsignsThere = mutableListOf<String>()
@@ -236,13 +291,13 @@ 			val headsignsBack = mutableListOf()
 			for (i in 0UL until headsignsBackNum) {
 				headsignsBack.add(reader.readString())
 			}
-			val graphThere = LineGraph.unmarshall(stream)
-			val graphBack = LineGraph.unmarshall(stream)
+			val graphThere = LineGraph.unmarshal(stream)
+			val graphBack = LineGraph.unmarshal(stream)
 			val name = reader.readString()
 			return Line(
-				name = name,
-				colour = colour, type = LineType(type.toUInt()), headsignsThere = headsignsThere,
-				headsignsBack = headsignsBack, graphThere = graphThere, graphBack = graphBack
+				name = name, colour = colour, type = LineType(type.toUInt()),
+				headsignsThere = headsignsThere, headsignsBack = headsignsBack, graphThere = graphThere,
+				graphBack = graphBack
 			)
 		}
 	}
@@ -253,7 +308,6 @@ 	TRAM, BUS, UNKNOWN
 }
 
 fun LineType(type: UInt): LineType {
-	Log.d("LineType", "$type")
 	return when (type) {
 		0U -> LineType.valueOf("TRAM")
 		3U -> LineType.valueOf("BUS")
@@ -263,7 +317,7 @@ }
 
 data class ChangeOption(val line: String, val headsign: String) {
 	companion object {
-		fun unmarshall(stream: InputStream): ChangeOption {
+		fun unmarshal(stream: InputStream): ChangeOption {
 			val reader = Reader(stream)
 			return ChangeOption(line = reader.readString(), headsign = reader.readString())
 		}
@@ -276,12 +330,12 @@ 	val nextNodes: Map>,
 	val prevNodes: Map<Long, List<Long>>
 ) {
 	companion object {
-		fun unmarshall(stream: InputStream): LineGraph {
+		fun unmarshal(stream: InputStream): LineGraph {
 			val reader = Reader(stream)
 			val stopsNum = reader.readUInt()
 			val stops = mutableListOf<StopStub>()
 			for (i in 0UL until stopsNum) {
-				stops.add(StopStub.unmarshall(stream))
+				stops.add(StopStub.unmarshal(stream))
 			}
 			val nextNodesNum = reader.readUInt()
 			val nextNodes = mutableMapOf<Long, List<Long>>()
@@ -313,7 +367,7 @@ }
 
 data class StopStub(val name: String, val code: String, val zone: String, val onDemand: Boolean) {
 	companion object {
-		fun unmarshall(stream: InputStream): StopStub {
+		fun unmarshal(stream: InputStream): StopStub {
 			val reader = Reader(stream)
 			return StopStub(
 				code = reader.readString(),




diff --git a/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt b/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt
index 6f332e97db71bd37fe532d492121f2c6ba5d221a..b83f22452740c0609385f489d0f6da6d7a3e5a4e 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt
@@ -31,8 +31,8 @@ 		) {
 			holder?.root?.setOnClickListener {
 				onClickListener(departure)
 			}
-			// todo line icon
-			holder?.lineName?.text = departure.line
+			holder?.lineIcon?.setImageBitmap(departure.line.icon(context!!))
+			holder?.lineName?.text = departure.line.name
 			holder?.headsign?.text = "» ${departure.headsign}"
 			val now = Calendar.getInstance()
 			val departureTime = Calendar.getInstance().apply {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/search/Results.kt b/app/src/main/java/ml/adamsprogs/bimba/search/Results.kt
index 94594584aef29ccad8bc69e64a00d0750885d2b5..42532933b71bfadaf09cdb132fb8a81224e58d7c 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/search/Results.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/search/Results.kt
@@ -42,7 +42,7 @@ 					val icon = item.icon(context!!)
 					holder?.icon?.apply {
 						setImageBitmap(icon)
 						colorFilter = null
-						Log.v("Colour", "${item.name}: ${item.colour}, ${item.colour.toString(16)}")
+						Log.v("Colour", "${item.name}: ${item.colour}, ${item.colour.toInt().toString(16)}")
 						Log.v("Colour", "${item.name}: ${item.textColour()}, ${item.textColour().toString(16)}")
 					}
 					holder?.title?.text = item.name




diff --git a/app/src/main/res/layout/activity_departures.xml b/app/src/main/res/layout/activity_departures.xml
index 91b47cfbe8c7eb229560a7d9f74e23df2ec32d97..24eb489bca14053dbfd46471fd0960428cbbfbb7 100644
--- a/app/src/main/res/layout/activity_departures.xml
+++ b/app/src/main/res/layout/activity_departures.xml
@@ -42,6 +42,8 @@
 		</com.google.android.material.appbar.CollapsingToolbarLayout>
 
 	</com.google.android.material.appbar.AppBarLayout>
+
+	<!-- is cut at the bottom -->
 	<androidx.recyclerview.widget.RecyclerView
 		android:id="@+id/departures_recycler"
 		android:layout_width="match_parent"




diff --git a/app/src/main/res/layout/departure.xml b/app/src/main/res/layout/departure.xml
index 4392552af5dfd71ac7cf8fb90038d46af9ca0a44..1cff5f1dd2a69000e7717ef7b9b104dec7872e66 100644
--- a/app/src/main/res/layout/departure.xml
+++ b/app/src/main/res/layout/departure.xml
@@ -16,8 +16,8 @@ 		app:layout_constraintTop_toTopOf="@+id/departure_time">
 
 		<ImageView
 			android:id="@+id/line_icon"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
+			android:layout_width="16dp"
+			android:layout_height="16dp"
 			app:layout_constraintEnd_toEndOf="@+id/line_name"
 			app:layout_constraintStart_toStartOf="@+id/line_name"
 			app:layout_constraintTop_toTopOf="parent"/>