Author: Adam Evyčędo <git@apiote.xyz>
improve journey map
%!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 2e42c505cc370dace2a41ec273a5921782b6c697..4044d0012f2868e09b4e2eca7e321d251da1a930 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 @@ -5,6 +5,7 @@ package xyz.apiote.bimba.czwek.journeys import android.content.Context +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -14,6 +15,7 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.google.android.material.card.MaterialCardView +import kotlinx.serialization.descriptors.PrimitiveKind import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.repo.Journey import xyz.apiote.bimba.czwek.units.Metre @@ -30,18 +32,15 @@ companion object { fun bind( holder: JourneysViewHolder, - onClickListener: (Journey) -> Unit, + onClickListener: (Journey, Int) -> Unit, journey: Journey, context: Context, - inflater: LayoutInflater + inflater: LayoutInflater, + isOpen: Boolean, + position: Int ) { holder.root.setOnClickListener { - if (holder.legs.visibility == View.GONE) { - holder.legs.visibility = View.VISIBLE - } else { - holder.legs.visibility = View.GONE - } - onClickListener(journey) + onClickListener(journey, position) } holder.startTime.text = context.getString(R.string.time, journey.startTime.hour, journey.startTime.minute) @@ -90,7 +89,11 @@ 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) + context.resources.getQuantityString( + R.plurals.number_stops, + it.intermediateStops.size + 1, + it.intermediateStops.size + 1 + ) } val legDestination = legView.findViewById<TextView>(R.id.leg_destination) @@ -105,10 +108,15 @@ } 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()) + context.getString( + R.string.time, + it.end.arrivalTime!!.Hour.toInt(), + it.end.arrivalTime.Minute.toInt() + ) holder.legs.addView(legView) } + holder.legs.visibility = if (isOpen) View.VISIBLE else View.GONE } } } @@ -117,9 +125,21 @@ class JourneysAdapter( private val inflater: LayoutInflater, private val context: Context, private var items: List<Journey>, - private val onClickListener: ((Journey) -> Unit), + private val onClickListener: ((Journey, Boolean) -> Unit), ) : RecyclerView.Adapter<JourneysViewHolder>() { + var openCard: Int = -1 + + val onClickListener2: ((Journey, Int) -> Unit) = { journey, position -> + Log.i("Journey", "open: $openCard, clicked: $position") + val previouslyOpen = openCard + openCard = if (position == openCard) -1 else position + Log.i("Journey", "open: $openCard, notifying: $previouslyOpen, $position, hide: ${openCard == -1}") + notifyItemChanged(previouslyOpen) + notifyItemChanged(position) + onClickListener(journey, openCard == -1) + } + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int @@ -132,7 +152,15 @@ override fun onBindViewHolder( holder: JourneysViewHolder, position: Int ) { - JourneysViewHolder.bind(holder, onClickListener, items[position], context, inflater) + JourneysViewHolder.bind( + holder, + onClickListener2, + items[position], + context, + inflater, + openCard == position, + position + ) } 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 df585e19e87fbc83d7dac59de1f5af37351ec5e5..db4beeaad871f5e4bb57fdd0bb349199f2cc36cf 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 @@ -32,8 +32,10 @@ import org.osmdroid.views.overlay.gestures.RotationGestureOverlay import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.databinding.ActivityJourneysBinding import xyz.apiote.bimba.czwek.dpToPixelI +import xyz.apiote.bimba.czwek.repo.Colour import xyz.apiote.bimba.czwek.repo.JourneyParams import xyz.apiote.bimba.czwek.repo.Place +import xyz.apiote.bimba.czwek.repo.Position import kotlin.math.max import kotlin.math.min @@ -46,10 +48,7 @@ const val DESTINATION_PARAM = "destination" const val PARAMS_PARAM = "params" fun getIntent( - context: Context, - origin: Place, - destination: Place, - params: JourneyParams + context: Context, origin: Place, destination: Place, params: JourneyParams ) = Intent(context, JourneysActivity::class.java).apply { putExtra(ORIGIN_PARAM, origin) putExtra(DESTINATION_PARAM, destination) @@ -60,31 +59,28 @@ private fun getOrigin(): Place = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(ORIGIN_PARAM, Place::class.java) } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(ORIGIN_PARAM) + @Suppress("DEPRECATION") intent.getParcelableExtra(ORIGIN_PARAM) } ?: throw Exception("Origin not given") private fun getDestination(): Place = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(DESTINATION_PARAM, Place::class.java) } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(DESTINATION_PARAM) + @Suppress("DEPRECATION") intent.getParcelableExtra(DESTINATION_PARAM) } ?: throw Exception("Destination not given") - private fun getJourneyParams(): JourneyParams = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra(PARAMS_PARAM, JourneyParams::class.java) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(PARAMS_PARAM) - } ?: throw Exception("Params not given") + private fun getJourneyParams(): JourneyParams = + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(PARAMS_PARAM, JourneyParams::class.java) + } else { + @Suppress("DEPRECATION") intent.getParcelableExtra(PARAMS_PARAM) + } ?: throw Exception("Params not given") override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) binding = ActivityJourneysBinding.inflate(layoutInflater) setContentView(binding.root) - val journeysViewModel = - ViewModelProvider(this)[JourneysViewModel::class.java] + val journeysViewModel = ViewModelProvider(this)[JourneysViewModel::class.java] // TODO check upside-down // TODO for horizontal make side sheet @@ -95,8 +91,8 @@ WindowInsetsCompat.CONSUMED } binding.map.setTileSource(TileSourceFactory.MAPNIK) - if (((resources?.configuration?.uiMode ?: UI_MODE_NIGHT_UNDEFINED) - and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES + 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) } @@ -113,63 +109,63 @@ val destination = getDestination() val params = getJourneyParams() journeysViewModel.journeys.observe(this) { - zoomMap(100) + zoomMap(dpToPixelI(100f)) binding.journeysProgress.visibility = View.GONE if (it.isEmpty()) { binding.emptyText.visibility = View.VISIBLE binding.emptyImage.visibility = View.VISIBLE } else { + showMarkers(origin.position(), destination.position()) + binding.journeys.visibility = View.VISIBLE - binding.journeys.adapter = JourneysAdapter(layoutInflater, this, it) { + binding.journeys.adapter = JourneysAdapter(layoutInflater, this, it) { journey, hide -> binding.map.overlays.removeAll { true } - // TODO show depending on open/close card - // TODO close other cards - - // TODO show without journey - val originMarker = Marker(binding.map).apply { - position = GeoPoint(it.legs[0].origin.latitude, it.legs[0].origin.longitude) - setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) - icon = AppCompatResources.getDrawable( - this@JourneysActivity, - R.drawable.pin // TODO R.drawable.legOrigin - ) // TODO colour - setOnMarkerClickListener { marker, map -> - true - } + if (hide) { + showMarkers(origin.position(), destination.position()) + binding.map.invalidate() + return@JourneysAdapter } - binding.map.overlays.add(originMarker) - // TODO show without journey - val destinationMarker = Marker(binding.map).apply { - position = GeoPoint(it.legs.last().destination.latitude, it.legs.last().destination.longitude) - setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) - icon = AppCompatResources.getDrawable( - this@JourneysActivity, - R.drawable.pin // TODO R.drawable.legDestination - ) // TODO colour - setOnMarkerClickListener { marker, map -> - true - } - } - binding.map.overlays.add(destinationMarker) + showMarkers(journey.legs[0].origin.position(), journey.legs[0].origin.position()) + showMarkers( + journey.legs.last().destination.position(), journey.legs.last().destination.position() + ) - it.legs.forEachIndexed { i, leg -> + journey.legs.forEachIndexed { i, leg -> val shapePoints = leg.shape.map { GeoPoint(it.latitude, it.longitude) } + val contrastShape = Polyline() + val contrastPaint = contrastShape.outlinePaint + contrastPaint.color = + Colour.getThemeColour(com.google.android.material.R.attr.colorOnBackground, this) + contrastPaint.strokeWidth = contrastPaint.strokeWidth * 1.5f + contrastShape.setPoints(shapePoints) + binding.map.overlays.add(contrastShape) + val shape = Polyline() val paint = shape.outlinePaint - paint.color = leg.start.vehicle.Line.colour.toInt() // TODO contrast + paint.color = leg.start.vehicle.Line.colour.toInt() if (leg.start.vehicle.Line.kind.isActive()) { paint.setPathEffect(DashPathEffect(floatArrayOf(10f, 10f), 0f)) + paint.color = Colour.getThemeColour( + com.google.android.material.R.attr.colorSurfaceContainer, this + ) } shape.setPoints(shapePoints) - shape.isVisible = true binding.map.overlays.add(shape) } binding.map.invalidate() - // todo move map accordingly + zoomMap( + dpToPixelI(100f), + BoundingBox( + max(journey.legs[0].origin.latitude, journey.legs.last().destination.latitude), + max(journey.legs[0].origin.longitude, journey.legs.last().destination.longitude), + min(journey.legs[0].origin.latitude, journey.legs.last().destination.latitude), + min(journey.legs[0].origin.longitude, journey.legs.last().destination.longitude), + ) + ) } } } @@ -184,13 +180,38 @@ fun zoomMap(margin: Int = 0, box: BoundingBox? = null) { val origin = getOrigin() val destination = getDestination() - // TODO offset bottom sheet val bb = box ?: BoundingBox( max(origin.latitude, destination.latitude), max(origin.longitude, destination.longitude), - min(origin.latitude, destination.latitude), + min(origin.latitude, destination.latitude), // TODO offset bottom sheet min(origin.longitude, destination.longitude), ) binding.map.zoomToBoundingBox(bb, false, margin) + } + + fun showMarkers(origin: Position, destination: Position) { + val originMarker = Marker(binding.map).apply { + position = GeoPoint(origin.latitude, origin.longitude) + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = AppCompatResources.getDrawable( + this@JourneysActivity, R.drawable.pin // TODO R.drawable.legOrigin + ) + setOnMarkerClickListener { marker, map -> + true + } + } + binding.map.overlays.add(originMarker) + + val destinationMarker = Marker(binding.map).apply { + position = GeoPoint(destination.latitude, destination.longitude) + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = AppCompatResources.getDrawable( + this@JourneysActivity, R.drawable.pin // TODO R.drawable.legDestination + ) + setOnMarkerClickListener { marker, map -> + true + } + } + binding.map.overlays.add(destinationMarker) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Colour.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Colour.kt index 5b33a56f8235911686e8e41586e7fcac87c39a40..24d3cdd91d221e201e1e3ce228981dfb7ca29983 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Colour.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Colour.kt @@ -4,8 +4,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.repo +import android.content.Context +import android.content.res.TypedArray import android.os.Parcelable import kotlinx.parcelize.Parcelize +import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.api.ColourV1 @Parcelize @@ -33,6 +36,15 @@ val b = it.substring(4 until 6).toUByte(16) Colour(r, g, b) } } + } + + fun getThemeColour(resource: Int, context: Context): Int { + val a: TypedArray = context.theme.obtainStyledAttributes( + R.style.Theme_Bimba, intArrayOf(resource) + ) + val intColor = a.getColor(0, 0) + a.recycle() + return intColor } } } \ No newline at end of file diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt index 116985190dd65e64ffc9a0f26c3d3652a5cfbe40..b12a8c50db61de66887a5322dbde71871070a92e 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Journey.kt @@ -45,6 +45,10 @@ ) constructor(stop: Stop) : this(stop, stop.position.latitude, stop.position.longitude) + fun position(): Position { + return Position(latitude, longitude) + } + fun planString(): String = if (stop.code == "") { "${latitude},${longitude},0" } else { diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/search/ui/LineGraphFragment.kt b/app/src/main/java/xyz/apiote/bimba/czwek/search/ui/LineGraphFragment.kt index ffb32d9486fbf7056c3629add6fdf2d58550898e..6cfe3be6d45ada1868d0abdd9b635815c77a2719 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/search/ui/LineGraphFragment.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/search/ui/LineGraphFragment.kt @@ -26,6 +26,7 @@ import dev.bandb.graphview.layouts.layered.SugiyamaLayoutManager import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.databinding.FragmentLineGraphBinding import xyz.apiote.bimba.czwek.departures.DeparturesActivity +import xyz.apiote.bimba.czwek.repo.Colour import xyz.apiote.bimba.czwek.repo.LineGraph import xyz.apiote.bimba.czwek.repo.StopStub import xyz.apiote.bimba.czwek.search.BimbaViewHolder @@ -57,7 +58,12 @@ _binding = FragmentLineGraphBinding.inflate(inflater, container, false) ViewCompat.setOnApplyWindowInsetsListener(binding.recycler) { v, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.updatePadding(right = insets.right, left = insets.left, top = insets.top, bottom = insets.bottom) + v.updatePadding( + right = insets.right, + left = insets.left, + top = insets.top, + bottom = insets.bottom + ) windowInsets } @@ -68,12 +74,9 @@ binding.recycler.layoutManager = SugiyamaLayoutManager(requireContext(), configuration) binding.recycler.addItemDecoration(SugiyamaArrowEdgeDecoration(Paint(Paint.ANTI_ALIAS_FLAG).apply { strokeWidth = 5f - val a: TypedArray? = context?.theme?.obtainStyledAttributes( - R.style.Theme_Bimba, intArrayOf(com.google.android.material.R.attr.colorOnBackground) - ) - val intColor = a?.getColor(0, 0) - a?.recycle() - color = intColor ?: 0 + color = context?.let { + Colour.getThemeColour(com.google.android.material.R.attr.colorOnBackground, it) + } ?: 0 style = Paint.Style.STROKE strokeJoin = Paint.Join.ROUND pathEffect = CornerPathEffect(10f) diff --git a/app/src/main/res/layout/activity_journeys.xml b/app/src/main/res/layout/activity_journeys.xml index c1ab83ad33ed4df004b224065382d003a5a0a836..b1124d955dc98e2b9d1bdcc66051f11bb3b59701 100644 --- a/app/src/main/res/layout/activity_journeys.xml +++ b/app/src/main/res/layout/activity_journeys.xml @@ -12,10 +12,9 @@ tools:context=".journeys.JourneysActivity"> <org.osmdroid.views.MapView android:id="@+id/map" + android:layout_marginBottom="200dp" android:layout_width="match_parent" - android:layout_height="match_parent"> - - </org.osmdroid.views.MapView> + android:layout_height="match_parent" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/journeys_bottom_sheet" @@ -57,28 +56,28 @@android:id="@+id/empty_image" android:layout_width="92dp" android:layout_height="92dp" + android:layout_marginTop="36dp" + android:src="@drawable/error_journeys" android:visibility="gone" - android:layout_marginTop="36dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/drag_handle" - tools:ignore="ContentDescription" - android:src="@drawable/error_journeys" /> + tools:ignore="ContentDescription" /> <com.google.android.material.textview.MaterialTextView android:id="@+id/empty_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:visibility="gone" android:layout_marginTop="8dp" android:layout_marginEnd="16dp" + android:text="@string/no_journeys_found" android:textAlignment="center" android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/empty_image" - android:text="@string/no_journeys_found" /> + app:layout_constraintTop_toBottomOf="@+id/empty_image" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/build.gradle.kts b/build.gradle.kts index c746a3ec056432afbc9711d8b48250689c0d939c..0e6fb37fe7f621022fb39098802726a647d6f19d 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.3" apply false - id("com.android.library") version "8.7.3" apply false + id("com.android.application") version "8.8.0" apply false + id("com.android.library") version "8.8.0" 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dfec7cc51514aaa2b56c69a2e03179fbb0370502..46c68e716d3fedc11efcf72d7e37efef71a31362 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,7 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later #Tue Aug 09 15:48:25 CEST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME