From 6627de4b62019901ec56c9bd7164cca80fd49e42 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Thu, 7 Jan 2021 10:40:10 -0700 Subject: [PATCH] Add filtering to library search Add filtering to the library search bar. --- .../oxycblt/auxio/library/LibraryFragment.kt | 98 ++++++++++++------- .../oxycblt/auxio/library/LibraryViewModel.kt | 77 +++++++++++---- .../org/oxycblt/auxio/recycler/DisplayMode.kt | 19 ++-- .../oxycblt/auxio/settings/SettingsManager.kt | 17 ++++ app/src/main/res/drawable/ic_filter.xml | 11 +++ app/src/main/res/menu/menu_library.xml | 32 +++++- app/src/main/res/values/strings.xml | 7 +- 7 files changed, 195 insertions(+), 66 deletions(-) create mode 100644 app/src/main/res/drawable/ic_filter.xml diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt index 6962cbdee..70ce06c99 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -41,8 +41,7 @@ import org.oxycblt.auxio.ui.toColor * search functionality. * FIXME: Heisenleak when navving from search * FIXME: Heisenleak on older versions - * TODO: Filtering & Search order upgrades - * TODO: Show result counts? + * TODO: Filtering */ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { @@ -61,10 +60,46 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { val searchAdapter = SearchAdapter(::onItemSelection, ::showActionsForItem) val sortAction = binding.libraryToolbar.menu.findItem(R.id.submenu_sorting) + val filterAction = binding.libraryToolbar.menu.findItem(R.id.submenu_filtering) + val searchView: SearchView // --- UI SETUP --- binding.libraryToolbar.apply { + menu.apply { + val searchAction = findItem(R.id.action_search) + searchView = searchAction.actionView as SearchView + + searchView.queryHint = getString(R.string.hint_search_library) + searchView.maxWidth = Int.MAX_VALUE + searchView.setOnQueryTextListener(this@LibraryFragment) + + searchAction.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + binding.libraryRecycler.adapter = searchAdapter + + searchAction.isVisible = false + sortAction.isVisible = false + filterAction.isVisible = true + + libraryModel.resetQuery() + + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + binding.libraryRecycler.adapter = libraryAdapter + + searchAction.isVisible = true + sortAction.isVisible = true + filterAction.isVisible = false + + libraryModel.resetQuery() + + return true + } + }) + } setOnMenuItemClickListener { when (it.itemId) { R.id.action_search -> { @@ -74,45 +109,22 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { it.expandActionView() } - R.id.submenu_sorting -> { - } + R.id.submenu_sorting -> {} - else -> libraryModel.updateSortMode(it.itemId) + R.id.submenu_filtering -> {} + + else -> { + if (sortAction.isVisible) { + libraryModel.updateSortMode(it.itemId) + } else if (filterAction.isVisible) { + libraryModel.updateFilterMode(it.itemId) + libraryModel.doSearch(searchView.query.toString(), requireContext()) + } + } } true } - - menu.apply { - val searchAction = findItem(R.id.action_search) - val searchView = searchAction.actionView as SearchView - - searchView.queryHint = getString(R.string.hint_search_library) - searchView.maxWidth = Int.MAX_VALUE - searchView.setOnQueryTextListener(this@LibraryFragment) - - searchAction.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - binding.libraryRecycler.adapter = searchAdapter - item.isVisible = false - sortAction.isVisible = false - - libraryModel.resetQuery() - - return true - } - - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - binding.libraryRecycler.adapter = libraryAdapter - item.isVisible = true - sortAction.isVisible = true - - libraryModel.resetQuery() - - return true - } - }) - } } binding.libraryRecycler.apply { @@ -164,6 +176,20 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { } } + libraryModel.filterMode.observe(viewLifecycleOwner) { mode -> + logD("Updating filter mode to $mode") + + val modeId = mode.toMenuId() + + filterAction.subMenu.forEach { + if (it.itemId == modeId) { + it.applyColor(accent.first.toColor(requireContext())) + } else { + it.applyColor(resolveAttr(requireContext(), android.R.attr.textColorPrimary)) + } + } + } + detailModel.navToItem.observe(viewLifecycleOwner) { if (it != null) { libraryModel.updateNavigationStatus(false) diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index e9b81c44f..0bf84d57f 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -22,16 +22,22 @@ import org.oxycblt.auxio.settings.SettingsManager */ class LibraryViewModel : ViewModel(), SettingsManager.Callback { private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN) - private val mLibraryData = MutableLiveData(listOf()) - private val mSearchResults = MutableLiveData(listOf()) - private var mDisplayMode = DisplayMode.SHOW_ARTISTS - private var mIsNavigating = false - val sortMode: LiveData get() = mSortMode + + private val mLibraryData = MutableLiveData(listOf()) val libraryData: LiveData> get() = mLibraryData + + private val mFilterMode = MutableLiveData(DisplayMode.SHOW_ALL) + val filterMode: LiveData get() = mFilterMode + + private val mSearchResults = MutableLiveData(listOf()) val searchResults: LiveData> get() = mSearchResults + + private var mIsNavigating = false val isNavigating: Boolean get() = mIsNavigating + private var mDisplayMode = DisplayMode.SHOW_ARTISTS + private val settingsManager = SettingsManager.getInstance() private val musicStore = MusicStore.getInstance() @@ -41,6 +47,7 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { // Set up the display/sort modes mDisplayMode = settingsManager.libraryDisplayMode mSortMode.value = settingsManager.librarySortMode + mFilterMode.value = settingsManager.libraryFilterMode updateLibraryData() } @@ -74,8 +81,8 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { } DisplayMode.SHOW_ARTISTS -> { - searchForArtists(combined, query, context) - - searchForAlbums(combined, query, context) + searchForArtists(combined, query, context) + searchForAlbums(combined, query, context) searchForGenres(combined, query, context) } @@ -84,6 +91,8 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { searchForArtists(combined, query, context) searchForGenres(combined, query, context) } + + else -> {} } mSearchResults.value = combined @@ -95,11 +104,15 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { query: String, context: Context ): MutableList { - val genres = musicStore.genres.filter { it.name.contains(query, true) } + if (mFilterMode.value == DisplayMode.SHOW_ALL || + mFilterMode.value == DisplayMode.SHOW_GENRES + ) { + val genres = musicStore.genres.filter { it.name.contains(query, true) } - if (genres.isNotEmpty()) { - data.add(Header(id = 0, name = context.getString(R.string.label_genres))) - data.addAll(genres) + if (genres.isNotEmpty()) { + data.add(Header(id = 0, name = context.getString(R.string.label_genres))) + data.addAll(genres) + } } return data @@ -110,11 +123,15 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { query: String, context: Context ): MutableList { - val artists = musicStore.artists.filter { it.name.contains(query, true) } + if (mFilterMode.value == DisplayMode.SHOW_ALL || + mFilterMode.value == DisplayMode.SHOW_ARTISTS + ) { + val artists = musicStore.artists.filter { it.name.contains(query, true) } - if (artists.isNotEmpty()) { - data.add(Header(id = 1, name = context.getString(R.string.label_artists))) - data.addAll(artists) + if (artists.isNotEmpty()) { + data.add(Header(id = 1, name = context.getString(R.string.label_artists))) + data.addAll(artists) + } } return data @@ -125,16 +142,36 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { query: String, context: Context ): MutableList { - val albums = musicStore.albums.filter { it.name.contains(query, true) } + if (mFilterMode.value == DisplayMode.SHOW_ALL || + mFilterMode.value == DisplayMode.SHOW_ALBUMS + ) { + val albums = musicStore.albums.filter { it.name.contains(query, true) } - if (albums.isNotEmpty()) { - data.add(Header(id = 2, name = context.getString(R.string.label_albums))) - data.addAll(albums) + if (albums.isNotEmpty()) { + data.add(Header(id = 2, name = context.getString(R.string.label_albums))) + data.addAll(albums) + } } return data } + fun updateFilterMode(@IdRes itemId: Int) { + val mode = when (itemId) { + R.id.option_filter_all -> DisplayMode.SHOW_ALL + R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS + R.id.option_filter_artists -> DisplayMode.SHOW_ARTISTS + R.id.option_filter_genres -> DisplayMode.SHOW_GENRES + + else -> DisplayMode.SHOW_ALL + } + + if (mFilterMode.value != mode) { + mFilterMode.value = mode + settingsManager.libraryFilterMode = mode + } + } + fun resetQuery() { mSearchResults.value = listOf() } @@ -203,6 +240,8 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { DisplayMode.SHOW_ALBUMS -> { mSortMode.value!!.getSortedAlbumList(musicStore.albums) } + + else -> error("DisplayMode $mDisplayMode is unsupported.") } } } 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 3cfaf1567..126c2f745 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 /** @@ -8,20 +9,22 @@ import org.oxycblt.auxio.R * @author OxygenCobalt */ enum class DisplayMode(@DrawableRes val iconRes: Int) { + SHOW_ALL(R.drawable.ic_sort_none), SHOW_GENRES(R.drawable.ic_genre), SHOW_ARTISTS(R.drawable.ic_artist), SHOW_ALBUMS(R.drawable.ic_album); /** - * Make a slice of all the values that this DisplayMode covers. - * - * ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS - * @return The values that this DisplayMode covers. + * Get a menu action for this show mode. Corresponds to filter actions. */ - fun getChildren(): List { - val vals = values() - - return vals.slice(vals.indexOf(this) until vals.size) + @IdRes + fun toMenuId(): Int { + return when (this) { + SHOW_ALL -> (R.id.option_filter_all) + SHOW_ALBUMS -> (R.id.option_filter_albums) + SHOW_ARTISTS -> (R.id.option_filter_artists) + SHOW_GENRES -> (R.id.option_filter_genres) + } } companion object { 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 b16dc88f0..47f5bdbdb 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 [DisplayMode] of the library search filtering + */ + var libraryFilterMode: DisplayMode + get() = DisplayMode.valueOfOrFallback( + sharedPrefs.getString( + Keys.KEY_LIBRARY_FILTER_MODE, + DisplayMode.SHOW_ARTISTS.toString() + ) + ) + set(value) { + sharedPrefs.edit() + .putString(Keys.KEY_LIBRARY_FILTER_MODE, value.toString()) + .apply() + } + // --- CALLBACKS --- private val callbacks = mutableListOf() @@ -231,6 +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_DEBUG_SAVE = "KEY_SAVE_STATE" } diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 000000000..7436eb6aa --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_library.xml b/app/src/main/res/menu/menu_library.xml index 0a85b8674..a63eb0215 100644 --- a/app/src/main/res/menu/menu_library.xml +++ b/app/src/main/res/menu/menu_library.xml @@ -12,8 +12,8 @@ tools:ignore="AlwaysShowAction" /> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ffc6540b0..e2880dfaf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,10 @@ Genres Artists Albums + Search + Filter + All Sort Default @@ -69,7 +72,9 @@ Turn off to save memory usage Ignore MediaStore covers - Results in higher quality album covers, but causes slower loading and higher memory usage + + Results in higher quality album covers, but causes slower loading and higher memory usage + Use alternate notification action Prefer repeat mode action