From 859391e69b969fa002fa636a8441a61efc667920 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 12 Dec 2020 12:22:06 -0700 Subject: [PATCH] Refactor library structure Refactor LibraryFragment & LibraryViewModel so that LibraryViewModel only holds the data and LibraryFragment displays it. --- .../oxycblt/auxio/library/LibraryFragment.kt | 32 ++++---- .../oxycblt/auxio/library/LibraryViewModel.kt | 49 ++++++++---- .../auxio/library/adapters/LibraryAdapter.kt | 74 ++++++++++--------- .../auxio/library/adapters/SearchAdapter.kt | 2 + .../oxycblt/auxio/recycler/NoLeakThumbView.kt | 7 +- .../org/oxycblt/auxio/songs/SongsFragment.kt | 7 ++ app/src/main/res/values/styles.xml | 1 + 7 files changed, 102 insertions(+), 70 deletions(-) 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 48445f779..13b215bdb 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -23,7 +23,6 @@ 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.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.state.PlaybackMode @@ -51,10 +50,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { ): View { val binding = FragmentLibraryBinding.inflate(inflater) - val musicStore = MusicStore.getInstance() - val libraryAdapter = LibraryAdapter( - libraryModel.displayMode.value!!, doOnClick = { navToItem(it) }, doOnLongClick = { data, view -> showActionsForItem(data, view) } ) @@ -127,16 +123,21 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { // --- VIEWMODEL SETUP --- + libraryModel.libraryData.observe(viewLifecycleOwner) { + libraryAdapter.updateData(it) + } + + libraryModel.searchResults.observe(viewLifecycleOwner) { + if (libraryModel.searchHasFocus) { + searchAdapter.submitList(it) { + binding.libraryRecycler.scrollToPosition(0) + } + } + } + libraryModel.sortMode.observe(viewLifecycleOwner) { mode -> Log.d(this::class.simpleName, "Updating sort mode to $mode") - // Update the adapter with the new data - libraryAdapter.updateData( - mode.getSortedBaseModelList( - musicStore.getListForShowMode(libraryModel.displayMode.value!!) - ) - ) - // Then update the menu item in the toolbar to reflect the new mode binding.libraryToolbar.menu.forEach { if (it.itemId == libraryModel.sortMode.value!!.toMenuId()) { @@ -147,14 +148,6 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { } } - libraryModel.searchResults.observe(viewLifecycleOwner) { - if (libraryModel.searchHasFocus) { - searchAdapter.submitList(it) { - binding.libraryRecycler.scrollToPosition(0) - } - } - } - playbackModel.navToItem.observe(viewLifecycleOwner) { if (it != null) { libraryModel.updateNavigationStatus(false) @@ -188,6 +181,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { private fun showActionsForItem(data: BaseModel, view: View) { val menu = PopupMenu(requireContext(), view) + when (data) { is Song -> menu.setupSongActions(data, requireContext(), playbackModel) is Album -> menu.setupAlbumActions(data, requireContext(), playbackModel) 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 bcda94b63..edcd6ec05 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -21,15 +21,17 @@ import org.oxycblt.auxio.settings.SettingsManager * @author OxygenCobalt */ class LibraryViewModel : ViewModel(), SettingsManager.Callback { - private val mDisplayMode = MutableLiveData(DisplayMode.SHOW_ARTISTS) - val displayMode: LiveData get() = mDisplayMode - private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN) val sortMode: LiveData get() = mSortMode + private val mLibraryData = MutableLiveData(listOf()) + val libraryData: LiveData> get() = mLibraryData + private val mSearchResults = MutableLiveData(listOf()) val searchResults: LiveData> get() = mSearchResults + private var mDisplayMode = DisplayMode.SHOW_ARTISTS + private var mIsNavigating = false val isNavigating: Boolean get() = mIsNavigating @@ -37,14 +39,19 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { val searchHasFocus: Boolean get() = mSearchHasFocus private val settingsManager = SettingsManager.getInstance() + private val musicStore = MusicStore.getInstance() init { settingsManager.addCallback(this) - mDisplayMode.value = settingsManager.libraryDisplayMode + mDisplayMode = settingsManager.libraryDisplayMode mSortMode.value = settingsManager.librarySortMode + + updateLibraryData() } + // --- SEARCH FUNCTIONS --- + /** * Perform a search of the music library, given a query. * Results are pushed to [searchResults]. @@ -63,9 +70,8 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { // the query, and update the LiveData with those items. This is done on a separate // thread as it can be a very long operation for large music libraries. viewModelScope.launch { - val musicStore = MusicStore.getInstance() val combined = mutableListOf() - val children = displayMode.value!!.getChildren() + val children = mDisplayMode.getChildren() // If the Library DisplayMode supports it, include artists / genres in the search. if (children.contains(DisplayMode.SHOW_GENRES)) { @@ -105,17 +111,15 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { } } + fun updateSearchFocusStatus(value: Boolean) { + mSearchHasFocus = value + } + fun resetQuery() { mSearchResults.value = listOf() } - fun updateNavigationStatus(value: Boolean) { - mIsNavigating = value - } - - fun updateSearchFocusStatus(value: Boolean) { - mSearchHasFocus = value - } + // --- LIBRARY FUNCTIONS --- fun updateSortMode(@IdRes itemId: Int) { val mode = when (itemId) { @@ -128,11 +132,16 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { if (mode != mSortMode.value) { mSortMode.value = mode - settingsManager.librarySortMode = mode + + updateLibraryData() } } + fun updateNavigationStatus(value: Boolean) { + mIsNavigating = value + } + // --- OVERRIDES --- override fun onCleared() { @@ -142,6 +151,16 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback { } override fun onLibDisplayModeUpdate(displayMode: DisplayMode) { - mDisplayMode.value = displayMode + mDisplayMode = displayMode + + updateLibraryData() + } + + // --- UTILS --- + + private fun updateLibraryData() { + mLibraryData.value = mSortMode.value!!.getSortedBaseModelList( + musicStore.getListForShowMode(mDisplayMode) + ) } } diff --git a/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt index db5e16eb4..d85d9189e 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/adapters/LibraryAdapter.kt @@ -1,69 +1,77 @@ package org.oxycblt.auxio.library.adapters +import android.util.Log import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView 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.recycler.DisplayMode +import org.oxycblt.auxio.music.Header +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder +import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder +import org.oxycblt.auxio.recycler.viewholders.SongViewHolder /** - * The primary recyclerview for the library. Can display either Genres, Artists, and Albums. + * A near-identical adapter as [SearchAdapter] but this one isn't a [ListAdapter] + * Id love to unify these two adapters but that triggers a bug on the android backend, so... * @author OxygenCobalt */ class LibraryAdapter( - private val displayMode: DisplayMode, private val doOnClick: (data: BaseModel) -> Unit, private val doOnLongClick: (data: BaseModel, view: View) -> Unit ) : RecyclerView.Adapter() { - private var data: List - - init { - // Assign the data on startup depending on the type - data = when (displayMode) { - DisplayMode.SHOW_GENRES -> listOf() - DisplayMode.SHOW_ARTISTS -> listOf() - DisplayMode.SHOW_ALBUMS -> listOf() - - else -> listOf() - } - } + private var data = listOf() override fun getItemCount(): Int = data.size - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - // Return a different View Holder depending on the show type - return when (displayMode) { - DisplayMode.SHOW_GENRES -> GenreViewHolder.from(parent.context, doOnClick, doOnLongClick) - DisplayMode.SHOW_ARTISTS -> ArtistViewHolder.from(parent.context, doOnClick, doOnLongClick) - DisplayMode.SHOW_ALBUMS -> AlbumViewHolder.from(parent.context, doOnClick, doOnLongClick) + override fun getItemViewType(position: Int): Int { + return when (data[position]) { + is Genre -> GenreViewHolder.ITEM_TYPE + is Artist -> ArtistViewHolder.ITEM_TYPE + is Album -> AlbumViewHolder.ITEM_TYPE - else -> error("Bad DisplayMode given.") + else -> -1 + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from( + parent.context, doOnClick, doOnLongClick + ) + + ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from( + parent.context, doOnClick, doOnLongClick + ) + + AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from( + parent.context, doOnClick, doOnLongClick + ) + + else -> error("Invalid viewholder item type.") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (displayMode) { - DisplayMode.SHOW_GENRES -> (holder as GenreViewHolder).bind(data[position] as Genre) - DisplayMode.SHOW_ARTISTS -> (holder as ArtistViewHolder).bind(data[position] as Artist) - DisplayMode.SHOW_ALBUMS -> (holder as AlbumViewHolder).bind(data[position] as Album) - - else -> return + when (val item = data[position]) { + is Genre -> (holder as GenreViewHolder).bind(item) + is Artist -> (holder as ArtistViewHolder).bind(item) + is Album -> (holder as AlbumViewHolder).bind(item) } } - // Update the data, as its an internal value. fun updateData(newData: List) { - if (data != newData) { - data = newData + data = newData - notifyDataSetChanged() - } + notifyDataSetChanged() } } diff --git a/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt index eea6968c1..bb375ae2a 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt @@ -1,7 +1,9 @@ package org.oxycblt.auxio.library.adapters +import android.util.Log import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.music.Album diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/NoLeakThumbView.kt b/app/src/main/java/org/oxycblt/auxio/recycler/NoLeakThumbView.kt index f856e6b73..ad83c6372 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/NoLeakThumbView.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/NoLeakThumbView.kt @@ -29,9 +29,10 @@ import org.oxycblt.auxio.ui.toColor /** * A semi-copy, semi-custom implementation of [com.reddit.indicatorfastscroll.FastScrollerThumbView] - * that fixes a memory leak that occurs from a bug fix they added. All credit goes to the authors of - * the fast scroll library. - * Link to repo + * that fixes a memory leak that occurs from a bug fix they added. + * All credit goes to the authors of the fast scroll library. + * + * https://github.com/reddit/IndicatorFastScroll * @author Reddit, OxygenCobalt */ class NoLeakThumbView @JvmOverloads constructor( diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index 17c5b4fdb..aa2a4b042 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -68,6 +68,13 @@ class SongsFragment : Fragment() { binding.songRecycler.apply { adapter = songAdapter setHasFixedSize(true) + + post { + if (computeVerticalScrollRange() < height) { + binding.songFastScroll.visibility = View.GONE + binding.songFastScrollThumb.visibility = View.GONE + } + } } setupFastScroller(binding) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3c97142c0..f770e0cfa 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -80,6 +80,7 @@ @color/control_color @color/control_color 0dp + @color/selection_color