Author: Adam <git@apiote.xyz>
show departures
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 88fa50c2e6286f4d53688dce0b3860a058798f69..8d4aaff42578cccac40b793bd26d41d6b93a4b93 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,9 @@ android:supportsRtl="true" android:theme="@style/Theme.Bimba.Style" tools:targetApi="31"> <activity + android:name=".departures.DeparturesActivity" + android:exported="false" /> + <activity android:name=".search.ResultsActivity" android:exported="false" android:label="@string/title_activity_results" diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt index 67be4d5e21984f6a8dbbf89f1f15195d84b20aad..c1ae81a02ba8a03d5b51abaadeb161af3041b940 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt @@ -15,6 +15,10 @@ suspend fun locateItems(plusCode: String): InputStream { return request("https://bimba.apiote.xyz", "poznan_ztm", "items", mapOf("near" to plusCode)) } +suspend fun getDepartures(stop: String, line: String?): InputStream { + return request("https://bimba.apiote.xyz", "poznan_ztm", "departures", mapOf("code" to stop)) +} + @Suppress("BlockingMethodInNonBlockingContext") suspend fun request( host: String, @@ -38,6 +42,7 @@ ) val c = (url.openConnection() as HttpURLConnection).apply { setRequestProperty("X-Bimba-Token", "ef0179272e7270e1a2da1710a8ba24e1") } + // todo handle errors c.inputStream } } \ No newline at end of file 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 6cb63b72d138c72809b6d7fa963409d6a349d0ed..bd01ca6f68bc9c0da07283037872c1f0d7be1f18 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt @@ -3,6 +3,61 @@ import xyz.apiote.fruchtfleisch.Reader import java.io.InputStream +interface DeparturesResponse { + companion object { + fun printable(it: String) = it.map { + when (Character.getType(it).toByte()) { + Character.CONTROL, Character.FORMAT, Character.PRIVATE_USE, + Character.SURROGATE, Character.UNASSIGNED, Character.OTHER_SYMBOL + -> "\\u%04x".format(it.code) + else -> it.toString() + } + }.joinToString("") + + fun unmarshall(stream: InputStream): DeparturesResponse { + val reader = Reader(stream) + when (reader.readUInt()) { + 0UL -> { + TODO("error response") + } + 1UL -> { + return DeparturesSuccess.unmarshall(stream) + } + else -> { + TODO("throw unknown tag") + } + } + } + } +} + +data class DeparturesSuccess( + val alerts: List<Alert>, + val departures: List<Departure>, + val stop: Stop +) : DeparturesResponse { + companion object { + fun unmarshall(stream: InputStream): DeparturesSuccess { + val alerts = mutableListOf<Alert>() + val departures = mutableListOf<Departure>() + + val reader = Reader(stream) + val alertsNum = reader.readUInt() + for (i in 0UL until alertsNum) { + val alert = Alert.unmarshall(stream) + alerts.add(alert) + } + val departuresNum = reader.readUInt() + for (i in 0UL until departuresNum) { + val departure = Departure.unmarshall(stream) + departures.add(departure) + } + + return DeparturesSuccess(alerts, departures, Stop.unmarshall(stream)) + } + } +} + interface ItemsResponse { companion object { fun unmarshall(stream: InputStream): ItemsResponse { 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 f99673af949bfcf97a51726de147d4a816d77846..25bb90e968345856ed89aef2b161fab580d2c40d 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt @@ -1,8 +1,104 @@ package ml.adamsprogs.bimba.api +import android.content.Context +import android.graphics.* +import android.graphics.drawable.Drawable import android.util.Log +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.toBitmap +import ml.adamsprogs.bimba.R import xyz.apiote.fruchtfleisch.Reader import java.io.InputStream +import kotlin.math.abs +import kotlin.math.pow + +data class Alert( + val header: String, + val Description: String, + val Url: String, + val Cause: UInt, + val Effect: UInt +) { + companion object { + fun unmarshall(stream: InputStream): Alert { + val reader = Reader(stream) + val header = reader.readString() + val description = reader.readString() + val url = reader.readString() + val cause = reader.readU32() + val effect = reader.readU32() + return Alert(header, description, url, cause, effect) + } + } +} + +data class Time( + val Hour: UInt, + val Minute: UInt, + val Second: UInt, + val DayOffset: Byte, + val Zone: String +) { + companion object { + fun unmarshall(stream: InputStream): Time { + val reader = Reader(stream) + return Time( + reader.readUInt().toUInt(), + reader.readUInt().toUInt(), + reader.readUInt().toUInt(), + reader.readI8(), + reader.readString() + ) + } + } +} + +data class Vehicle( + val Position: String, + val Capabilities: UByte, + val Speed: Float, + val CongestionLevel: UByte, + val OccupancyStatus: UByte +) { + companion object { + fun unmarshall(stream: InputStream): Vehicle { + val reader = Reader(stream) + return Vehicle( + reader.readString(), + reader.readU8(), + reader.readFloat32(), + reader.readU8(), + reader.readU8() + ) + } + } +} + +data class Departure( + val line: String, + val headsign: String, + val time: Time, + val status: UByte, + val isRealtime: Boolean, + val stopOrder: String, + val vehicle: Vehicle, + val boarding: UByte +) { + companion object { + fun unmarshall(stream: InputStream): Departure { + val reader = Reader(stream) + val line = reader.readString() + val headsign = reader.readString() + val time = Time.unmarshall(stream) + val status = reader.readU8() + val isRealtime = reader.readBoolean() + val stopOrder = reader.readString() + val vehicle = Vehicle.unmarshall(stream) + val boarding = reader.readU8() + return Departure(line, headsign, time, status, isRealtime, stopOrder, vehicle, boarding) + } + } +} interface Item @@ -44,7 +140,6 @@ } } data class Line( - val textColour: Int, val colour: Int, val type: LineType, val headsignsThere: List<String>, @@ -54,7 +149,75 @@ val graphBack: LineGraph, val name: String ) : Item { override fun toString(): String { - return "$name ($type) [$textColour/$colour]\n→ [${headsignsThere.joinToString()}]\n→ [${headsignsBack.joinToString()}]\n" + 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 + } else { + 0x000000 + } + } + + 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) + return 0.2126 * r + 0.7152 * g + 0.0722 * b + } + + private fun fromSRGB(part: Double): Double { + return if (part <= 0.03928) { + part / 12.92 + } else { + ((part + 0.055) / 1.055).pow(2.4) + } + } + + fun icon(context: Context): Bitmap { + 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 }) + 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 + } + val icon = AppCompatResources.getDrawable(context, iconID) + ?.toBitmap(1920, 1920, Bitmap.Config.ARGB_8888) + canvas.drawBitmap(icon!!, 240f, 240f, Paint().apply { color = textColour() }) + return drawingBitmap + } + + private fun getSquirclePath(left: Int, top: Int, radius: Int): Path { + val radiusToPow = (radius * radius * radius).toDouble() + val path = Path() + path.moveTo(-radius.toFloat(), 0f) + for (x in -radius..radius) path.lineTo( + x.toFloat(), + Math.cbrt(radiusToPow - abs(x * x * x)).toFloat() + ) + for (x in radius downTo -radius) path.lineTo( + x.toFloat(), + -Math.cbrt(radiusToPow - abs(x * x * x)).toFloat() + ) + path.close() + val matrix = Matrix() + matrix.postTranslate((left + radius).toFloat(), (top + radius).toFloat()) + path.transform(matrix) + return path } companion object { @@ -77,7 +240,7 @@ val graphThere = LineGraph.unmarshall(stream) val graphBack = LineGraph.unmarshall(stream) val name = reader.readString() return Line( - name = name, textColour = textColour, + name = name, colour = colour, type = LineType(type.toUInt()), headsignsThere = headsignsThere, headsignsBack = headsignsBack, graphThere = graphThere, graphBack = graphBack ) diff --git a/app/src/main/java/ml/adamsprogs/bimba/dashboard/MainActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/dashboard/MainActivity.kt index 5963e6ff2bfd57957699339564f7d6dec9890bb1..1e1db600a542b551fc02a4e8479c6bf0a060dac8 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/dashboard/MainActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/dashboard/MainActivity.kt @@ -18,11 +18,15 @@ import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import com.google.android.material.bottomnavigation.BottomNavigationView import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.api.Item +import ml.adamsprogs.bimba.api.Line +import ml.adamsprogs.bimba.api.Stop import ml.adamsprogs.bimba.databinding.ActivityMainBinding import ml.adamsprogs.bimba.search.ResultsActivity import ml.adamsprogs.bimba.dashboard.ui.home.HomeFragment import ml.adamsprogs.bimba.dashboard.ui.map.MapFragment import ml.adamsprogs.bimba.dashboard.ui.voyage.VoyageFragment +import ml.adamsprogs.bimba.departures.DeparturesActivity class MainActivity : AppCompatActivity() { @@ -85,7 +89,6 @@ } } fun onGpsClicked(fab: View) { - when (PackageManager.PERMISSION_GRANTED) { ContextCompat.checkSelfPermission( this, @@ -104,6 +107,22 @@ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ) + } + } + } + + fun onSuggestionClicked(item: Item) { + when (item) { + is Stop -> { + val intent = Intent(this, DeparturesActivity::class.java).apply { + putExtra("code", item.code) + putExtra("name", item.name) + // todo line, date, etc + } + startActivity(intent) + } + is Line -> { + TODO("start line graph actvity") } } } diff --git a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt index 744d3b43f1476c3916ee2fd2a3e48920efe4cf9f..a1b274047a5161010c9982befea97dc495bfa2fd 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt @@ -58,7 +58,8 @@ } }) binding.searchBar.setCardViewElevation(0) binding.searchBar.setCustomSuggestionAdapter(BimbaSuggestionsAdapter(layoutInflater, context){ - TODO("On click suggestion") + binding.searchBar.clearSuggestions() + (context as MainActivity).onSuggestionClicked(it) }) binding.floatingActionButton.setOnClickListener { diff --git a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt index 48254fc128da6829a73a41991af55f70db50b0be..b4127b6668c4abbb97446356ec8637d671f1db04 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt @@ -34,13 +34,4 @@ } } } } - - private fun String.printable() = map { - when (Character.getType(it).toByte()) { - Character.CONTROL, Character.FORMAT, Character.PRIVATE_USE, - Character.SURROGATE, Character.UNASSIGNED, Character.OTHER_SYMBOL - -> "\\u%04x".format(it.code) - else -> it.toString() - } - }.joinToString("") } \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt b/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt new file mode 100644 index 0000000000000000000000000000000000000000..6f332e97db71bd37fe532d492121f2c6ba5d221a --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/departures/Departures.kt @@ -0,0 +1,95 @@ +package ml.adamsprogs.bimba.departures + +import android.content.Context +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.api.Departure +import java.util.* + + +class BimbaDepartureViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val root: View = itemView.findViewById(R.id.departure) + val lineIcon: ImageView = itemView.findViewById(R.id.line_icon) + val lineName: TextView = itemView.findViewById(R.id.line_name) + val departureTime: TextView = itemView.findViewById(R.id.departure_time) + val headsign: TextView = itemView.findViewById(R.id.departure_headsign) + + + companion object { + fun bind( + departure: Departure, + holder: BimbaDepartureViewHolder?, + context: Context?, + onClickListener: (Departure) -> Unit + ) { + holder?.root?.setOnClickListener { + onClickListener(departure) + } + // todo line icon + holder?.lineName?.text = departure.line + holder?.headsign?.text = "» ${departure.headsign}" + val now = Calendar.getInstance() + val departureTime = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, departure.time.Hour.toInt()) + set(Calendar.MINUTE, departure.time.Minute.toInt()) + set(Calendar.SECOND, departure.time.Second.toInt()) + // todo zone + roll(Calendar.DAY_OF_MONTH, departure.time.DayOffset.toInt()) + } + var duration = departureTime.timeInMillis - now.timeInMillis + val days = duration / (24 * 60 * 60 * 1000) + duration %= (24 * 60 * 60 * 1000) + val hours = duration / (60 * 60 * 1000) + duration %= (60 * 60 * 1000) + val minutes = duration / (60 * 1000) + duration %= (60 * 1000) + val seconds = duration / 1000 + holder?.departureTime?.text = when (departure.status.toInt()) { + 0 -> { + "in " + + if (hours > 0) { + "$hours h " + } else { + "" + } + "$minutes min" + } + 1 -> "momentarily" + 2 -> "now" + 3 -> "departed" + else -> "" + } + } + } +} + +class BimbaDeparturesAdapter( + private val inflater: LayoutInflater, + private val context: Context?, + private var departures: List<Departure>, + private val onClickListener: ((Departure) -> Unit) +) : + RecyclerView.Adapter<BimbaDepartureViewHolder>() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BimbaDepartureViewHolder { + val rowView = inflater.inflate(R.layout.departure, parent, false) + return BimbaDepartureViewHolder(rowView) + } + + override fun onBindViewHolder(holder: BimbaDepartureViewHolder, position: Int) { + BimbaDepartureViewHolder.bind(departures[position], holder, context, onClickListener) + } + + override fun getItemCount(): Int = departures.size + + fun update(items: List<Departure>) { + departures = items + Log.v("Departures", departures.toString()) + notifyDataSetChanged() + } + +} \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt index 299c9d06176c355fa85f0baacc6a124817d232b2..9a152b387a0476eada01ddaf1c9051f225f5ca5e 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt @@ -3,12 +3,25 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log +import android.view.View +import androidx.core.view.WindowCompat +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import ml.adamsprogs.bimba.api.* import ml.adamsprogs.bimba.databinding.ActivityDeparturesBinding +import ml.adamsprogs.bimba.search.BimbaResultsAdapter +import java.io.InputStream class DeparturesActivity : AppCompatActivity() { private var _binding: ActivityDeparturesBinding? = null private val binding get() = _binding!! + + private lateinit var adapter: BimbaDeparturesAdapter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _binding = ActivityDeparturesBinding.inflate(layoutInflater) @@ -16,6 +29,43 @@ setContentView(binding.root) // setSupportActionBar(binding.departuresAppBar) // supportActionBar?.title = "Półwiejska" - binding.collapsingLayout.title = "Very long stop name that should span over multpile lines" // intent?.extras?.getString("name") + binding.collapsingLayout.title = intent?.extras?.getString("name") + binding.departuresRecycler.layoutManager = LinearLayoutManager(this) + adapter = BimbaDeparturesAdapter(layoutInflater, this, listOf()) { + Log.v("Departure", "clicked: $it") + // todo show bottom sheet + } + binding.departuresRecycler.adapter = adapter + WindowCompat.setDecorFitsSystemWindows(window, false) + + // todo check every 30s + + MainScope().launch { + intent?.extras?.getString("code")?.let { + val departuresStream = getDepartures(it, null) + updateItems(unmarshallDepartureResponse(departuresStream)) + } + } + } + + private suspend fun unmarshallDepartureResponse(stream: InputStream): DeparturesSuccess { + return withContext(Dispatchers.IO) { + when (val response = DeparturesResponse.unmarshall(stream)) { + is DeparturesSuccess -> { + return@withContext response + } + else -> { + TODO("Error response") + } + } + } + } + + private fun updateItems(response: DeparturesSuccess) { + binding.departuresProgress.visibility = View.GONE + binding.departuresRecycler.visibility = View.VISIBLE + adapter.update(response.departures) + // todo alerts + // todo stop info } } \ No newline at end of file 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 2218389bd2574588d9b6f66944be39a319137f43..94594584aef29ccad8bc69e64a00d0750885d2b5 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/search/Results.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/search/Results.kt @@ -38,25 +38,12 @@ holder?.description?.text = item.changeOptions.joinToString { "${it.line} » ${it.headsign}" } } is Line -> { - val icon = when (item.type) { - LineType.TRAM -> { - AppCompatResources.getDrawable(context!!, R.drawable.ic_circle_white_24dp)?.mutate() - } - LineType.BUS -> { - AppCompatResources.getDrawable( - context!!, - R.drawable.ic_square_white_24dp - )?.mutate() // todo bigger and squircle - } - else -> { - null - } - } + val icon = item.icon(context!!) holder?.icon?.apply { - setImageDrawable(icon) + setImageBitmap(icon) colorFilter = null Log.v("Colour", "${item.name}: ${item.colour}, ${item.colour.toString(16)}") - setColorFilter(item.colour, PorterDuff.Mode.SRC_IN) // fixme check colours in TRAFFIC + Log.v("Colour", "${item.name}: ${item.textColour()}, ${item.textColour().toString(16)}") } holder?.title?.text = item.name holder?.description?.text = @@ -134,7 +121,7 @@ val rowView = layoutInflater.inflate(R.layout.suggestion, parent, false) return BimbaViewHolder(rowView) } - override fun getSingleViewHeight(): Int = 64 + override fun getSingleViewHeight(): Int = 64 // todo calculate actual height override fun onBindSuggestionHolder(item: Item, holder: BimbaViewHolder?, pos: Int) { BimbaViewHolder.bind(item, holder, context, onClickListener) diff --git a/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt index 57b5d1f384f54b620c1238774cf8a5846ab228f1..bbf6efd30df91934f5cf3b6949a531fe257d290f 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt @@ -74,9 +74,12 @@ val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 5000, 10f, this ) + locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) + ?.let { onLocationChanged(it) } } override fun onLocationChanged(location: Location) { + Log.v("Location", "found $location") val code = OpenLocationCode.encode(location.latitude, location.longitude) val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager.removeUpdates(this) @@ -98,6 +101,7 @@ } } private fun getItemsByLocation(plusCode: String) { + Log.v("RESPONSE", "getting ItemsByLocation") MainScope().launch { val itemsStream = locateItems(plusCode) updateItems(unmarshallItemResponse(itemsStream)) diff --git a/app/src/main/java/xyz/apiote/fruchtfleisch/Reader.kt b/app/src/main/java/xyz/apiote/fruchtfleisch/Reader.kt index cf700941c0f505541b2e818365e5535880ba7fa1..d6246c44ab04f79f66dccf7913a02bdc2ad3a665 100644 --- a/app/src/main/java/xyz/apiote/fruchtfleisch/Reader.kt +++ b/app/src/main/java/xyz/apiote/fruchtfleisch/Reader.kt @@ -68,6 +68,10 @@ } return result.toUShort() } + fun readI8(): Byte { + return readU8().toByte() + } + fun readFloat32(): Float { return DataInputStream(stream).readFloat() } diff --git a/app/src/main/res/drawable/ic_bus_black_24dp.xml b/app/src/main/res/drawable/ic_bus_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..e2b18a05948d28b39cd2b4bfb102cac858c738bf --- /dev/null +++ b/app/src/main/res/drawable/ic_bus_black_24dp.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#000000" + 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="M4,16c0,0.88 0.39,1.67 1,2.22L5,20c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h8v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1.78c0.61,-0.55 1,-1.34 1,-2.22L20,6c0,-3.5 -3.58,-4 -8,-4s-8,0.5 -8,4v10zM7.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,14 7.5,14s1.5,0.67 1.5,1.5S8.33,17 7.5,17zM16.5,17c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM18,11L6,11L6,6h12v5z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_tram_black_24dp.xml b/app/src/main/res/drawable/ic_tram_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..989349cbc9a456e6458fff607d6ad99c1e9d1c79 --- /dev/null +++ b/app/src/main/res/drawable/ic_tram_black_24dp.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#000000" + 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,16.94L19,8.5c0,-2.79 -2.61,-3.4 -6.01,-3.49l0.76,-1.51L17,3.5L17,2L7,2v1.5h4.75l-0.76,1.52C7.86,5.11 5,5.73 5,8.5v8.44c0,1.45 1.19,2.66 2.59,2.97L6,21.5v0.5h2.23l2,-2L14,20l2,2h2v-0.5L16.5,20h-0.08c1.69,0 2.58,-1.37 2.58,-3.06zM12,18.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM17,14L7,14L7,9h10v5z"/> +</vector> diff --git a/app/src/main/res/layout/activity_departures.xml b/app/src/main/res/layout/activity_departures.xml new file mode 100644 index 0000000000000000000000000000000000000000..91b47cfbe8c7eb229560a7d9f74e23df2ec32d97 --- /dev/null +++ b/app/src/main/res/layout/activity_departures.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/departures_progress" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ProgressBar + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + <com.google.android.material.appbar.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true"> + + <!-- todo toolbar font family --> + <com.google.android.material.appbar.CollapsingToolbarLayout + android:id="@+id/collapsing_layout" + app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" + style="?attr/collapsingToolbarLayoutMediumStyle" + android:layout_width="match_parent" + app:maxLines="2" + android:layout_height="?attr/collapsingToolbarLayoutMediumSize"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/departures_app_bar" + android:layout_width="match_parent" + app:layout_collapseMode="pin" + android:layout_height="?attr/actionBarSize" + android:elevation="0dp" /> + + </com.google.android.material.appbar.CollapsingToolbarLayout> + + </com.google.android.material.appbar.AppBarLayout> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/departures_recycler" + android:layout_width="match_parent" + android:layout_height="match_parent" + + android:visibility="gone" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_results.xml b/app/src/main/res/layout/activity_results.xml index 52fbb1021ffa1df3b2e05915a803979f8160c9fa..5da29b4134691ec1c55cd50e76cead7aecbfceae 100644 --- a/app/src/main/res/layout/activity_results.xml +++ b/app/src/main/res/layout/activity_results.xml @@ -19,6 +19,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> + <!-- todo toolbar font family --> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/departure.xml b/app/src/main/res/layout/departure.xml new file mode 100644 index 0000000000000000000000000000000000000000..4392552af5dfd71ac7cf8fb90038d46af9ca0a44 --- /dev/null +++ b/app/src/main/res/layout/departure.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:id="@+id/departure" + android:layout_height="wrap_content"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/line" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + app:layout_constraintBottom_toBottomOf="@+id/departure_headsign" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/departure_time"> + + <ImageView + android:id="@+id/line_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="@+id/line_name" + app:layout_constraintStart_toStartOf="@+id/line_name" + app:layout_constraintTop_toTopOf="parent"/> + + <TextView + android:id="@+id/line_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="" + android:textAppearance="@style/TextAppearance.Material3.LabelLarge" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/line_icon" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + <TextView + android:id="@+id/departure_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:text="" + android:textAppearance="@style/TextAppearance.Material3.DisplaySmall" + app:layout_constraintStart_toEndOf="@+id/line" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/departure_headsign" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="" + android:textAppearance="@style/TextAppearance.Material3.BodySmall" + app:layout_constraintStart_toStartOf="@+id/departure_time" + app:layout_constraintTop_toBottomOf="@+id/departure_time" /> + + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/suggestion.xml b/app/src/main/res/layout/suggestion.xml index d1eb7758fad295a2a8d7fe4f1c54638ea9a67cef..9eff05aff67bae1f471373a73beb09ea30c57b0a 100644 --- a/app/src/main/res/layout/suggestion.xml +++ b/app/src/main/res/layout/suggestion.xml @@ -3,7 +3,7 @@xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/suggestion" android:layout_width="match_parent" - android:layout_height="64dp"> + android:layout_height="64dp"> <!-- todo wrap_content --> <ImageView android:id="@+id/suggestion_image"