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"