From ab194c14c24b5ee726f45ffa8ecc279905683017 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 2 Apr 2022 20:21:06 -0600 Subject: [PATCH] ui: create viewmodel for navigation Create a ViewModel for the more complicated navigation pathways. Normally, navigation was fragmented along a complicated stretch of fragment hacks and DetailViewModel's navToItem attitbute, both of which were not really that ideal. Dumpster them for a single, unified viewmodel for the more complicated navigation situations. This removes much of the duplicate navigation logic and is likely much more maintainable for future situations. --- .../java/org/oxycblt/auxio/MainFragment.kt | 33 +++++++- .../org/oxycblt/auxio/accent/AccentAdapter.kt | 19 +++-- .../auxio/coil/{Fetchers.kt => Components.kt} | 16 +++- .../java/org/oxycblt/auxio/coil/MusicKeyer.kt | 35 -------- .../org/oxycblt/auxio/coil/StyledImageView.kt | 6 +- .../auxio/detail/AlbumDetailFragment.kt | 31 +++---- .../auxio/detail/ArtistDetailFragment.kt | 61 ++++++-------- .../oxycblt/auxio/detail/DetailFragment.kt | 7 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 26 ------ .../auxio/detail/GenreDetailFragment.kt | 37 ++++----- .../org/oxycblt/auxio/home/HomeFragment.kt | 22 +++-- .../auxio/home/list/AlbumListFragment.kt | 7 +- .../auxio/home/list/ArtistListFragment.kt | 7 +- .../auxio/home/list/GenreListFragment.kt | 7 +- .../auxio/home/list/HomeListFragment.kt | 4 +- .../java/org/oxycblt/auxio/home/tabs/Tab.kt | 2 +- .../auxio/playback/PlaybackBarFragment.kt | 14 ++-- .../auxio/playback/PlaybackPanelFragment.kt | 28 ++----- .../oxycblt/auxio/search/SearchFragment.kt | 36 +++------ .../oxycblt/auxio/search/SearchViewModel.kt | 2 - .../java/org/oxycblt/auxio/ui/ActionMenu.kt | 11 ++- .../oxycblt/auxio/ui/NavigationViewModel.kt | 80 +++++++++++++++++++ .../layout-land/fragment_playback_panel.xml | 2 + .../fragment_playback_panel.xml | 2 + .../fragment_playback_panel.xml | 2 + .../fragment_playback_panel.xml | 2 + app/src/main/res/layout/dialog_accent.xml | 1 + app/src/main/res/layout/dialog_excluded.xml | 3 +- app/src/main/res/layout/dialog_tabs.xml | 1 + app/src/main/res/layout/fragment_about.xml | 3 + app/src/main/res/layout/fragment_detail.xml | 1 + app/src/main/res/layout/fragment_home.xml | 4 + .../main/res/layout/fragment_home_list.xml | 2 +- app/src/main/res/layout/fragment_main.xml | 2 + .../res/layout/fragment_playback_panel.xml | 2 + app/src/main/res/layout/fragment_queue.xml | 1 + app/src/main/res/layout/fragment_search.xml | 2 + app/src/main/res/layout/fragment_settings.xml | 2 + app/src/main/res/layout/item_accent.xml | 1 + app/src/main/res/layout/item_album_song.xml | 1 + app/src/main/res/layout/item_excluded_dir.xml | 1 + app/src/main/res/layout/item_queue_song.xml | 2 + app/src/main/res/layout/item_sort_header.xml | 2 + 43 files changed, 283 insertions(+), 247 deletions(-) rename app/src/main/java/org/oxycblt/auxio/coil/{Fetchers.kt => Components.kt} (89%) delete mode 100644 app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 30c628f72..5b0c54131 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -27,12 +27,16 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.activityViewModels import androidx.navigation.findNavController +import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar import org.oxycblt.auxio.databinding.FragmentMainBinding +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.ui.MainNavigationAction +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.logW @@ -45,13 +49,13 @@ import org.oxycblt.auxio.util.logW */ class MainFragment : ViewBindingFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() + private val navModel: NavigationViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels() private var callback: DynamicBackPressedCallback? = null override fun onCreateBinding(inflater: LayoutInflater) = FragmentMainBinding.inflate(inflater) override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) { - // --- UI SETUP --- // Build the permission launcher here as you can only do it in onCreateView/onCreate val permLauncher = @@ -89,6 +93,9 @@ class MainFragment : ViewBindingFragment() { handleLoaderResponse(response, permLauncher) } + navModel.mainNavigationAction.observe(viewLifecycleOwner, ::handleMainNavigation) + navModel.exploreNavigationItem.observe(viewLifecycleOwner, ::handleExploreNavigation) + playbackModel.song.observe(viewLifecycleOwner, ::updateSong) } @@ -146,6 +153,30 @@ class MainFragment : ViewBindingFragment() { } } + private fun handleMainNavigation(action: MainNavigationAction?) { + if (action == null) return + + val binding = requireBinding() + when (action) { + MainNavigationAction.EXPAND -> binding.bottomSheetLayout.expand() + MainNavigationAction.COLLAPSE -> binding.bottomSheetLayout.collapse() + MainNavigationAction.SETTINGS -> + findNavController().navigate(MainFragmentDirections.actionShowSettings()) + MainNavigationAction.ABOUT -> + findNavController().navigate(MainFragmentDirections.actionShowAbout()) + MainNavigationAction.QUEUE -> + findNavController().navigate(MainFragmentDirections.actionShowQueue()) + } + + navModel.finishMainNavigation() + } + + private fun handleExploreNavigation(item: Music?) { + if (item != null) { + requireBinding().bottomSheetLayout.collapse() + } + } + private fun updateSong(song: Song?) { val binding = requireBinding() if (song != null) { diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt index 4f471dfd6..485e7d6ad 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt @@ -33,15 +33,15 @@ import org.oxycblt.auxio.util.stateList /** An adapter that displays the accent palette. */ class AccentAdapter(listener: Listener) : - MonoAdapter(listener) { + MonoAdapter(listener) { var selectedAccent: Accent? = null private set - private var selectedViewHolder: NewAccentViewHolder? = null + private var selectedViewHolder: AccentViewHolder? = null override val data = AccentData() - override val creator = NewAccentViewHolder.CREATOR + override val creator = AccentViewHolder.CREATOR - override fun onBindViewHolder(viewHolder: NewAccentViewHolder, position: Int) { + override fun onBindViewHolder(viewHolder: AccentViewHolder, position: Int) { super.onBindViewHolder(viewHolder, position) if (data.getItem(position) == selectedAccent) { @@ -55,7 +55,7 @@ class AccentAdapter(listener: Listener) : if (accent == selectedAccent) return selectedAccent = accent selectedViewHolder?.setSelected(false) - selectedViewHolder = recycler.getViewHolderAt(accent.index) as NewAccentViewHolder? + selectedViewHolder = recycler.getViewHolderAt(accent.index) as AccentViewHolder? selectedViewHolder?.setSelected(true) } @@ -69,7 +69,7 @@ class AccentAdapter(listener: Listener) : } } -class NewAccentViewHolder private constructor(private val binding: ItemAccentBinding) : +class AccentViewHolder private constructor(private val binding: ItemAccentBinding) : BindingViewHolder(binding.root) { override fun bind(item: Accent, listener: AccentAdapter.Listener) { @@ -79,9 +79,8 @@ class NewAccentViewHolder private constructor(private val binding: ItemAccentBin backgroundTintList = context.getColorSafe(item.primary).stateList contentDescription = context.getString(item.name) TooltipCompat.setTooltipText(this, contentDescription) + setOnClickListener { listener.onAccentSelected(item) } } - - binding.accent.setOnClickListener { listener.onAccentSelected(item) } } fun setSelected(isSelected: Boolean) { @@ -98,12 +97,12 @@ class NewAccentViewHolder private constructor(private val binding: ItemAccentBin companion object { val CREATOR = - object : Creator { + object : Creator { override val viewType: Int get() = throw UnsupportedOperationException() override fun create(context: Context) = - NewAccentViewHolder(ItemAccentBinding.inflate(context.inflater)) + AccentViewHolder(ItemAccentBinding.inflate(context.inflater)) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt b/app/src/main/java/org/oxycblt/auxio/coil/Components.kt similarity index 89% rename from app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt rename to app/src/main/java/org/oxycblt/auxio/coil/Components.kt index f7e7f48ed..1e6fb5776 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/Components.kt @@ -24,6 +24,7 @@ import coil.decode.ImageSource import coil.fetch.FetchResult import coil.fetch.Fetcher import coil.fetch.SourceResult +import coil.key.Keyer import coil.request.Options import coil.size.Size import kotlin.math.min @@ -32,9 +33,22 @@ import okio.source import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.ui.Sort +/** A basic keyer for music data. */ +class MusicKeyer : Keyer { + override fun key(data: Music, options: Options): String { + return if (data is Song) { + // Group up song covers with album covers for better caching + key(data.album, options) + } else { + "${data::class.simpleName}: ${data.id}" + } + } +} + /** * Fetcher that returns the album art for a given [Album] or [Song], depending on the factory used. * @author OxygenCobalt @@ -128,7 +142,7 @@ private inline fun Collection.mapAtMost( break } - transform(item)?.let { out.add(it) } + transform(item)?.let(out::add) } return out diff --git a/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt b/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt deleted file mode 100644 index 0b9c58904..000000000 --- a/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2022 Auxio Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.oxycblt.auxio.coil - -import coil.key.Keyer -import coil.request.Options -import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.Song - -/** A basic keyer for music data. */ -class MusicKeyer : Keyer { - override fun key(data: Music, options: Options): String { - return if (data is Song) { - // Group up song covers with album covers for better caching - key(data.album, options) - } else { - "${data::class.simpleName}: ${data.id}" - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/coil/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/coil/StyledImageView.kt index 30ca20a4e..54293d2f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/StyledImageView.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/StyledImageView.kt @@ -44,7 +44,10 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.getColorStateListSafe -/** An [AppCompatImageView] that applies many of the stylistic choices thjat Auxio uses wi */ +/** + * An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding + * images. + */ class StyledImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : @@ -85,6 +88,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) + // Scale the image down to half-size imageMatrix = centerMatrix.apply { reset() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 8d296135e..13e14f364 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -81,16 +81,9 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { // -- VIEWMODEL SETUP --- - detailModel.albumData.observe(viewLifecycleOwner) { list -> - detailAdapter.data.submitList(list) - } - - detailModel.navToItem.observe(viewLifecycleOwner) { item -> - handleNavigation(item, detailAdapter) - } - - updateSong(playbackModel.song.value, detailAdapter) - playbackModel.song.observe(viewLifecycleOwner) { song -> updateSong(song, detailAdapter) } + detailModel.albumData.observe(viewLifecycleOwner, detailAdapter.data::submitList) + navModel.exploreNavigationItem.observe(viewLifecycleOwner, ::handleNavigation) + playbackModel.song.observe(viewLifecycleOwner, ::updateSong) } override fun onItemClick(item: Item) { @@ -126,7 +119,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { unlikelyToBeNull(detailModel.currentAlbum.value).artist.id)) } - private fun handleNavigation(item: Music?, adapter: AlbumDetailAdapter) { + private fun handleNavigation(item: Music?) { val binding = requireBinding() when (item) { // Songs should be scrolled to if the album matches, or a new detail @@ -134,8 +127,8 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { is Song -> { if (unlikelyToBeNull(detailModel.currentAlbum.value).id == item.album.id) { logD("Navigating to a song in this album") - scrollToItem(item.id, adapter) - detailModel.finishNavToItem() + scrollToItem(item.id) + navModel.finishExploreNavigation() } else { logD("Navigating to another album") findNavController() @@ -149,7 +142,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { if (unlikelyToBeNull(detailModel.currentAlbum.value).id == item.id) { logD("Navigating to the top of this album") binding.detailRecycler.scrollToPosition(0) - detailModel.finishNavToItem() + navModel.finishExploreNavigation() } else { logD("Navigating to another album") findNavController() @@ -169,9 +162,9 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { } /** Scroll to an song using its [id]. */ - private fun scrollToItem(id: Long, adapter: AlbumDetailAdapter) { + private fun scrollToItem(id: Long) { // Calculate where the item for the currently played song is - val pos = adapter.data.currentList.indexOfFirst { it.id == id && it is Song } + val pos = detailAdapter.data.currentList.indexOfFirst { it.id == id && it is Song } if (pos != -1) { val binding = requireBinding() @@ -189,7 +182,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { } /** Updates the queue actions when a song is present or not */ - private fun updateSong(song: Song?, adapter: AlbumDetailAdapter) { + private fun updateSong(song: Song?) { val binding = requireBinding() for (item in binding.detailToolbar.menu.children) { @@ -200,10 +193,10 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { if (playbackModel.playbackMode.value == PlaybackMode.IN_ALBUM && playbackModel.parent.value?.id == unlikelyToBeNull(detailModel.currentAlbum.value).id) { - adapter.highlightSong(song, binding.detailRecycler) + detailAdapter.highlightSong(song, binding.detailRecycler) } else { // Clear the ViewHolders if the mode isn't ALL_SONGS - adapter.highlightSong(null, binding.detailRecycler) + detailAdapter.highlightSong(null, binding.detailRecycler) } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index b13aadb3a..a33f6d428 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -63,27 +63,16 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { // --- VIEWMODEL SETUP --- - detailModel.artistData.observe(viewLifecycleOwner) { list -> - detailAdapter.data.submitList(list) - } - - detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation) - - // Highlight songs if they are being played - playbackModel.song.observe(viewLifecycleOwner) { song -> updateSong(song, detailAdapter) } - - // Highlight albums if they are being played - playbackModel.parent.observe(viewLifecycleOwner) { parent -> - updateParent(parent, detailAdapter) - } + detailModel.artistData.observe(viewLifecycleOwner, detailAdapter.data::submitList) + navModel.exploreNavigationItem.observe(viewLifecycleOwner, ::handleNavigation) + playbackModel.song.observe(viewLifecycleOwner, ::updateSong) + playbackModel.parent.observe(viewLifecycleOwner, ::updateParent) } override fun onItemClick(item: Item) { when (item) { is Song -> playbackModel.playSong(item, PlaybackMode.IN_ARTIST) - is Album -> - findNavController() - .navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.id)) + is Album -> navModel.exploreNavigateTo(item) } } @@ -111,49 +100,49 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { val binding = requireBinding() when (item) { - is Artist -> { - if (item.id == detailModel.currentArtist.value?.id) { - logD("Navigating to the top of this artist") - binding.detailRecycler.scrollToPosition(0) - detailModel.finishNavToItem() - } else { - logD("Navigating to another artist") - findNavController() - .navigate(ArtistDetailFragmentDirections.actionShowArtist(item.id)) - } + is Song -> { + logD("Navigating to another album") + findNavController() + .navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)) } is Album -> { logD("Navigating to another album") findNavController() .navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.id)) } - is Song -> { - logD("Navigating to another album") - findNavController() - .navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)) + is Artist -> { + if (item.id == detailModel.currentArtist.value?.id) { + logD("Navigating to the top of this artist") + binding.detailRecycler.scrollToPosition(0) + navModel.finishExploreNavigation() + } else { + logD("Navigating to another artist") + findNavController() + .navigate(ArtistDetailFragmentDirections.actionShowArtist(item.id)) + } } null -> {} else -> logW("Unsupported navigation item ${item::class.java}") } } - private fun updateSong(song: Song?, adapter: ArtistDetailAdapter) { + private fun updateSong(song: Song?) { val binding = requireBinding() if (playbackModel.playbackMode.value == PlaybackMode.IN_ARTIST && playbackModel.parent.value?.id == detailModel.currentArtist.value?.id) { - adapter.highlightSong(song, binding.detailRecycler) + detailAdapter.highlightSong(song, binding.detailRecycler) } else { // Clear the ViewHolders if the mode isn't ALL_SONGS - adapter.highlightSong(null, binding.detailRecycler) + detailAdapter.highlightSong(null, binding.detailRecycler) } } - private fun updateParent(parent: MusicParent?, adapter: ArtistDetailAdapter) { + private fun updateParent(parent: MusicParent?) { val binding = requireBinding() if (parent is Album?) { - adapter.highlightAlbum(parent, binding.detailRecycler) + detailAdapter.highlightAlbum(parent, binding.detailRecycler) } else { - adapter.highlightAlbum(null, binding.detailRecycler) + detailAdapter.highlightAlbum(null, binding.detailRecycler) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index a6b044b55..2c6880460 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -29,6 +29,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.logD @@ -40,16 +41,12 @@ import org.oxycblt.auxio.util.unlikelyToBeNull */ abstract class DetailFragment : ViewBindingFragment() { protected val detailModel: DetailViewModel by activityViewModels() + protected val navModel: NavigationViewModel by activityViewModels() protected val playbackModel: PlaybackViewModel by activityViewModels() override fun onCreateBinding(inflater: LayoutInflater): FragmentDetailBinding = FragmentDetailBinding.inflate(inflater) - override fun onResume() { - super.onResume() - detailModel.setNavigating(false) - } - override fun onDestroyBinding(binding: FragmentDetailBinding) { super.onDestroyBinding(binding) binding.detailRecycler.adapter = null diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 6795b822d..44dae3e3a 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -25,7 +25,6 @@ import org.oxycblt.auxio.detail.recycler.SortHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.Header @@ -38,7 +37,6 @@ import org.oxycblt.auxio.util.logD * - What item the fragment should be showing * - The RecyclerView data for each fragment * - Menu triggers for each fragment - * - Navigation triggers for each fragment [e.g "Go to artist"] * @author OxygenCobalt */ class DetailViewModel : ViewModel() { @@ -87,15 +85,6 @@ class DetailViewModel : ViewModel() { currentGenre.value?.let(::refreshGenreData) } - private val mNavToItem = MutableLiveData() - - /** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */ - val navToItem: LiveData - get() = mNavToItem - - var isNavigating = false - private set - fun setAlbumId(id: Long) { if (mCurrentAlbum.value?.id == id) return val musicStore = MusicStore.requireInstance() @@ -122,21 +111,6 @@ class DetailViewModel : ViewModel() { refreshGenreData(genre) } - /** Navigate to an item, whether a song/album/artist */ - fun navToItem(item: Music) { - mNavToItem.value = item - } - - /** Mark that the navigation process is done. */ - fun finishNavToItem() { - mNavToItem.value = null - } - - /** Update the current navigation status to [isNavigating] */ - fun setNavigating(navigating: Boolean) { - isNavigating = navigating - } - private fun refreshGenreData(genre: Genre) { logD("Refreshing genre data") val data = mutableListOf(genre) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index f5bbae8c6..a878ec501 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -36,7 +36,6 @@ import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -61,13 +60,9 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { // --- VIEWMODEL SETUP --- - detailModel.genreData.observe(viewLifecycleOwner) { list -> - detailAdapter.data.submitList(list) - } - - detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation) - - playbackModel.song.observe(viewLifecycleOwner) { song -> updateSong(song, detailAdapter) } + detailModel.genreData.observe(viewLifecycleOwner, detailAdapter.data::submitList) + navModel.exploreNavigationItem.observe(viewLifecycleOwner, ::handleNavigation) + playbackModel.song.observe(viewLifecycleOwner, ::updateSong) } override fun onItemClick(item: Item) { @@ -97,34 +92,36 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { private fun handleNavigation(item: Music?) { when (item) { + is Song -> { + logD("Navigating to another song") + findNavController() + .navigate(GenreDetailFragmentDirections.actionShowAlbum(item.album.id)) + } + is Album -> { + logD("Navigating to another album") + findNavController().navigate(GenreDetailFragmentDirections.actionShowAlbum(item.id)) + } // All items will launch new detail fragments. is Artist -> { logD("Navigating to another artist") findNavController() .navigate(GenreDetailFragmentDirections.actionShowArtist(item.id)) } - is Album -> { - logD("Navigating to another album") - findNavController().navigate(GenreDetailFragmentDirections.actionShowAlbum(item.id)) - } - is Song -> { - logD("Navigating to another song") - findNavController() - .navigate(GenreDetailFragmentDirections.actionShowAlbum(item.album.id)) + is Genre -> { + navModel.finishExploreNavigation() } null -> {} - else -> logW("Unsupported navigation command ${item::class.java}") } } - private fun updateSong(song: Song?, adapter: GenreDetailAdapter) { + private fun updateSong(song: Song?) { val binding = requireBinding() if (playbackModel.playbackMode.value == PlaybackMode.IN_GENRE && playbackModel.parent.value?.id == unlikelyToBeNull(detailModel.currentGenre.value).id) { - adapter.highlightSong(song, binding.detailRecycler) + detailAdapter.highlightSong(song, binding.detailRecycler) } else { // Clear the ViewHolders if the mode isn't ALL_SONGS - adapter.highlightSong(null, binding.detailRecycler) + detailAdapter.highlightSong(null, binding.detailRecycler) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index d66e74de9..330477811 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -28,10 +28,8 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator -import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeBinding -import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.home.list.AlbumListFragment import org.oxycblt.auxio.home.list.ArtistListFragment import org.oxycblt.auxio.home.list.GenreListFragment @@ -45,6 +43,8 @@ import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode +import org.oxycblt.auxio.ui.MainNavigationAction +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE @@ -62,7 +62,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull */ class HomeFragment : ViewBindingFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() - private val detailModel: DetailViewModel by activityViewModels() + private val navModel: NavigationViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels() @@ -108,7 +108,7 @@ class HomeFragment : ViewBindingFragment() { homeModel.recreateTabs.observe(viewLifecycleOwner, ::handleRecreateTabs) musicModel.loaderResponse.observe(viewLifecycleOwner, ::handleLoaderResponse) - detailModel.navToItem.observe(viewLifecycleOwner, ::handleNavigation) + navModel.exploreNavigationItem.observe(viewLifecycleOwner, ::handleNavigation) } private fun onMenuClick(item: MenuItem) { @@ -119,19 +119,15 @@ class HomeFragment : ViewBindingFragment() { } R.id.action_settings -> { logD("Navigating to settings") - parentFragment - ?.parentFragment - ?.findNavController() - ?.navigate(MainFragmentDirections.actionShowSettings()) + navModel.mainNavigateTo(MainNavigationAction.SETTINGS) } R.id.action_about -> { logD("Navigating to about") - parentFragment - ?.parentFragment - ?.findNavController() - ?.navigate(MainFragmentDirections.actionShowAbout()) + navModel.mainNavigateTo(MainNavigationAction.ABOUT) + } + R.id.submenu_sorting -> { + // Junk click event when opening the menu } - R.id.submenu_sorting -> {} R.id.option_sort_asc -> { item.isChecked = !item.isChecked homeModel.updateCurrentSort( diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index 0c59c0ef5..964ba6ee6 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -18,11 +18,10 @@ package org.oxycblt.auxio.home.list import android.view.View -import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R -import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.ui.AlbumViewHolder import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Item @@ -70,8 +69,8 @@ class AlbumListFragment : HomeListFragment() { } override fun onItemClick(item: Item) { - check(item is Album) - findNavController().navigate(HomeFragmentDirections.actionShowAlbum(item.id)) + check(item is Music) + navModel.exploreNavigateTo(item) } override fun onOpenMenu(item: Item, anchor: View) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index 7dba02641..8759991eb 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -18,11 +18,10 @@ package org.oxycblt.auxio.home.list import android.view.View -import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R -import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.ui.ArtistViewHolder import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuItemListener @@ -56,8 +55,8 @@ class ArtistListFragment : HomeListFragment() { .uppercase() override fun onItemClick(item: Item) { - check(item is Artist) - findNavController().navigate(HomeFragmentDirections.actionShowArtist(item.id)) + check(item is Music) + navModel.exploreNavigateTo(item) } override fun onOpenMenu(item: Item, anchor: View) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index a8922a631..d7a59454f 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -18,11 +18,10 @@ package org.oxycblt.auxio.home.list import android.view.View -import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R -import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.ui.GenreViewHolder import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuItemListener @@ -56,8 +55,8 @@ class GenreListFragment : HomeListFragment() { .uppercase() override fun onItemClick(item: Item) { - check(item is Genre) - findNavController().navigate(HomeFragmentDirections.actionShowGenre(item.id)) + check(item is Music) + navModel.exploreNavigateTo(item) } override fun onOpenMenu(item: Item, anchor: View) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt index f623a542b..a4f1c0da1 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt @@ -28,6 +28,7 @@ import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuItemListener +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment /** @@ -41,8 +42,9 @@ abstract class HomeListFragment : FastScrollRecyclerView.OnFastScrollListener { abstract fun setupRecycler(recycler: RecyclerView) - protected val homeModel: HomeViewModel by activityViewModels() protected val playbackModel: PlaybackViewModel by activityViewModels() + protected val navModel: NavigationViewModel by activityViewModels() + protected val homeModel: HomeViewModel by activityViewModels() override fun onCreateBinding(inflater: LayoutInflater) = FragmentHomeListBinding.inflate(inflater) diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt index 93f55bbe4..3108bcd4b 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt @@ -54,7 +54,7 @@ sealed class Tab(open val mode: DisplayMode) { const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100 /** - * Maps between the integer code in the tab sequence and the actual [DisplayMode] instance + * Maps between the integer code in the tab sequence and the actual [DisplayMode] instance. */ private val MODE_TABLE = arrayOf( diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index 39b196397..52d7857c2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -27,8 +27,8 @@ import com.google.android.material.color.MaterialColors import org.oxycblt.auxio.R import org.oxycblt.auxio.coil.bindAlbumCover import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding -import org.oxycblt.auxio.detail.DetailViewModel -import org.oxycblt.auxio.ui.BottomSheetLayout +import org.oxycblt.auxio.ui.MainNavigationAction +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.textSafe class PlaybackBarFragment : ViewBindingFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() - private val detailModel: DetailViewModel by activityViewModels() + private val navModel: NavigationViewModel by activityViewModels() override fun onCreateBinding(inflater: LayoutInflater) = FragmentPlaybackBarBinding.inflate(inflater) @@ -46,14 +46,10 @@ class PlaybackBarFragment : ViewBindingFragment() { savedInstanceState: Bundle? ) { binding.root.apply { - setOnClickListener { - // This is a dumb and fragile hack but this fragment isn't part of the navigation - // stack so we can't really do much - (requireView().parent.parent.parent as BottomSheetLayout).expand() - } + setOnClickListener { navModel.mainNavigateTo(MainNavigationAction.EXPAND) } setOnLongClickListener { - playbackModel.song.value?.let { song -> detailModel.navToItem(song) } + playbackModel.song.value?.let(navModel::exploreNavigateTo) true } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 38f241c41..8cef08fc0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -23,11 +23,9 @@ import android.view.MenuItem import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.navigation.fragment.findNavController import com.google.android.material.color.MaterialColors import com.google.android.material.slider.Slider import kotlin.math.max -import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.coil.bindAlbumCover import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding @@ -36,7 +34,8 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toDuration import org.oxycblt.auxio.playback.state.LoopMode -import org.oxycblt.auxio.ui.BottomSheetLayout +import org.oxycblt.auxio.ui.MainNavigationAction +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.logD @@ -56,6 +55,7 @@ class PlaybackPanelFragment : Slider.OnChangeListener, Slider.OnSliderTouchListener { private val playbackModel: PlaybackViewModel by activityViewModels() + private val navModel: NavigationViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() override fun onCreateBinding(inflater: LayoutInflater) = @@ -75,11 +75,11 @@ class PlaybackPanelFragment : val queueItem: MenuItem binding.playbackToolbar.apply { - setNavigationOnClickListener { navigateUp() } + setNavigationOnClickListener { navModel.mainNavigateTo(MainNavigationAction.COLLAPSE) } setOnMenuItemClickListener { item -> if (item.itemId == R.id.action_queue) { - findNavController().navigate(MainFragmentDirections.actionShowQueue()) + navModel.mainNavigateTo(MainNavigationAction.QUEUE) true } else { false @@ -92,15 +92,15 @@ class PlaybackPanelFragment : binding.playbackSong.apply { // Make marquee of the song title work isSelected = true - setOnClickListener { playbackModel.song.value?.let { detailModel.navToItem(it) } } + setOnClickListener { playbackModel.song.value?.let(navModel::exploreNavigateTo) } } binding.playbackArtist.setOnClickListener { - playbackModel.song.value?.let { detailModel.navToItem(it.album.artist) } + playbackModel.song.value?.let { navModel.exploreNavigateTo(it.album.artist) } } binding.playbackAlbum.setOnClickListener { - playbackModel.song.value?.let { detailModel.navToItem(it.album) } + playbackModel.song.value?.let { navModel.exploreNavigateTo(it.album) } } binding.playbackSeekBar.apply { @@ -141,12 +141,6 @@ class PlaybackPanelFragment : queueItem.isEnabled = nextUp.isNotEmpty() } - detailModel.navToItem.observe(viewLifecycleOwner) { item -> - if (item != null) { - navigateUp() - } - } - logD("Fragment Created") } @@ -218,10 +212,4 @@ class PlaybackPanelFragment : private fun updateShuffle(isShuffling: Boolean) { requireBinding().playbackShuffle.isActivated = isShuffling } - - private fun navigateUp() { - // This is a dumb and fragile hack but this fragment isn't part of the navigation stack - // so we can't really do much - (requireView().parent.parent.parent as BottomSheetLayout).collapse() - } } 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 755da5b26..df7c9fb0e 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -30,7 +30,6 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentSearchBinding -import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -41,11 +40,11 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.Header import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuItemListener +import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.getSystemServiceSafe -import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.requireAttached /** @@ -56,7 +55,7 @@ class SearchFragment : ViewBindingFragment(), MenuItemLis // SearchViewModel is only scoped to this Fragment private val searchModel: SearchViewModel by viewModels() private val playbackModel: PlaybackViewModel by activityViewModels() - private val detailModel: DetailViewModel by activityViewModels() + private val navModel: NavigationViewModel by activityViewModels() private val searchAdapter = SearchAdapter(this) private var imm: InputMethodManager? = null @@ -109,11 +108,7 @@ class SearchFragment : ViewBindingFragment(), MenuItemLis // --- VIEWMODEL SETUP --- searchModel.searchResults.observe(viewLifecycleOwner, ::updateResults) - - detailModel.navToItem.observe(viewLifecycleOwner) { item -> - handleNavigation(item) - requireImm().hide() - } + navModel.exploreNavigationItem.observe(viewLifecycleOwner, ::handleNavigation) } override fun onResume() { @@ -128,25 +123,9 @@ class SearchFragment : ViewBindingFragment(), MenuItemLis } override fun onItemClick(item: Item) { - if (item is Song) { - playbackModel.playSong(item) - return - } - - if (item is MusicParent && !searchModel.isNavigating) { - searchModel.setNavigating(true) - - logD("Navigating to the detail fragment for ${item.rawName}") - - findNavController() - .navigate( - when (item) { - is Genre -> SearchFragmentDirections.actionShowGenre(item.id) - is Artist -> SearchFragmentDirections.actionShowArtist(item.id) - is Album -> SearchFragmentDirections.actionShowAlbum(item.id) - }) - - requireImm().hide() + when (item) { + is Song -> playbackModel.playSong(item) + is MusicParent -> navModel.exploreNavigateTo(item) } } @@ -178,8 +157,11 @@ class SearchFragment : ViewBindingFragment(), MenuItemLis is Song -> SearchFragmentDirections.actionShowAlbum(item.album.id) is Album -> SearchFragmentDirections.actionShowAlbum(item.id) is Artist -> SearchFragmentDirections.actionShowArtist(item.id) + is Genre -> SearchFragmentDirections.actionShowGenre(item.id) else -> return }) + + requireImm().hide() } private fun requireImm(): InputMethodManager { 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 b83051c82..c220b7b4a 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -47,8 +47,6 @@ class SearchViewModel : ViewModel() { /** Current search results from the last [search] call. */ val searchResults: LiveData> get() = mSearchResults - val isNavigating: Boolean - get() = mIsNavigating val filterMode: DisplayMode? get() = mFilterMode diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt index 510e894b7..1ccc025bc 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -26,7 +26,6 @@ import androidx.core.view.children import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import org.oxycblt.auxio.R -import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -70,8 +69,8 @@ class ActionMenu( private val context = activity.applicationContext // Get ViewModels using the activity as the store owner - private val detailModel: DetailViewModel by lazy { - ViewModelProvider(activity)[DetailViewModel::class.java] + private val navModel: NavigationViewModel by lazy { + ViewModelProvider(activity)[NavigationViewModel::class.java] } private val playbackModel: PlaybackViewModel by lazy { @@ -172,14 +171,14 @@ class ActionMenu( } R.id.action_go_album -> { if (data is Song) { - detailModel.navToItem(data.album) + navModel.exploreNavigateTo(data.album) } } R.id.action_go_artist -> { if (data is Song) { - detailModel.navToItem(data.album.artist) + navModel.exploreNavigateTo(data.album.artist) } else if (data is Album) { - detailModel.navToItem(data.artist) + navModel.exploreNavigateTo(data.artist) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt new file mode 100644 index 000000000..d8a0288f3 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.ui + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.oxycblt.auxio.music.Music + +/** + * A ViewModel that handles complicated navigation situations. + */ +class NavigationViewModel : ViewModel() { + private val mMainNavigationAction = MutableLiveData() + /** Flag for main fragment navigation. Intended for MainFragment use only. */ + val mainNavigationAction: LiveData + get() = mMainNavigationAction + + private val mExploreNavigationItem = MutableLiveData() + /** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */ + val exploreNavigationItem: LiveData + get() = mExploreNavigationItem + + /** + * Notify MainFragment to navigate to the location outlined in [MainNavigationAction]. + */ + fun mainNavigateTo(action: MainNavigationAction) { + if (mMainNavigationAction.value != null) return + mMainNavigationAction.value = action + } + + /** Mark that the main navigation process is done. */ + fun finishMainNavigation() { + mMainNavigationAction.value = null + } + + /** Navigate to an item's detail menu, whether a song/album/artist */ + fun exploreNavigateTo(item: Music) { + if (mExploreNavigationItem.value != null) return + mExploreNavigationItem.value = item + } + + /** Mark that the item navigation process is done. */ + fun finishExploreNavigation() { + mExploreNavigationItem.value = null + } +} + +/** + * Represents the navigation options for the Main Fragment, which tends to be multiple layers above + * normal fragments. This can be passed to [NavigationViewModel.mainNavigateTo] in order to + * facilitate navigation without stupid fragment hacks. + */ +enum class MainNavigationAction { + /** Expand the playback panel. */ + EXPAND, + /** Collapse the playback panel. */ + COLLAPSE, + /** Go to settings. */ + SETTINGS, + /** Go to the about page. */ + ABOUT, + /** Go to the queue. */ + QUEUE +} diff --git a/app/src/main/res/layout-land/fragment_playback_panel.xml b/app/src/main/res/layout-land/fragment_playback_panel.xml index 9c20dfbb5..2c8590936 100644 --- a/app/src/main/res/layout-land/fragment_playback_panel.xml +++ b/app/src/main/res/layout-land/fragment_playback_panel.xml @@ -94,6 +94,7 @@ app:thumbRadius="@dimen/slider_thumb_radius" /> - diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 0d7d1a1d1..d54884ab3 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -6,15 +6,18 @@ android:layout_height="match_parent"> - @@ -32,6 +33,7 @@ @@ -25,6 +26,7 @@ app:hintEnabled="false"> diff --git a/app/src/main/res/layout/item_accent.xml b/app/src/main/res/layout/item_accent.xml index d47d7e5df..212ca04d5 100644 --- a/app/src/main/res/layout/item_accent.xml +++ b/app/src/main/res/layout/item_accent.xml @@ -8,6 +8,7 @@ android:theme="@style/ThemeOverlay.Accent"> diff --git a/app/src/main/res/layout/item_sort_header.xml b/app/src/main/res/layout/item_sort_header.xml index 15b38b3f1..6983534ee 100644 --- a/app/src/main/res/layout/item_sort_header.xml +++ b/app/src/main/res/layout/item_sort_header.xml @@ -7,6 +7,7 @@ android:layout_height="wrap_content">