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"/>