Bimba.git

commit 3de3f8a9ce69cd33028a85a70c74ee1c6e501403

Author: Adam Evyčędo <git@apiote.xyz>

arrows and distance in results

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeFragment.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeFragment.kt
index e22468daa531581c6de54bf84b14ccc24d153874..a08bf4c2729a751497adeee3b29c5ec6c660c12d 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeFragment.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeFragment.kt
@@ -61,7 +61,7 @@ 			ViewModelProvider(this)[HomeViewModel::class.java]
 		viewModel.queryables.observe(viewLifecycleOwner) {
 			adapter.feedsSettings = viewModel.feedsSettings
 			adapter.feeds = viewModel.feeds
-			adapter.update(it)
+			adapter.update(it, null, false)
 		}
 		viewModel.favourites.observe(viewLifecycleOwner) {
 			favouritesAdapter.updateFavourites(it)
@@ -89,7 +89,7 @@ 				TransitionState.HIDDEN -> false
 				else -> false
 			}
 			if (newState === TransitionState.HIDING) {
-				adapter.update(listOf())
+				adapter.update(listOf(), null, false)
 			}
 		}
 
@@ -97,7 +97,7 @@ 		binding.searchBar.setNavigationOnClickListener {
 			(context as MainActivity).onNavigationClicked()
 		}
 		binding.suggestionsRecycler.layoutManager = LinearLayoutManager(activity)
-		adapter = BimbaResultsAdapter(layoutInflater, activity, listOf())
+		adapter = BimbaResultsAdapter(layoutInflater, activity, listOf(), null, false)
 		binding.suggestionsRecycler.adapter = adapter
 
 		binding.searchView.editText.addTextChangedListener(




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/search/Results.kt b/app/src/main/java/xyz/apiote/bimba/czwek/search/Results.kt
index 5575f8547ed6f8abfeacabc82767927c814a74ac..4c94db9e8d6f4bed71759e0240ae038ab93301bd 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/search/Results.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/search/Results.kt
@@ -6,6 +6,8 @@ package xyz.apiote.bimba.czwek.search
 
 import android.content.Context
 import android.content.Intent
+import android.graphics.Matrix
+import android.location.Location
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -28,6 +30,8 @@ 	val icon: ImageView = itemView.findViewById(R.id.suggestion_image)
 	val title: TextView = itemView.findViewById(R.id.suggestion_title)
 	val description: TextView = itemView.findViewById(R.id.suggestion_description)
 	val feedName: TextView = itemView.findViewById(R.id.feed_name)
+	val distance: TextView = itemView.findViewById(R.id.distance)
+	val arrow: ImageView = itemView.findViewById(R.id.arrow)
 
 	companion object {
 		fun bind(
@@ -36,10 +40,12 @@ 			holder: BimbaViewHolder?,
 			context: Context?,
 			feeds: Map<String, FeedInfo>?,
 			feedsSettings: FeedsSettings?,
-			onClickListener: (Queryable) -> Unit
+			onClickListener: (Queryable) -> Unit,
+			position: Location?,
+			showArrow: Boolean
 		) {
 			when (queryable) {
-				is Stop -> bindStop(queryable, holder, context, feeds, feedsSettings)
+				is Stop -> bindStop(queryable, holder, context, feeds, feedsSettings, position, showArrow)
 				is Line -> bindLine(queryable, holder, context, feeds, feedsSettings)
 			}
 			holder?.root?.setOnClickListener {
@@ -82,8 +88,37 @@ 			stop: Stop,
 			holder: BimbaViewHolder?,
 			context: Context?,
 			feeds: Map<String, FeedInfo>?,
-			feedsSettings: FeedsSettings?
+			feedsSettings: FeedsSettings?,
+			position: Location?,
+			showArrow: Boolean
 		) {
+
+			if (showArrow && position != null && position.hasBearing()) {
+				Location(null).apply {
+					latitude = stop.position.latitude
+					longitude = stop.position.longitude
+				}.let {
+					val rotation =
+						(360 + ((position.bearingTo(it) + 360).mod(360f)) - position.bearing).mod(360f)
+					val distance = position.distanceTo(it)
+					holder?.arrow?.apply {
+						setImageResource(R.drawable.arrow)
+						scaleType = ImageView.ScaleType.MATRIX
+						val matrix = Matrix()
+						matrix.postRotate(rotation, width.toFloat() / 2, height.toFloat() / 2)
+						imageMatrix = matrix
+						visibility = View.VISIBLE
+					}
+					holder?.distance?.apply {
+						text = "$distance m" // TODO units
+						visibility = View.VISIBLE
+					}
+				}
+			} else {
+				holder?.arrow?.visibility = View.GONE
+				holder?.distance?.visibility = View.GONE
+			}
+
 			holder?.icon?.apply {
 				setImageDrawable(stop.icon(context!!))
 				contentDescription = context.getString(R.string.stop_content_description)
@@ -149,11 +184,17 @@ class BimbaResultsAdapter(
 	private val inflater: LayoutInflater,
 	private val context: Context?,
 	private var queryables: List<Queryable>,
+	private var position: Location?,
+	private var showArrow: Boolean
 ) :
 	RecyclerView.Adapter<BimbaViewHolder>() {
 	class DiffUtilCallback(
 		private val oldQueryables: List<Queryable>,
-		private val newQueryables: List<Queryable>
+		private val newQueryables: List<Queryable>,
+		private val oldPosition: Location?,
+		private val newPosition: Location?,
+		private val oldShowArrow: Boolean,
+		private val newShowArrow: Boolean
 	) : DiffUtil.Callback() {
 		override fun getOldListSize() = oldQueryables.size
 
@@ -202,7 +243,7 @@ 					val oldChangeOptions =
 						oldQueryable.changeOptions.joinToString { "${it.line}->${it.headsign}" }
 					val newChangeOptions =
 						(newQueryable as Stop).changeOptions.joinToString { "${it.line}->${it.headsign}" }
-					oldQueryable.name == newQueryable.name && oldChangeOptions == newChangeOptions
+					oldQueryable.name == newQueryable.name && oldChangeOptions == newChangeOptions && oldPosition == newPosition && oldShowArrow == newShowArrow
 				}
 
 				else -> false // XXX unreachable
@@ -247,15 +288,28 @@ 			holder,
 			context,
 			feeds,
 			feedsSettings,
-			onClickListener
+			onClickListener,
+			this.position,
+			showArrow
 		)
 	}
 
 	override fun getItemCount(): Int = queryables.size
 
-	fun update(queryables: List<Queryable>?) {
+	fun update(queryables: List<Queryable>?, position: Location?, showArrow: Boolean) {
 		val newQueryables = queryables ?: emptyList()
-		val diff = DiffUtil.calculateDiff(DiffUtilCallback(this.queryables, newQueryables))
+		val diff = DiffUtil.calculateDiff(
+			DiffUtilCallback(
+				this.queryables,
+				newQueryables,
+				this.position,
+				position,
+				this.showArrow,
+				showArrow
+			)
+		)
+		this.position = position
+		this.showArrow = showArrow
 		this.queryables = newQueryables
 		diff.dispatchUpdatesTo(this)
 	}




diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
index f8729039653a32172c5b25a51668ff9ddfb15a6e..066d95276405023a5d7f868e9c92f4c49c6c6886 100644
--- a/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
+++ b/app/src/main/java/xyz/apiote/bimba/czwek/search/ResultsActivity.kt
@@ -73,7 +73,7 @@ 			windowInsets
 		}
 
 		binding.resultsRecycler.layoutManager = LinearLayoutManager(this)
-		adapter = BimbaResultsAdapter(layoutInflater, this, listOf())
+		adapter = BimbaResultsAdapter(layoutInflater, this, listOf(), null, false)
 		binding.resultsRecycler.adapter = adapter
 
 		when (getMode()) {
@@ -87,7 +87,10 @@ 				val query = intent.extras?.getString("query")
 				val lat = intent.extras?.getDouble("lat")
 				val lon = intent.extras?.getDouble("lon")
 				supportActionBar?.title = getString(R.string.stops_near_code, query)
-				getQueryablesByLocation(Position(lat!!, lon!!), this)
+				getQueryablesByLocation(Location(null).apply {
+					latitude = lat!!
+					longitude = lon!!
+				}, this)
 			}
 
 			Mode.MODE_SEARCH -> {
@@ -127,7 +130,7 @@ 	}
 
 	override fun onLocationChanged(location: Location) {
 		handler.removeCallbacks(runnable)
-		getQueryablesByLocation(Position(location.latitude, location.longitude), this)
+		getQueryablesByLocation(location, this, true)
 	}
 
 	override fun onResume() {
@@ -168,7 +171,7 @@ 			try {
 				val repository = OnlineRepository()
 				val result = repository.queryQueryables(query, context)
 				getFeeds()
-				updateItems(result)
+				updateItems(result, null, false)
 			} catch (e: TrafficResponseException) {
 				Log.w("Suggestion", "$e")
 				showError(e.error)
@@ -176,13 +179,17 @@ 			}
 		}
 	}
 
-	private fun getQueryablesByLocation(position: Position, context: Context) {
+	private fun getQueryablesByLocation(
+		position: Location,
+		context: Context,
+		showArrow: Boolean = false
+	) {
 		MainScope().launch {
 			try {
 				val repository = OnlineRepository()
-				val result = repository.locateQueryables(position, context)
+				val result = repository.locateQueryables(Position(position.latitude, position.longitude), context)
 				getFeeds()
-				updateItems(result)
+				updateItems(result, position, showArrow)
 			} catch (e: TrafficResponseException) {
 				Log.w("Suggestion", "$e")
 				showError(e.error)
@@ -200,9 +207,9 @@ 		binding.errorText.text = getString(error.stringResource)
 		binding.errorImage.setImageDrawable(AppCompatResources.getDrawable(this, error.imageResource))
 	}
 
-	private fun updateItems(queryables: List<Queryable>?) {
+	private fun updateItems(queryables: List<Queryable>?, position: Location?, showArrow: Boolean) {
 		binding.resultsProgress.visibility = View.GONE
-		adapter.update(queryables)
+		adapter.update(queryables, position, showArrow)
 		if (queryables.isNullOrEmpty()) {
 			binding.errorImage.visibility = View.VISIBLE
 			binding.errorText.visibility = View.VISIBLE




diff --git a/app/src/main/res/drawable/arrow.xml b/app/src/main/res/drawable/arrow.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d985176695cd4a54bd596b14ad6b3595028fdff6
--- /dev/null
+++ b/app/src/main/res/drawable/arrow.xml
@@ -0,0 +1,15 @@
+<!--
+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:tint="?attr/colorOnSurface"
+	android:viewportWidth="960"
+	android:viewportHeight="960">
+	<path
+		android:fillColor="@android:color/white"
+		android:pathData="M440,880L440,233L256,416L200,360L480,80L760,360L704,417L520,233L520,880L440,880Z" />
+</vector>
\ No newline at end of file




diff --git a/app/src/main/res/layout/result.xml b/app/src/main/res/layout/result.xml
index ca09e81118eadcdf7ab34fed534d7e84c4169680..6d71f2fea7c9a4c4742a87913b9f1ea7aef6cb3d 100644
--- a/app/src/main/res/layout/result.xml
+++ b/app/src/main/res/layout/result.xml
@@ -24,6 +24,30 @@ 		app:layout_constraintTop_toTopOf="@+id/suggestion_title"
 		tool:ignore="ContentDescription"
 		tool:src="@drawable/vehicle_black" />
 
+	<ImageView
+		android:id="@+id/arrow"
+		android:layout_width="24dp"
+		android:layout_height="24dp"
+		android:importantForAccessibility="no"
+		app:layout_constraintEnd_toEndOf="parent"
+		app:layout_constraintTop_toTopOf="parent"
+		android:layout_marginEnd="8dp"
+		android:layout_marginTop="8dp"
+		android:visibility="gone"
+		tool:src="@drawable/arrow" />
+
+	<com.google.android.material.textview.MaterialTextView
+		android:id="@+id/distance"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_marginEnd="4dp"
+		android:visibility="gone"
+		app:layout_constraintBottom_toBottomOf="@+id/arrow"
+		app:layout_constraintEnd_toStartOf="@+id/arrow"
+		app:layout_constraintTop_toTopOf="@+id/arrow"
+		android:textAppearance="@style/TextAppearance.Material3.BodySmall"
+		tool:text="650m" />
+
 	<!-- todo maxWidth or separate layout for graphView -->
 	<com.google.android.material.textview.MaterialTextView
 		android:id="@+id/suggestion_title"