Refactor library structure

Refactor LibraryFragment & LibraryViewModel so that LibraryViewModel only holds the data and LibraryFragment displays it.
This commit is contained in:
OxygenCobalt 2020-12-12 12:22:06 -07:00
parent 60750d976b
commit 859391e69b
7 changed files with 102 additions and 70 deletions

View file

@ -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)

View file

@ -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<DisplayMode> get() = mDisplayMode
private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN)
val sortMode: LiveData<SortMode> get() = mSortMode
private val mLibraryData = MutableLiveData(listOf<BaseModel>())
val libraryData: LiveData<List<BaseModel>> get() = mLibraryData
private val mSearchResults = MutableLiveData(listOf<BaseModel>())
val searchResults: LiveData<List<BaseModel>> 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<BaseModel>()
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)
)
}
}

View file

@ -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<RecyclerView.ViewHolder>() {
private var data: List<BaseModel>
init {
// Assign the data on startup depending on the type
data = when (displayMode) {
DisplayMode.SHOW_GENRES -> listOf<Genre>()
DisplayMode.SHOW_ARTISTS -> listOf<Artist>()
DisplayMode.SHOW_ALBUMS -> listOf<Album>()
else -> listOf<Artist>()
}
}
private var data = listOf<BaseModel>()
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<BaseModel>) {
if (data != newData) {
data = newData
data = newData
notifyDataSetChanged()
}
notifyDataSetChanged()
}
}

View file

@ -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

View file

@ -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.
* <a href="https://github.com/reddit/IndicatorFastScroll"> Link to repo </a>
* 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(

View file

@ -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)

View file

@ -80,6 +80,7 @@
<item name="colorPrimary">@color/control_color</item>
<item name="colorSecondary">@color/control_color</item>
<item name="dialogCornerRadius">0dp</item>
<item name="colorControlHighlight">@color/selection_color</item>
</style>
<!-- Custom dialog title theme -->