Author: Adam Evyčędo <git@apiote.xyz>
show feed name in search 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 fb91f012e086d05335c724054932e4adeb84d1bc..937651e34f7d64ab76dc0caeecf9ae19f23374ce 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 @@ -4,8 +4,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.dashboard.ui.home -import android.content.Context -import android.net.ConnectivityManager import android.os.Bundle import android.view.KeyEvent import android.view.LayoutInflater @@ -40,6 +38,8 @@ val homeViewModel = ViewModelProvider(this)[HomeViewModel::class.java] homeViewModel.queryables.observe(viewLifecycleOwner) { + adapter.feedsSettings = homeViewModel.feedsSettings + adapter.feeds = homeViewModel.feeds adapter.update(it) } @@ -74,12 +74,8 @@ binding.suggestionsRecycler.layoutManager = LinearLayoutManager(activity) adapter = BimbaResultsAdapter(layoutInflater, activity, listOf()) binding.suggestionsRecycler.adapter = adapter - val cm = requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager binding.searchView.editText.addTextChangedListener( - homeViewModel.SearchBarWatcher( - requireContext(), - cm - ) + homeViewModel.SearchBarWatcher(requireContext()) ) binding.searchView.editText.setOnKeyListener { v, keyCode, event -> when (keyCode) { @@ -107,4 +103,4 @@ override fun onDestroyView() { super.onDestroyView() _binding = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt index bfe94f905401ecfea122365da7f6c07f5c47d910..38afc1078fe260f2e80a6553869cc720b446b48e 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/dashboard/ui/home/HomeViewModel.kt @@ -16,19 +16,24 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch +import xyz.apiote.bimba.czwek.repo.FeedInfo +import xyz.apiote.bimba.czwek.repo.OfflineRepository import xyz.apiote.bimba.czwek.repo.OnlineRepository import xyz.apiote.bimba.czwek.repo.Queryable import xyz.apiote.bimba.czwek.repo.TrafficResponseException +import xyz.apiote.bimba.czwek.settings.feeds.FeedsSettings class HomeViewModel : ViewModel() { private val mutableQueryables = MutableLiveData<List<Queryable>>() val queryables: LiveData<List<Queryable>> = mutableQueryables + var feeds: Map<String, FeedInfo>? = null + var feedsSettings: FeedsSettings? = null - fun getQueryables(cm: ConnectivityManager, query: String, context: Context) { + fun getQueryables(query: String, context: Context) { viewModelScope.launch { try { - val repository = OnlineRepository() - mutableQueryables.value = repository.queryQueryables(cm, query, context) ?: emptyList() + getFeeds(context) + mutableQueryables.value = OnlineRepository().queryQueryables(query, context) ?: emptyList() } catch (e: TrafficResponseException) { // xxx intentionally no error showing in suggestions Log.e("Suggestion", "$e") @@ -36,9 +41,13 @@ } } } - inner class SearchBarWatcher( - private val context: Context, private val cm: ConnectivityManager - ) : TextWatcher { + private suspend fun getFeeds(context: Context) { + feeds = OfflineRepository().getFeeds(context) + feedsSettings = FeedsSettings.load(context) + } + + + inner class SearchBarWatcher(private val context: Context) : TextWatcher { private val handler = Handler(Looper.getMainLooper()) private var workRunnable = Runnable {} @@ -52,9 +61,9 @@ override fun afterTextChanged(s: Editable?) { handler.removeCallbacks(workRunnable) workRunnable = Runnable { val text = s.toString() - getQueryables(cm, text, context) + getQueryables(text, context) } handler.postDelayed(workRunnable, 750) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt index 4905c0f55b5ce6589d62fbaefd313b170a9fc639..dc8bdba932bf7327316bd331ae92791002adce35 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/Interfaces.kt @@ -18,8 +18,8 @@ } interface Repository { suspend fun getFeeds( - server: Server, - context: Context + context: Context, + server: Server = Server.get(context) ): Map<String, FeedInfo>? suspend fun getDepartures( @@ -45,13 +45,11 @@ context: Context ): Line? suspend fun queryQueryables( - cm: ConnectivityManager, query: String, context: Context ): List<Queryable>? suspend fun locateQueryables( - cm: ConnectivityManager, position: Position, context: Context ): List<Queryable>? diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt index 9943189f8702bf06fd84fd25a1d919b8f3f181db..0a92c37cd563b5560cd44f559b7ff8ce787d82bb 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OfflineRepository.kt @@ -19,11 +19,11 @@ import java.net.URLEncoder class OfflineRepository : Repository { override suspend fun getFeeds( - server: Server, - context: Context + context: Context, + server: Server ): Map<String, FeedInfo>? { val file = File( - context.cacheDir, URLEncoder.encode(server.apiPath, "utf-8") + context.filesDir, URLEncoder.encode(server.apiPath, "utf-8") ) if (!file.exists()) { return emptyMap() @@ -84,7 +84,6 @@ TODO("Not yet implemented") } override suspend fun queryQueryables( - cm: ConnectivityManager, query: String, context: Context ): List<Queryable>? { @@ -92,7 +91,6 @@ TODO("Not yet implemented") } override suspend fun locateQueryables( - cm: ConnectivityManager, position: Position, context: Context ): List<Queryable>? { diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt index a84e433fe5cb2fc1ca004ebc8b5852c1a9216fd2..e25943d37a5bb1d941a759ad6cd56a29f23b20ba 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/repo/OnlineRepository.kt @@ -50,14 +50,14 @@ class OnlineRepository : Repository { private fun saveFeedCache(server: Server, context: Context, rawResponse: ByteArray) { val file = File( - context.cacheDir, URLEncoder.encode(server.apiPath, "utf-8") + context.filesDir, URLEncoder.encode(server.apiPath, "utf-8") ) file.writeBytes(rawResponse) } override suspend fun getFeeds( - server: Server, - context: Context + context: Context, + server: Server ): Map<String, FeedInfo>? { val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val result = @@ -210,20 +210,21 @@ } } override suspend fun queryQueryables( - cm: ConnectivityManager, query: String, context: Context + query: String, context: Context ): List<Queryable>? { - return getQueryables(cm, query, null, context, "query") + return getQueryables(query, null, context, "query") } override suspend fun locateQueryables( - cm: ConnectivityManager, position: Position, context: Context + position: Position, context: Context ): List<Queryable>? { - return getQueryables(cm, null, position, context, "locate") + return getQueryables(null, position, context, "locate") } private suspend fun getQueryables( - cm: ConnectivityManager, query: String?, position: Position?, context: Context, type: String + query: String?, position: Position?, context: Context, type: String ): List<Queryable>? { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val result = when (type) { "query" -> { xyz.apiote.bimba.czwek.api.queryQueryables(cm, Server.get(context), query!!, limit = 12) 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 c59ee9c8f563016082f1d8fa69b1f379cd2e8e30..ef4757d062459420ae3714d74bce0c0592bcb441 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 @@ -15,27 +15,32 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.departures.DeparturesActivity +import xyz.apiote.bimba.czwek.repo.FeedInfo import xyz.apiote.bimba.czwek.repo.Line import xyz.apiote.bimba.czwek.repo.Queryable import xyz.apiote.bimba.czwek.repo.Stop import xyz.apiote.bimba.czwek.repo.StopStub +import xyz.apiote.bimba.czwek.settings.feeds.FeedsSettings class BimbaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val root: View = itemView.findViewById(R.id.suggestion) 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) companion object { fun bind( queryable: Queryable, holder: BimbaViewHolder?, context: Context?, + feeds: Map<String, FeedInfo>?, + feedsSettings: FeedsSettings?, onClickListener: (Queryable) -> Unit ) { when (queryable) { - is Stop -> bindStop(queryable, holder, context) - is Line -> bindLine(queryable, holder, context) + is Stop -> bindStop(queryable, holder, context, feeds, feedsSettings) + is Line -> bindLine(queryable, holder, context, feeds, feedsSettings) } holder?.root?.setOnClickListener { onClickListener(queryable) @@ -72,12 +77,22 @@ onClickListener(stopStub) } } - private fun bindStop(stop: Stop, holder: BimbaViewHolder?, context: Context?) { + private fun bindStop( + stop: Stop, + holder: BimbaViewHolder?, + context: Context?, + feeds: Map<String, FeedInfo>?, + feedsSettings: FeedsSettings? + ) { holder?.icon?.apply { setImageDrawable(stop.icon(context!!)) contentDescription = context.getString(R.string.stop_content_description) } holder?.title?.text = stop.name + if ((feedsSettings?.activeFeeds() ?: 0) > 1) { + holder?.feedName?.visibility = View.VISIBLE + holder?.feedName?.text = feeds?.get(stop.feedID)?.name ?: "" + } context?.let { stop.changeOptions(it).let { changeOptions -> holder?.description?.apply { @@ -88,11 +103,21 @@ } } } - private fun bindLine(line: Line, holder: BimbaViewHolder?, context: Context?) { + private fun bindLine( + line: Line, + holder: BimbaViewHolder?, + context: Context?, + feeds: Map<String, FeedInfo>?, + feedsSettings: FeedsSettings? + ) { holder?.icon?.apply { setImageDrawable(line.icon(context!!)) contentDescription = line.type.name colorFilter = null + } + if ((feedsSettings?.activeFeeds() ?: 0) > 1) { + holder?.feedName?.visibility = View.VISIBLE + holder?.feedName?.text = feeds?.get(line.feedID)?.name ?: "" } holder?.title?.text = line.name holder?.description?.text = if (line.headsigns.size == 1) { @@ -186,6 +211,8 @@ } } + var feeds: Map<String, FeedInfo>? = null + var feedsSettings: FeedsSettings? = null private val onClickListener: ((Queryable) -> Unit) = { when (it) { is Stop -> { @@ -213,7 +240,14 @@ return BimbaViewHolder(rowView) } override fun onBindViewHolder(holder: BimbaViewHolder, position: Int) { - BimbaViewHolder.bind(queryables[position], holder, context, onClickListener) + BimbaViewHolder.bind( + queryables[position], + holder, + context, + feeds, + feedsSettings, + onClickListener + ) } override fun getItemCount(): Int = queryables.size 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 2aded60740f242cfb8b7ac4283a21791bf8abc9e..6243e61e18928bc6551514c3f3195c511222738c 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 @@ -25,10 +25,12 @@ import kotlinx.coroutines.launch import xyz.apiote.bimba.czwek.R import xyz.apiote.bimba.czwek.api.Error import xyz.apiote.bimba.czwek.databinding.ActivityResultsBinding +import xyz.apiote.bimba.czwek.repo.OfflineRepository import xyz.apiote.bimba.czwek.repo.OnlineRepository import xyz.apiote.bimba.czwek.repo.Position import xyz.apiote.bimba.czwek.repo.Queryable import xyz.apiote.bimba.czwek.repo.TrafficResponseException +import xyz.apiote.bimba.czwek.settings.feeds.FeedsSettings class ResultsActivity : AppCompatActivity(), LocationListener { enum class Mode { @@ -79,7 +81,7 @@ } } } - private fun getMode():Mode { + private fun getMode(): Mode { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.extras!!.getSerializable("mode", Mode::class.java)!! } else { @@ -132,12 +134,21 @@ locationManager.removeUpdates(this) handler.removeCallbacks(runnable) } + private suspend fun getFeeds() { + if (adapter.feeds.isNullOrEmpty()) { + adapter.feeds = OfflineRepository().getFeeds(this) + } + if (adapter.feedsSettings == null) { + adapter.feedsSettings = FeedsSettings.load(this) + } + } + private fun getQueryablesByQuery(query: String, context: Context) { - val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager MainScope().launch { try { val repository = OnlineRepository() - val result = repository.queryQueryables(cm, query, context) + val result = repository.queryQueryables(query, context) + getFeeds() updateItems(result) } catch (e: TrafficResponseException) { Log.w("Suggestion", "$e") @@ -147,11 +158,11 @@ } } private fun getQueryablesByLocation(position: Position, context: Context) { - val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager MainScope().launch { try { val repository = OnlineRepository() - val result = repository.locateQueryables(cm, position, context) + val result = repository.locateQueryables(position, context) + getFeeds() updateItems(result) } catch (e: TrafficResponseException) { Log.w("Suggestion", "$e") @@ -195,4 +206,4 @@ binding.errorText.visibility = View.GONE binding.resultsRecycler.visibility = View.VISIBLE } } -} \ No newline at end of file +} diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt index aaabcdc6a63e0a4d6da769274b660fd5db0611b0..13fd5163f5b2f5f7313d89cea0289946670e810c 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedChooserActivity.kt @@ -93,7 +93,7 @@ MainScope().launch { val offlineRepository = OfflineRepository() val offlineFeeds = - offlineRepository.getFeeds(Server.get(this@FeedChooserActivity), this@FeedChooserActivity) + offlineRepository.getFeeds(this@FeedChooserActivity) if (!offlineFeeds.isNullOrEmpty()) { feeds.putAll(offlineFeeds) updateItems(offlineFeeds.map { it.value }, null) @@ -101,7 +101,7 @@ } try { val repository = OnlineRepository() val onlineFeeds = - repository.getFeeds(Server.get(this@FeedChooserActivity), this@FeedChooserActivity) + repository.getFeeds(this@FeedChooserActivity) feeds.clear() joinFeeds(offlineFeeds, onlineFeeds).let { joinedFeeds -> feeds.putAll(joinedFeeds) diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt index 9412dd8b17cd21c2ad90fd6448ec7acbbe4dbec9..91921e1025e74c19ad73d9d75cd4e39d5668bfec 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedSettings.kt @@ -15,7 +15,8 @@ @Serializable @OptIn(ExperimentalStdlibApi::class) data class FeedsSettings(val settings: MutableMap<String, FeedSettings>) { - fun getIDs() = settings.filter { it.value.enabled }.keys.joinToString(",") + fun activeFeeds() = settings.count { it.value.enabled && it.value.useOnline } + fun getIDs() = settings.filter { it.value.enabled && it.value.useOnline }.keys.joinToString(",") fun save(context: Context, server: Server) { val doc = KBson().dump(serializer(), this).toHexString() @@ -28,7 +29,7 @@ } } companion object { - fun load(context: Context, apiPath: String): FeedsSettings { + fun load(context: Context, apiPath: String = Server.get(context).apiPath): FeedsSettings { val doc = context.getSharedPreferences( "feeds_settings", Context.MODE_PRIVATE diff --git a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedsViewModel.kt b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedsViewModel.kt index 22faaa10edbd16162766777f7d60ae45c7713ed5..edcd9f0503d5f81973bab770d443f5717dbe6255 100644 --- a/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedsViewModel.kt +++ b/app/src/main/java/xyz/apiote/bimba/czwek/settings/feeds/FeedsViewModel.kt @@ -5,14 +5,13 @@ import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import xyz.apiote.bimba.czwek.api.Server class FeedsViewModel : ViewModel() { private val _settings = MutableLiveData<FeedsSettings>() val settings: LiveData<FeedsSettings> = _settings fun loadSettings(context: Context) { - _settings.value = FeedsSettings.load(context, Server.get(context).apiPath) + _settings.value = FeedsSettings.load(context) } fun setSettings(feedID: String, feedSettings: FeedSettings) { diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index a34a6f00d8c1c13f15c1498c63de29195cd4c75a..eb621f767365a76f5fa26f83738325eaefa828dc 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -25,14 +25,12 @@ android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" - android:contentDescription="@string/search_placeholder" android:hint="@string/search_placeholder" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:navigationIcon="@drawable/menu" - app:useDrawerArrowDrawable="true" - tool:ignore="ContentDescription" /> + app:useDrawerArrowDrawable="true" /> </com.google.android.material.appbar.AppBarLayout> <com.google.android.material.search.SearchView @@ -67,7 +65,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tool:ignore="ContentDescription" /> + tool:ignore="ContentDescription,ImageContrastCheck" /> </androidx.constraintlayout.widget.ConstraintLayout> @@ -80,5 +78,6 @@ android:layout_margin="16dp" android:contentDescription="@string/home_fab_description" android:src="@drawable/gps_black" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + app:layout_constraintEnd_toEndOf="parent" + tool:ignore="ImageContrastCheck" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> \ 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 75e4256123ce4cfe818cf5cd9be5558a3ab0ccff..ca09e81118eadcdf7ab34fed534d7e84c4169680 100644 --- a/app/src/main/res/layout/result.xml +++ b/app/src/main/res/layout/result.xml @@ -18,13 +18,11 @@ android:id="@+id/suggestion_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:layout_marginTop="8dp" - android:layout_marginBottom="8dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@+id/suggestion_title" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tool:src="@drawable/vehicle_black" - tool:ignore="ContentDescription" /> + app:layout_constraintTop_toTopOf="@+id/suggestion_title" + tool:ignore="ContentDescription" + tool:src="@drawable/vehicle_black" /> <!-- todo maxWidth or separate layout for graphView --> <com.google.android.material.textview.MaterialTextView @@ -40,14 +38,25 @@ app:layout_constraintTop_toTopOf="parent" tool:text="Tower Hill" /> <com.google.android.material.textview.MaterialTextView + android:id="@+id/feed_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:textAppearance="@style/TextAppearance.Material3.LabelSmall" + app:layout_constraintStart_toStartOf="@+id/suggestion_title" + app:layout_constraintTop_toBottomOf="@+id/suggestion_title" + tool:text="TfL London" /> + + <com.google.android.material.textview.MaterialTextView android:id="@+id/suggestion_description" style="@style/Theme.Bimba.SearchResult.Description" android:layout_width="wrap_content" - android:maxWidth="360dp" android:layout_height="wrap_content" + android:maxWidth="360dp" + android:layout_marginTop="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@+id/suggestion_title" - app:layout_constraintTop_toBottomOf="@+id/suggestion_title" + app:layout_constraintTop_toBottomOf="@+id/feed_name" tool:text="Metropolitan » Baker Street, Tower Hill The Monument, Westminster, Piccadilly Circus, Oxford Street" /> </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file