Author: Adam Evyčędo <git@apiote.xyz>
show journey detailed info
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/journeys/Journeys.kt b/app/src/main/java/xyz/apiote/bimba/czwek/journeys/Journeys.kt index 8a4aaacd2d2d7f8e5e24133eae1e752527e347d3..2e42c505cc370dace2a41ec273a5921782b6c697 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/journeys/Journeys.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/journeys/Journeys.kt @@ -8,6 +8,7 @@ import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.RecyclerView @@ -15,6 +16,9 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.google.android.material.card.MaterialCardView import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.repo.Journey +import xyz.apiote.bimba.czwek.units.Metre +import xyz.apiote.bimba.czwek.units.UnitSystem +import java.util.zip.Inflater class JourneysViewHolder(itemView: View) : ViewHolder(itemView) { val root: MaterialCardView = itemView.findViewById(R.id.journey) @@ -28,15 +32,83 @@ fun bind( holder: JourneysViewHolder, onClickListener: (Journey) -> Unit, journey: Journey, - context: Context + context: Context, + inflater: LayoutInflater ) { - holder.root.setOnClickListener { onClickListener(journey) } + holder.root.setOnClickListener { + if (holder.legs.visibility == View.GONE) { + holder.legs.visibility = View.VISIBLE + } else { + holder.legs.visibility = View.GONE + } + onClickListener(journey) + } holder.startTime.text = context.getString(R.string.time, journey.startTime.hour, journey.startTime.minute) holder.endTime.text = context.getString(R.string.time, journey.endTime.hour, journey.endTime.minute) holder.lines.text = journey.legs.map { it.start.vehicle.Line.name }.filter { it.isNotBlank() }.joinToString() + + holder.legs.removeAllViews() + journey.legs.forEach { + val legView = inflater.inflate(R.layout.journey_leg, null, false) + + val legOrigin = legView.findViewById<TextView>(R.id.leg_origin) + if (it.origin.stop.name.isBlank() || it.origin.stop.name == "START") { + legOrigin.visibility = View.GONE + } else { + legOrigin.apply { + text = it.origin.stop.name + visibility = View.VISIBLE + } + } + + val legOriginTime = legView.findViewById<TextView>(R.id.leg_origin_time) + legOriginTime.text = context.getString( + R.string.time, + it.start.departureTime!!.Hour.toInt(), + it.start.departureTime.Minute.toInt() + ) + + val legModeImage = legView.findViewById<ImageView>(R.id.leg_mode_image) + legModeImage.setImageDrawable(it.start.vehicle.Line.icon(context)) + // TODO legModeImage.contentDescription = translate "leg mode: $mode" + + val legLine = legView.findViewById<TextView>(R.id.leg_line) + legLine.apply { + if (it.start.vehicle.Line.name.isBlank()) { + visibility = View.GONE + } else { + visibility = View.VISIBLE + text = it.start.vehicle.Line.name + } + } + + val distance = legView.findViewById<TextView>(R.id.leg_distance) + distance.text = if (it.start.vehicle.Line.name.isBlank() && it.distance != null) { + val us = UnitSystem.getSelected(context) + us.toString(context, it.distance) + } else { + context.resources.getQuantityString(R.plurals.number_stops, it.intermediateStops.size+1, it.intermediateStops.size+1) + } + + val legDestination = legView.findViewById<TextView>(R.id.leg_destination) + if (it.destination.stop.name.isBlank() || it.destination.stop.name == "END") { + legDestination.visibility = View.GONE + } else { + legDestination.apply { + text = it.destination.stop.name + visibility = View.VISIBLE + } + } + + val legDestinationTime = legView.findViewById<TextView>(R.id.leg_destination_time) + legDestinationTime.text = + context.getString(R.string.time, it.end.arrivalTime!!.Hour.toInt(), it.end.arrivalTime.Minute.toInt()) + + holder.legs.addView(legView) + } } } } @@ -60,7 +132,7 @@ override fun onBindViewHolder( holder: JourneysViewHolder, position: Int ) { - JourneysViewHolder.bind(holder, onClickListener, items[position], context) + JourneysViewHolder.bind(holder, onClickListener, items[position], context, inflater) } override fun getItemCount(): Int = items.size diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt index e807424c10d11689a915fbbbd984354d5c0474ff..058c1489442f28428b92c9a0398c7af00dfea2fe 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/journeys/JourneysActivity.kt @@ -6,21 +6,33 @@ package xyz.apiote.bimba.czwek.journeys import android.content.Context import android.content.Intent +import android.content.res.Configuration.UI_MODE_NIGHT_MASK +import android.content.res.Configuration.UI_MODE_NIGHT_UNDEFINED +import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.os.Build import android.os.Bundle +import android.util.Log import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager +import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.util.BoundingBox +import org.osmdroid.views.CustomZoomButtonsController +import org.osmdroid.views.overlay.TilesOverlay +import org.osmdroid.views.overlay.gestures.RotationGestureOverlay import xyz.apiote.bimba.czwek.databinding.ActivityJourneysBinding +import xyz.apiote.bimba.czwek.dpToPixelI import xyz.apiote.bimba.czwek.repo.Place import kotlin.math.max import kotlin.math.min class JourneysActivity : AppCompatActivity() { + private lateinit var binding: ActivityJourneysBinding + companion object { const val ORIGIN_PARAM = "origin" const val DESTINATION_PARAM = "destination" @@ -49,7 +61,6 @@ @Suppress("DEPRECATION") intent.getParcelableExtra(DESTINATION_PARAM) } ?: throw Exception("Destination not given") - private lateinit var binding: ActivityJourneysBinding override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -58,31 +69,55 @@ setContentView(binding.root) val journeysViewModel = ViewModelProvider(this)[JourneysViewModel::class.java] - // TODO check insets - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets + // TODO check upside-down + // TODO for horizontal make side sheet + ViewCompat.setOnApplyWindowInsetsListener(binding.journeys) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updatePadding(bottom = insets.bottom + dpToPixelI(16f)) + WindowInsetsCompat.CONSUMED } - val origin = getOrigin() - val destination = getDestination() - // FIXME - binding.map.zoomToBoundingBox(BoundingBox( - max(origin.latitude, destination.latitude), - max(origin.longitude, destination.longitude), - min(origin.latitude, destination.latitude), - min(origin.longitude, destination.longitude), - ), false) + binding.map.setTileSource(TileSourceFactory.MAPNIK) + if (((resources?.configuration?.uiMode ?: UI_MODE_NIGHT_UNDEFINED) + and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES + ) { + binding.map.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS) + } + binding.map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) + binding.map.maxZoomLevel = 21.5 + binding.map.minZoomLevel = 5.5 + binding.map.setMultiTouchControls(true) + binding.map.overlays.add(RotationGestureOverlay(binding.map).apply { isEnabled = true }) binding.journeys.layoutManager = LinearLayoutManager(this) + val origin = getOrigin() + val destination = getDestination() + journeysViewModel.journeys.observe(this) { + zoomMap(100) binding.journeys.adapter = JourneysAdapter(layoutInflater, this, it) { // todo move map accordingly // todo show shape on map } } journeysViewModel.getJourneys(this, origin, destination) + } + + override fun onStart() { + super.onStart() + zoomMap() + } + + fun zoomMap(margin: Int = 0, box: BoundingBox? = null) { + val origin = getOrigin() + val destination = getDestination() + val bb = box?:BoundingBox( + max(origin.latitude, destination.latitude), + max(origin.longitude, destination.longitude), + min(origin.latitude, destination.latitude), + min(origin.longitude, destination.longitude), + ) + binding.map.zoomToBoundingBox(bb, false, margin) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineAbstract.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineAbstract.kt index 8cfbfc83fdca77fbde3ae8077f613596bc0317cf..3073620c339606475ca5c6d963cc04a23966f30c 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineAbstract.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineAbstract.kt @@ -73,6 +73,7 @@ LineType.FUNICULAR -> R.drawable.funicular_black LineType.MONORAIL -> R.drawable.monorail_black LineType.UNKNOWN -> R.drawable.vehicle_black LineType.PLANE -> R.drawable.plane_black + LineType.WALK -> R.drawable.walk_black } val icon = AppCompatResources.getDrawable(context, iconID)?.mutate()?.apply { setTint(textColour(colour)) diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineType.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineType.kt index be76479081cc2763d596e2271e83e03096730d91..29612aa9cd60bebeefcd4423bc7bb84ad1164368 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineType.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/LineType.kt @@ -10,7 +10,7 @@ import xyz.apiote.bimba.czwek.api.LineTypeV3 import xyz.apiote.bimba.czwek.api.transitous.model.Mode enum class LineType { - UNKNOWN, TRAM, BUS, TROLLEYBUS, METRO, RAIL, FERRY, CABLE_TRAM, CABLE_CAR, FUNICULAR, MONORAIL, PLANE; + UNKNOWN, TRAM, BUS, TROLLEYBUS, METRO, RAIL, FERRY, CABLE_TRAM, CABLE_CAR, FUNICULAR, MONORAIL, PLANE, WALK; companion object { fun of(t: LineTypeV1): LineType { @@ -47,7 +47,7 @@ fun fromTransitous2(mode: Mode): LineType { return when(mode) { Mode.AIRPLANE -> PLANE - Mode.WALK -> UNKNOWN + Mode.WALK -> WALK Mode.BIKE -> UNKNOWN Mode.CAR -> UNKNOWN Mode.RENTAL -> UNKNOWN diff --git a/app/src/main/res/drawable/walk_black.xml b/app/src/main/res/drawable/walk_black.xml new file mode 100644 index 0000000000000000000000000000000000000000..ae56cca4f7a58709c49b5f78ddb5306168f1ed65 --- /dev/null +++ b/app/src/main/res/drawable/walk_black.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +SPDX-FileCopyrightText: Google + +SPDX-License-Identifier: Apache-2.0 +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:autoMirrored="true" + android:tint="#000000" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M13.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM9.8,8.9L7,23h2.1l1.8,-8 2.1,2v6h2v-7.5l-2.1,-2 0.6,-3C14.8,12 16.8,13 19,13v-2c-1.9,0 -3.5,-1 -4.3,-2.4l-1,-1.6c-0.4,-0.6 -1,-1 -1.7,-1 -0.3,0 -0.5,0.1 -0.8,0.1L6,8.3V13h2V9.6l1.8,-0.7" /> +</vector> diff --git a/app/src/main/res/layout/activity_journeys.xml b/app/src/main/res/layout/activity_journeys.xml index 245ab2b24414a10047326b0a6e871574f155987e..23884c043666b148164bde917d548dbf2229c97b 100644 --- a/app/src/main/res/layout/activity_journeys.xml +++ b/app/src/main/res/layout/activity_journeys.xml @@ -17,10 +17,10 @@ android:layout_height="match_parent" /> <androidx.constraintlayout.widget.ConstraintLayout app:behavior_hideable="false" - android:id="@+id/standard_bottom_sheet" + android:id="@+id/journeys_bottom_sheet" style="@style/Widget.Material3.BottomSheet" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="640dp" app:behavior_peekHeight="256dp" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> @@ -36,6 +36,7 @@android:id="@+id/journeys" android:layout_width="match_parent" android:layout_height="wrap_content" + android:clipToPadding="false" app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_constraintTop_toBottomOf="@+id/drag_handle" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/journey.xml b/app/src/main/res/layout/journey.xml index ecc05a3f4474b5cfd7e418ac9610bb451d3ae3e5..be5120d120ac8514a2a112c47feef904733d0a6b 100644 --- a/app/src/main/res/layout/journey.xml +++ b/app/src/main/res/layout/journey.xml @@ -49,6 +49,7 @@ tools:text="12:30" /> <LinearLayout android:id="@+id/legs" + android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" diff --git a/app/src/main/res/layout/journey_leg.xml b/app/src/main/res/layout/journey_leg.xml index 4d09620b97d748d141e5df41708710620432c8fc..dcf3850b25ca8890463b1d5f4b7366b0a780bb66 100644 --- a/app/src/main/res/layout/journey_leg.xml +++ b/app/src/main/res/layout/journey_leg.xml @@ -15,7 +15,7 @@ android:id="@+id/leg_origin_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/origin" - android:contentDescription="beginning of journey's leg" + android:contentDescription="@string/beginning_of_journey_s_leg" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -47,15 +47,16 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:src="@drawable/bike" - android:contentDescription="mode of journey's leg: walk" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/leg_origin_image" /> + app:layout_constraintTop_toBottomOf="@+id/leg_origin_image" + tool:ignore="ContentDescription" /> <com.google.android.material.textview.MaterialTextView android:id="@+id/leg_line" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:paddingEnd="8dp" + android:paddingStart="8dp" android:textAppearance="@style/TextAppearance.Material3.BodyLarge" app:layout_constraintBottom_toBottomOf="@+id/leg_mode_image" app:layout_constraintStart_toEndOf="@+id/leg_mode_image" @@ -66,7 +67,7 @@ android:id="@+id/leg_distance" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="8dp" android:textAppearance="@style/TextAppearance.Material3.LabelSmall" app:layout_constraintBottom_toBottomOf="@+id/leg_line" app:layout_constraintStart_toEndOf="@+id/leg_line" @@ -79,7 +80,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:src="@drawable/destination" - android:contentDescription="end of journey's leg" + android:contentDescription="@string/end_of_journey_s_leg" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/leg_mode_image" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04ed881557c310fb27e0ee81e4aa3dc498b4e343..2f796af4d2f8128f2c97dedefdb33ccc04e53508 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -303,4 +303,10 @@ Now <string name="wheelchair_accessible">Wheelchair accessible</string> <string name="bike_transport">Bike transport</string> <string name="go">Go</string> + <string name="beginning_of_journey_s_leg">beginning of journey\'s leg</string> + <string name="end_of_journey_s_leg">end of journey\'s leg</string> + <plurals name="number_stops"> + <item quantity="one">%1$d stop</item> + <item quantity="other">%1$d stops</item> + </plurals> </resources> diff --git a/build.gradle.kts b/build.gradle.kts index 469ffc011b5a5a8007953b795633669f6779049e..c746a3ec056432afbc9711d8b48250689c0d939c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,8 +4,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.7.2" apply false - id("com.android.library") version "8.7.2" apply false + id("com.android.application") version "8.7.3" apply false + id("com.android.library") version "8.7.3" apply false id("org.openapi.generator") version "7.9.0" apply false id("de.undercouch.download") version "5.6.0" apply false kotlin("android") version "2.0.10" apply false