Refactor library structure
Refactor LibraryFragment & LibraryViewModel so that LibraryViewModel only holds the data and LibraryFragment displays it.
This commit is contained in:
parent
60750d976b
commit
859391e69b
7 changed files with 102 additions and 70 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue