From eab260a9c10fb3c3345798bccf82c40cedd16c8e Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 12 Jan 2021 16:15:46 -0700 Subject: [PATCH] Add filtering to SearchFragment Add the ability to filter items to SearchFragment --- .../org/oxycblt/auxio/recycler/DisplayMode.kt | 32 +++++++- .../oxycblt/auxio/search/SearchFragment.kt | 24 +++++- .../oxycblt/auxio/search/SearchViewModel.kt | 73 ++++++++++++++++--- .../oxycblt/auxio/settings/SettingsManager.kt | 18 ++++- app/src/main/res/values/strings.xml | 3 +- 5 files changed, 128 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/DisplayMode.kt b/app/src/main/java/org/oxycblt/auxio/recycler/DisplayMode.kt index e766351bf..d8e6a1ba2 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/DisplayMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/DisplayMode.kt @@ -1,6 +1,7 @@ package org.oxycblt.auxio.recycler import androidx.annotation.DrawableRes +import androidx.annotation.IdRes import org.oxycblt.auxio.R /** @@ -14,19 +15,44 @@ enum class DisplayMode(@DrawableRes val iconRes: Int) { SHOW_ALBUMS(R.drawable.ic_album), SHOW_SONGS(R.drawable.ic_song); + fun isAllOr(value: DisplayMode) = this == SHOW_ALL || this == value + + @IdRes + fun toId(): Int { + return when (this) { + SHOW_ALL -> R.id.option_filter_all + SHOW_GENRES -> R.id.option_filter_genres + SHOW_ARTISTS -> R.id.option_filter_artists + SHOW_ALBUMS -> R.id.option_filter_albums + SHOW_SONGS -> R.id.option_filter_songs + } + } + companion object { /** * A valueOf wrapper that will return a default value if given a null/invalid string. */ - fun valueOfOrFallback(value: String?): DisplayMode { + fun valueOfOrFallback(value: String?, fallback: DisplayMode = SHOW_ARTISTS): DisplayMode { if (value == null) { - return SHOW_ARTISTS + return fallback } return try { valueOf(value) } catch (e: IllegalArgumentException) { - SHOW_ARTISTS + fallback + } + } + + fun fromId(@IdRes id: Int): DisplayMode { + return when (id) { + R.id.option_filter_all -> SHOW_ALL + R.id.option_filter_songs -> SHOW_SONGS + R.id.option_filter_albums -> SHOW_ALBUMS + R.id.option_filter_artists -> SHOW_ARTISTS + R.id.option_filter_genres -> SHOW_GENRES + + else -> SHOW_ALL } } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index f9be11cf2..d466aae8d 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -31,9 +31,12 @@ import org.oxycblt.auxio.ui.isLandscape import org.oxycblt.auxio.ui.requireCompatActivity import org.oxycblt.auxio.ui.toColor -// TODO: Fix TextView memory leak -// TODO: Add Filtering -// TODO: Add "No Results" marker +/** + * A [Fragment] that allows for the searching of the entire music library. + * TODO: Add "Recent Searches" & No Results indicator + * TODO: Filtering + * @author OxygenCobalt + */ class SearchFragment : Fragment() { // SearchViewModel only scoped to this Fragment private val searchModel: SearchViewModel by viewModels() @@ -56,6 +59,19 @@ class SearchFragment : Fragment() { // --- UI SETUP -- + binding.searchToolbar.apply { + menu.findItem(searchModel.filterMode.toId()).isChecked = true + + setOnMenuItemClickListener { + if (it.itemId != R.id.submenu_filtering) { + it.isChecked = true + searchModel.updateFilterModeWithId(it.itemId, requireContext()) + + true + } else false + } + } + binding.searchTextLayout.apply { boxStrokeColor = accent hintTextColor = ColorStateList.valueOf(accent) @@ -113,7 +129,7 @@ class SearchFragment : Fragment() { invoke(this@SearchFragment, null) } } catch (e: Exception) { - logE("Hacky reflection leak fix failed. Oh well.") + logE("Hacky reflection leak fix failed.") e.printStackTrace() } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 867d6b059..0fdafc426 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -1,26 +1,49 @@ package org.oxycblt.auxio.search import android.content.Context +import androidx.annotation.IdRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch import org.oxycblt.auxio.R +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.MusicStore +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.recycler.DisplayMode +import org.oxycblt.auxio.settings.SettingsManager +/** + * The [ViewModel] for the search functionality + * @author OxygenCobalt + */ class SearchViewModel : ViewModel() { private val mSearchResults = MutableLiveData(listOf()) val searchResults: LiveData> get() = mSearchResults + private var mFilterMode = DisplayMode.SHOW_ALL + val filterMode: DisplayMode get() = mFilterMode + + private var mLastQuery = "" + private var mIsNavigating = false val isNavigating: Boolean get() = mIsNavigating private val musicStore = MusicStore.getInstance() + private val settingsManager = SettingsManager.getInstance() + + init { + mFilterMode = settingsManager.searchFilterMode + } fun doSearch(query: String, context: Context) { + mLastQuery = query + if (query.isEmpty()) { mSearchResults.value = listOf() @@ -30,36 +53,62 @@ class SearchViewModel : ViewModel() { viewModelScope.launch { val results = mutableListOf() - musicStore.artists.filterByOrNull(query)?.let { - results.add(Header(id = -1, name = context.getString(R.string.label_artists))) - results.addAll(it) + if (mFilterMode.isAllOr(DisplayMode.SHOW_ARTISTS)) { + musicStore.artists.filterByOrNull(query)?.let { + results.add(Header(id = -1, name = context.getString(R.string.label_artists))) + results.addAll(it) + } } - musicStore.albums.filterByOrNull(query)?.let { - results.add(Header(id = -2, name = context.getString(R.string.label_albums))) - results.addAll(it) + if (mFilterMode.isAllOr(DisplayMode.SHOW_ALBUMS)) { + musicStore.albums.filterByOrNull(query)?.let { + results.add(Header(id = -2, name = context.getString(R.string.label_albums))) + results.addAll(it) + } } - musicStore.genres.filterByOrNull(query)?.let { - results.add(Header(id = -3, name = context.getString(R.string.label_genres))) - results.addAll(it) + if (mFilterMode.isAllOr(DisplayMode.SHOW_GENRES)) { + musicStore.genres.filterByOrNull(query)?.let { + results.add(Header(id = -3, name = context.getString(R.string.label_genres))) + results.addAll(it) + } } - musicStore.songs.filterByOrNull(query)?.let { - results.add(Header(id = -4, name = context.getString(R.string.label_songs))) - results.addAll(it) + if (mFilterMode.isAllOr(DisplayMode.SHOW_SONGS)) { + musicStore.songs.filterByOrNull(query)?.let { + results.add(Header(id = -4, name = context.getString(R.string.label_songs))) + results.addAll(it) + } } mSearchResults.value = results } } + fun updateFilterModeWithId(@IdRes id: Int, context: Context) { + mFilterMode = DisplayMode.fromId(id) + + settingsManager.searchFilterMode = mFilterMode + + doSearch(mLastQuery, context) + } + private fun List.filterByOrNull(value: String): List? { val filtered = filter { it.name.contains(value, ignoreCase = true) } return if (filtered.isNotEmpty()) filtered else null } + private fun List.filterByDisplayMode(mode: DisplayMode): List { + return when (mode) { + DisplayMode.SHOW_ALL -> this + DisplayMode.SHOW_SONGS -> filterIsInstance() + DisplayMode.SHOW_ALBUMS -> filterIsInstance() + DisplayMode.SHOW_ARTISTS -> filterIsInstance() + DisplayMode.SHOW_GENRES -> filterIsInstance() + } + } + /** * Update the current navigation status * @param value Whether LibraryFragment is navigating or not diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index b8d9fff1b..2b7fdf183 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -144,6 +144,22 @@ class SettingsManager private constructor(context: Context) : .apply() } + /** + * The current filter mode of the search tab + */ + var searchFilterMode: DisplayMode + get() = DisplayMode.valueOfOrFallback( + sharedPrefs.getString( + Keys.KEY_SEARCH_FILTER_MODE, DisplayMode.SHOW_ALL.toString() + ), + fallback = DisplayMode.SHOW_ALL + ) + set(value) { + sharedPrefs.edit() + .putString(Keys.KEY_SEARCH_FILTER_MODE, value.toString()) + .apply() + } + // --- CALLBACKS --- private val callbacks = mutableListOf() @@ -231,7 +247,7 @@ class SettingsManager private constructor(context: Context) : const val KEY_PREV_REWIND = "KEY_PREV_REWIND" const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE" - const val KEY_LIBRARY_FILTER_MODE = "KEY_LIBRARY_FILTER_MODE" + const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH" const val KEY_DEBUG_SAVE = "KEY_SAVE_STATE" } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 834eba791..f8e8b9e70 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,8 +114,7 @@ Could not open link. - Search Library… - Search Songs… + Search your library… Album Cover for %s