ui: flatten nav graph

Flatten the navigation graph into a single "main" graph that links
home to both explore and preference fragments.

***This massively breaks the app in it's current state***. Further
changes and refactors are needed to get this back to working.
This commit is contained in:
Alexander Capehart 2023-06-27 17:30:11 -06:00
parent 3ddf0347ea
commit 07e9ca8ef6
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
35 changed files with 974 additions and 887 deletions

View file

@ -31,7 +31,6 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import com.google.android.material.R as MR import com.google.android.material.R as MR
import com.google.android.material.bottomsheet.BackportBottomSheetBehavior import com.google.android.material.bottomsheet.BackportBottomSheetBehavior
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
@ -43,23 +42,18 @@ import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.MainNavigationAction import org.oxycblt.auxio.playback.Panel
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior import org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.coordinatorLayoutBehavior import org.oxycblt.auxio.util.coordinatorLayoutBehavior
import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getDimen import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.systemBarInsetsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
@ -76,8 +70,6 @@ class MainFragment :
ViewBindingFragment<FragmentMainBinding>(), ViewBindingFragment<FragmentMainBinding>(),
ViewTreeObserver.OnPreDrawListener, ViewTreeObserver.OnPreDrawListener,
NavController.OnDestinationChangedListener { NavController.OnDestinationChangedListener {
private val navModel: NavigationViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val selectionModel: SelectionViewModel by activityViewModels() private val selectionModel: SelectionViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
@ -148,11 +140,7 @@ class MainFragment :
// In portrait mode, set up click listeners on the stacked sheets. // In portrait mode, set up click listeners on the stacked sheets.
logD("Configuring stacked bottom sheets") logD("Configuring stacked bottom sheets")
unlikelyToBeNull(binding.queueHandleWrapper).setOnClickListener { unlikelyToBeNull(binding.queueHandleWrapper).setOnClickListener {
if (playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_EXPANDED && playbackModel.openQueue()
queueSheetBehavior.targetState == BackportBottomSheetBehavior.STATE_COLLAPSED) {
// Playback sheet is expanded and queue sheet is collapsed, we can expand it.
queueSheetBehavior.state = BackportBottomSheetBehavior.STATE_EXPANDED
}
} }
} else { } else {
// Dual-pane mode, manually style the static queue sheet. // Dual-pane mode, manually style the static queue sheet.
@ -175,16 +163,8 @@ class MainFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collectImmediately(detailModel.editedPlaylist, detailBackCallback::invalidateEnabled) collectImmediately(detailModel.editedPlaylist, detailBackCallback::invalidateEnabled)
collectImmediately(selectionModel.selected, selectionBackCallback::invalidateEnabled) collectImmediately(selectionModel.selected, selectionBackCallback::invalidateEnabled)
collect(musicModel.newPlaylistSongs.flow, ::handleNewPlaylist)
collect(musicModel.playlistToRename.flow, ::handleRenamePlaylist)
collect(musicModel.playlistToDelete.flow, ::handleDeletePlaylist)
collect(musicModel.songsToAdd.flow, ::handleAddToPlaylist)
collect(navModel.mainNavigationAction.flow, ::handleMainNavigation)
collect(navModel.exploreNavigationItem.flow, ::handleExploreNavigation)
collect(navModel.exploreArtistNavigationItem.flow, ::handleArtistNavigationPicker)
collectImmediately(playbackModel.song, ::updateSong) collectImmediately(playbackModel.song, ::updateSong)
collect(playbackModel.artistPickerSong.flow, ::handlePlaybackArtistPicker) collectImmediately(playbackModel.openPanel.flow, ::handlePanel)
collect(playbackModel.genrePickerSong.flow, ::handlePlaybackGenrePicker)
} }
override fun onStart() { override fun onStart() {
@ -322,33 +302,6 @@ class MainFragment :
selectionModel.drop() selectionModel.drop()
} }
private fun handleMainNavigation(action: MainNavigationAction?) {
if (action != null) {
when (action) {
is MainNavigationAction.OpenPlaybackPanel -> tryOpenPlaybackPanel()
is MainNavigationAction.ClosePlaybackPanel -> tryClosePlaybackPanel()
is MainNavigationAction.Directions ->
findNavController().navigateSafe(action.directions)
}
navModel.mainNavigationAction.consume()
}
}
private fun handleExploreNavigation(item: Music?) {
if (item != null) {
tryClosePlaybackPanel()
}
}
private fun handleArtistNavigationPicker(item: Music?) {
if (item != null) {
navModel.mainNavigateTo(
MainNavigationAction.Directions(
MainFragmentDirections.actionPickNavigationArtist(item.uid)))
navModel.exploreArtistNavigationItem.consume()
}
}
private fun updateSong(song: Song?) { private fun updateSong(song: Song?) {
if (song != null) { if (song != null) {
tryShowSheets() tryShowSheets()
@ -357,56 +310,15 @@ class MainFragment :
} }
} }
private fun handleNewPlaylist(songs: List<Song>?) { private fun handlePanel(panel: Panel?) {
if (songs != null) { if (panel == null) return
findNavController() logD("Trying to update panel to $panel")
.navigateSafe( when (panel) {
MainFragmentDirections.actionNewPlaylist(songs.map { it.uid }.toTypedArray())) is Panel.Main -> tryClosePlaybackPanel()
musicModel.newPlaylistSongs.consume() is Panel.Playback -> tryOpenPlaybackPanel()
} is Panel.Queue -> tryOpenQueuePanel()
}
private fun handleRenamePlaylist(playlist: Playlist?) {
if (playlist != null) {
findNavController()
.navigateSafe(MainFragmentDirections.actionRenamePlaylist(playlist.uid))
musicModel.playlistToRename.consume()
}
}
private fun handleDeletePlaylist(playlist: Playlist?) {
if (playlist != null) {
findNavController()
.navigateSafe(MainFragmentDirections.actionDeletePlaylist(playlist.uid))
musicModel.playlistToDelete.consume()
}
}
private fun handleAddToPlaylist(songs: List<Song>?) {
if (songs != null) {
findNavController()
.navigateSafe(
MainFragmentDirections.actionAddToPlaylist(songs.map { it.uid }.toTypedArray()))
musicModel.songsToAdd.consume()
}
}
private fun handlePlaybackArtistPicker(song: Song?) {
if (song != null) {
navModel.mainNavigateTo(
MainNavigationAction.Directions(
MainFragmentDirections.actionPickPlaybackArtist(song.uid)))
playbackModel.artistPickerSong.consume()
}
}
private fun handlePlaybackGenrePicker(song: Song?) {
if (song != null) {
navModel.mainNavigateTo(
MainNavigationAction.Directions(
MainFragmentDirections.actionPickPlaybackGenre(song.uid)))
playbackModel.genrePickerSong.consume()
} }
playbackModel.openPanel.consume()
} }
private fun tryOpenPlaybackPanel() { private fun tryOpenPlaybackPanel() {
@ -446,6 +358,19 @@ class MainFragment :
} }
} }
private fun tryOpenQueuePanel() {
val binding = requireBinding()
val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
val queueSheetBehavior =
(binding.queueSheet.coordinatorLayoutBehavior ?: return) as QueueBottomSheetBehavior
if (playbackSheetBehavior.state == BackportBottomSheetBehavior.STATE_EXPANDED &&
queueSheetBehavior.targetState == BackportBottomSheetBehavior.STATE_COLLAPSED) {
// Playback sheet is expanded and queue sheet is collapsed, we can expand it.
queueSheetBehavior.state = BackportBottomSheetBehavior.STATE_EXPANDED
}
}
private fun tryShowSheets() { private fun tryShowSheets() {
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =

View file

@ -42,14 +42,12 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
@ -72,11 +70,10 @@ class AlbumDetailFragment :
ListFragment<Song, FragmentDetailBinding>(), ListFragment<Song, FragmentDetailBinding>(),
AlbumDetailHeaderAdapter.Listener, AlbumDetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Song> { DetailListAdapter.Listener<Song> {
private val detailModel: DetailViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what album to display is initially within the navigation arguments // Information about what album to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an album. // as a UID, as that is the only safe way to parcel an album.
private val args: AlbumDetailFragmentArgs by navArgs() private val args: AlbumDetailFragmentArgs by navArgs()
@ -125,10 +122,12 @@ class AlbumDetailFragment :
detailModel.setAlbum(args.albumUid) detailModel.setAlbum(args.albumUid)
collectImmediately(detailModel.currentAlbum, ::updateAlbum) collectImmediately(detailModel.currentAlbum, ::updateAlbum)
collectImmediately(detailModel.albumList, ::updateList) collectImmediately(detailModel.albumList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collectImmediately(selectionModel.selected, ::updateSelection) collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
} }
override fun onDestroyBinding(binding: FragmentDetailBinding) { override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -221,7 +220,7 @@ class AlbumDetailFragment :
} }
override fun onNavigateToParentArtist() { override fun onNavigateToParentArtist() {
navModel.exploreNavigateToParentArtist(unlikelyToBeNull(detailModel.currentAlbum.value)) detailModel.showAlbum(unlikelyToBeNull(detailModel.currentAlbum.value))
} }
private fun updateAlbum(album: Album?) { private fun updateAlbum(album: Album?) {
@ -234,53 +233,88 @@ class AlbumDetailFragment :
albumHeaderAdapter.setParent(album) albumHeaderAdapter.setParent(album)
} }
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updateList(list: List<Item>) {
albumListAdapter.setPlaying( albumListAdapter.update(list, detailModel.albumInstructions.consume())
song.takeIf { parent == detailModel.currentAlbum.value }, isPlaying)
} }
private fun handleNavigation(item: Music?) { private fun handleShow(show: Show?) {
val binding = requireBinding() val binding = requireBinding()
when (item) { when (show) {
is Show.SongDetails -> {
logD("Navigating to ${show.song}")
findNavController()
.navigateSafe(AlbumDetailFragmentDirections.showSong(show.song.uid))
}
// Songs should be scrolled to if the album matches, or a new detail // Songs should be scrolled to if the album matches, or a new detail
// fragment should be launched otherwise. // fragment should be launched otherwise.
is Song -> { is Show.SongAlbumDetails -> {
if (unlikelyToBeNull(detailModel.currentAlbum.value) == item.album) { if (unlikelyToBeNull(detailModel.currentAlbum.value) == show.song.album) {
logD("Navigating to a song in this album") logD("Navigating to a ${show.song} in this album")
scrollToAlbumSong(item) scrollToAlbumSong(show.song)
navModel.exploreNavigationItem.consume() detailModel.toShow.consume()
} else { } else {
logD("Navigating to another album") logD("Navigating to the album of ${show.song}")
findNavController() findNavController()
.navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.album.uid)) .navigateSafe(AlbumDetailFragmentDirections.showAlbum(show.song.album.uid))
} }
} }
// If the album matches, no need to do anything. Otherwise launch a new // If the album matches, no need to do anything. Otherwise launch a new
// detail fragment. // detail fragment.
is Album -> { is Show.AlbumDetails -> {
if (unlikelyToBeNull(detailModel.currentAlbum.value) == item) { if (unlikelyToBeNull(detailModel.currentAlbum.value) == show.album) {
logD("Navigating to the top of this album") logD("Navigating to the top of this album")
binding.detailRecycler.scrollToPosition(0) binding.detailRecycler.scrollToPosition(0)
navModel.exploreNavigationItem.consume() detailModel.toShow.consume()
} else { } else {
logD("Navigating to another album") logD("Navigating to ${show.album}")
findNavController() findNavController()
.navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.uid)) .navigateSafe(AlbumDetailFragmentDirections.showAlbum(show.album.uid))
} }
} }
// Always launch a new ArtistDetailFragment. // Always launch a new ArtistDetailFragment.
is Artist -> { is Show.ArtistDetails -> {
logD("Navigating to another artist") logD("Navigating to ${show.artist}")
findNavController() findNavController()
.navigateSafe(AlbumDetailFragmentDirections.actionShowArtist(item.uid)) .navigateSafe(AlbumDetailFragmentDirections.showArtist(show.artist.uid))
}
is Show.SongArtistDetails -> {
logD("Navigating to artist choices for ${show.song}")
findNavController()
.navigateSafe(AlbumDetailFragmentDirections.showArtist(show.song.uid))
}
is Show.AlbumArtistDetails -> {
logD("Navigating to artist choices for ${show.album}")
findNavController()
.navigateSafe(AlbumDetailFragmentDirections.showArtist(show.album.uid))
}
is Show.GenreDetails,
is Show.PlaylistDetails -> {
error("Unexpected show command $show")
} }
null -> {} null -> {}
else -> error("Unexpected datatype: ${item::class.java}")
} }
} }
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
albumListAdapter.setPlaying(
song.takeIf { parent == detailModel.currentAlbum.value }, isPlaying)
}
private fun handlePlayFromArtist(song: Song?) {
if (song == null) return
logD("Launching play from artist dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromArtist(song.uid))
}
private fun handlePlayFromGenre(song: Song?) {
if (song == null) return
logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
}
private fun scrollToAlbumSong(song: Song) { private fun scrollToAlbumSong(song: Song) {
// Calculate where the item for the currently played song is // Calculate where the item for the currently played song is
val pos = detailModel.albumList.value.indexOf(song) val pos = detailModel.albumList.value.indexOf(song)
@ -319,10 +353,6 @@ class AlbumDetailFragment :
} }
} }
private fun updateList(list: List<Item>) {
albumListAdapter.update(list, detailModel.albumInstructions.consume())
}
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {
albumListAdapter.setSelected(selected.toSet()) albumListAdapter.setSelected(selected.toSet())

View file

@ -47,7 +47,6 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -69,11 +68,10 @@ class ArtistDetailFragment :
ListFragment<Music, FragmentDetailBinding>(), ListFragment<Music, FragmentDetailBinding>(),
DetailHeaderAdapter.Listener, DetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Music> { DetailListAdapter.Listener<Music> {
private val detailModel: DetailViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what artist to display is initially within the navigation arguments // Information about what artist to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an artist. // as a UID, as that is the only safe way to parcel an artist.
private val args: ArtistDetailFragmentArgs by navArgs() private val args: ArtistDetailFragmentArgs by navArgs()
@ -125,9 +123,11 @@ class ArtistDetailFragment :
detailModel.setArtist(args.artistUid) detailModel.setArtist(args.artistUid)
collectImmediately(detailModel.currentArtist, ::updateArtist) collectImmediately(detailModel.currentArtist, ::updateArtist)
collectImmediately(detailModel.artistList, ::updateList) collectImmediately(detailModel.artistList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
} }
@ -174,7 +174,7 @@ class ArtistDetailFragment :
override fun onRealClick(item: Music) { override fun onRealClick(item: Music) {
when (item) { when (item) {
is Album -> navModel.exploreNavigateTo(item) is Album -> detailModel.showAlbum(item)
is Song -> { is Song -> {
val playbackMode = detailModel.playbackMode val playbackMode = detailModel.playbackMode
if (playbackMode != null) { if (playbackMode != null) {
@ -257,6 +257,57 @@ class ArtistDetailFragment :
artistHeaderAdapter.setParent(artist) artistHeaderAdapter.setParent(artist)
} }
private fun updateList(list: List<Item>) {
artistListAdapter.update(list, detailModel.artistInstructions.consume())
}
private fun handleShow(show: Show?) {
val binding = requireBinding()
when (show) {
is Show.SongDetails -> {
logD("Navigating to ${show.song}")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.showSong(show.song.uid))
}
// Songs should be shown in their album, not in their artist.
is Show.SongAlbumDetails -> {
logD("Navigating to the album of ${show.song}")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.showAlbum(show.song.album.uid))
}
// Launch a new detail view for an album, even if it is part of
// this artist.
is Show.AlbumDetails -> {
logD("Navigating to ${show.album}")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.showAlbum(show.album.uid))
}
// If the artist that should be navigated to is this artist, then
// scroll back to the top. Otherwise launch a new detail view.
is Show.ArtistDetails -> {
if (show.artist == detailModel.currentArtist.value) {
logD("Navigating to the top of this artist")
binding.detailRecycler.scrollToPosition(0)
detailModel.toShow.consume()
} else {
logD("Navigating to ${show.artist}")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.showArtist(show.artist.uid))
}
}
is Show.SongArtistDetails,
is Show.AlbumArtistDetails,
is Show.GenreDetails,
is Show.PlaylistDetails -> {
error("Unexpected show command $show")
}
null -> {}
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
val currentArtist = unlikelyToBeNull(detailModel.currentArtist.value) val currentArtist = unlikelyToBeNull(detailModel.currentArtist.value)
val playingItem = val playingItem =
@ -272,43 +323,16 @@ class ArtistDetailFragment :
artistListAdapter.setPlaying(playingItem, isPlaying) artistListAdapter.setPlaying(playingItem, isPlaying)
} }
private fun handleNavigation(item: Music?) { private fun handlePlayFromArtist(song: Song?) {
val binding = requireBinding() if (song == null) return
logD("Launching play from artist dialog for $song")
when (item) { findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromArtist(song.uid))
// Songs should be shown in their album, not in their artist.
is Song -> {
logD("Navigating to another album")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.actionShowAlbum(item.album.uid))
}
// Launch a new detail view for an album, even if it is part of
// this artist.
is Album -> {
logD("Navigating to another album")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.actionShowAlbum(item.uid))
}
// If the artist that should be navigated to is this artist, then
// scroll back to the top. Otherwise launch a new detail view.
is Artist -> {
if (item.uid == detailModel.currentArtist.value?.uid) {
logD("Navigating to the top of this artist")
binding.detailRecycler.scrollToPosition(0)
navModel.exploreNavigationItem.consume()
} else {
logD("Navigating to another artist")
findNavController()
.navigateSafe(ArtistDetailFragmentDirections.actionShowArtist(item.uid))
}
}
null -> {}
else -> error("Unexpected datatype: ${item::class.java}")
}
} }
private fun updateList(list: List<Item>) { private fun handlePlayFromGenre(song: Song?) {
artistListAdapter.update(list, detailModel.artistInstructions.consume()) if (song == null) return
logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -70,6 +70,10 @@ constructor(
private val musicSettings: MusicSettings, private val musicSettings: MusicSettings,
private val playbackSettings: PlaybackSettings private val playbackSettings: PlaybackSettings
) : ViewModel(), MusicRepository.UpdateListener { ) : ViewModel(), MusicRepository.UpdateListener {
private val _toShow = MutableEvent<Show>()
val toShow: Event<Show>
get() = _toShow
// --- SONG --- // --- SONG ---
private var currentSongJob: Job? = null private var currentSongJob: Job? = null
@ -237,6 +241,43 @@ constructor(
} }
} }
fun showSong(song: Song) = showImpl(Show.SongDetails(song))
fun showAlbum(song: Song) = showImpl(Show.SongAlbumDetails(song))
fun showAlbum(album: Album) = showImpl(Show.AlbumDetails(album))
fun showArtist(song: Song) =
showImpl(
if (song.artists.size > 1) {
Show.SongArtistDetails(song)
} else {
Show.ArtistDetails(song.artists.first())
})
fun showArtist(album: Album) =
showImpl(
if (album.artists.size > 1) {
Show.AlbumArtistDetails(album)
} else {
Show.ArtistDetails(album.artists.first())
})
fun showArtist(artist: Artist) = showImpl(Show.ArtistDetails(artist))
fun showGenre(genre: Genre) = showImpl(Show.GenreDetails(genre))
fun showPlaylist(playlist: Playlist) = showImpl(Show.PlaylistDetails(playlist))
private fun showImpl(show: Show) {
val existing = toShow.flow.value
if (existing != null) {
logD("Already have pending show command $existing, ignoring $show")
return
}
_toShow.put(show)
}
/** /**
* Set a new [currentSong] from it's [Music.UID]. [currentSong] and [songAudioProperties] will * Set a new [currentSong] from it's [Music.UID]. [currentSong] and [songAudioProperties] will
* be updated to align with the new [Song]. * be updated to align with the new [Song].
@ -582,3 +623,14 @@ constructor(
val GENRE_ARTIST_SORT = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING) val GENRE_ARTIST_SORT = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
} }
} }
sealed interface Show {
data class SongDetails(val song: Song) : Show
data class AlbumDetails(val album: Album) : Show
data class SongAlbumDetails(val song: Song) : Show
data class ArtistDetails(val artist: Artist) : Show
data class SongArtistDetails(val song: Song) : Show
data class AlbumArtistDetails(val album: Album) : Show
data class GenreDetails(val genre: Genre) : Show
data class PlaylistDetails(val playlist: Playlist) : Show
}

View file

@ -41,14 +41,12 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -70,11 +68,10 @@ class GenreDetailFragment :
ListFragment<Music, FragmentDetailBinding>(), ListFragment<Music, FragmentDetailBinding>(),
DetailHeaderAdapter.Listener, DetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Music> { DetailListAdapter.Listener<Music> {
private val detailModel: DetailViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what genre to display is initially within the navigation arguments // Information about what genre to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an genre. // as a UID, as that is the only safe way to parcel an genre.
private val args: GenreDetailFragmentArgs by navArgs() private val args: GenreDetailFragmentArgs by navArgs()
@ -124,9 +121,11 @@ class GenreDetailFragment :
detailModel.setGenre(args.genreUid) detailModel.setGenre(args.genreUid)
collectImmediately(detailModel.currentGenre, ::updatePlaylist) collectImmediately(detailModel.currentGenre, ::updatePlaylist)
collectImmediately(detailModel.genreList, ::updateList) collectImmediately(detailModel.genreList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
} }
@ -173,7 +172,7 @@ class GenreDetailFragment :
override fun onRealClick(item: Music) { override fun onRealClick(item: Music) {
when (item) { when (item) {
is Artist -> navModel.exploreNavigateTo(item) is Artist -> detailModel.showArtist(item)
is Song -> { is Song -> {
val playbackMode = detailModel.playbackMode val playbackMode = detailModel.playbackMode
if (playbackMode != null) { if (playbackMode != null) {
@ -242,6 +241,60 @@ class GenreDetailFragment :
genreHeaderAdapter.setParent(genre) genreHeaderAdapter.setParent(genre)
} }
private fun updateList(list: List<Item>) {
genreListAdapter.update(list, detailModel.genreInstructions.consume())
}
private fun handleShow(show: Show?) {
when (show) {
is Show.SongDetails -> {
logD("Navigating to ${show.song}")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.showSong(show.song.uid))
}
// Songs should be scrolled to if the album matches, or a new detail
// fragment should be launched otherwise.
is Show.SongAlbumDetails -> {
logD("Navigating to the album of ${show.song}")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.showAlbum(show.song.album.uid))
}
// If the album matches, no need to do anything. Otherwise launch a new
// detail fragment.
is Show.AlbumDetails -> {
logD("Navigating to ${show.album}")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.showAlbum(show.album.uid))
}
// Always launch a new ArtistDetailFragment.
is Show.ArtistDetails -> {
logD("Navigating to ${show.artist}")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.showArtist(show.artist.uid))
}
is Show.SongArtistDetails -> {
logD("Navigating to artist choices for ${show.song}")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.showArtist(show.song.uid))
}
is Show.AlbumArtistDetails -> {
logD("Navigating to artist choices for ${show.album}")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.showArtist(show.album.uid))
}
is Show.GenreDetails -> {
logD("Navigated to this genre")
}
is Show.PlaylistDetails -> {
error("Unexpected show command $show")
}
null -> {}
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
val currentGenre = unlikelyToBeNull(detailModel.currentGenre.value) val currentGenre = unlikelyToBeNull(detailModel.currentGenre.value)
val playingItem = val playingItem =
@ -257,32 +310,16 @@ class GenreDetailFragment :
genreListAdapter.setPlaying(playingItem, isPlaying) genreListAdapter.setPlaying(playingItem, isPlaying)
} }
private fun handleNavigation(item: Music?) { private fun handlePlayFromArtist(song: Song?) {
when (item) { if (song == null) return
is Song -> { logD("Launching play from artist dialog for $song")
logD("Navigating to another song") findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromArtist(song.uid))
findNavController()
.navigateSafe(GenreDetailFragmentDirections.actionShowAlbum(item.album.uid))
}
is Album -> {
logD("Navigating to another album")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.actionShowAlbum(item.uid))
}
is Artist -> {
logD("Navigating to another artist")
findNavController()
.navigateSafe(GenreDetailFragmentDirections.actionShowArtist(item.uid))
}
is Genre -> {
navModel.exploreNavigationItem.consume()
}
else -> {}
}
} }
private fun updateList(list: List<Item>) { private fun handlePlayFromGenre(song: Song?) {
genreListAdapter.update(list, detailModel.genreInstructions.consume()) if (song == null) return
logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -44,14 +44,11 @@ import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -74,11 +71,10 @@ class PlaylistDetailFragment :
DetailHeaderAdapter.Listener, DetailHeaderAdapter.Listener,
PlaylistDetailListAdapter.Listener, PlaylistDetailListAdapter.Listener,
NavController.OnDestinationChangedListener { NavController.OnDestinationChangedListener {
private val detailModel: DetailViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what playlist to display is initially within the navigation arguments // Information about what playlist to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an playlist. // as a UID, as that is the only safe way to parcel an playlist.
private val args: PlaylistDetailFragmentArgs by navArgs() private val args: PlaylistDetailFragmentArgs by navArgs()
@ -139,10 +135,12 @@ class PlaylistDetailFragment :
detailModel.setPlaylist(args.playlistUid) detailModel.setPlaylist(args.playlistUid)
collectImmediately(detailModel.currentPlaylist, ::updatePlaylist) collectImmediately(detailModel.currentPlaylist, ::updatePlaylist)
collectImmediately(detailModel.playlistList, ::updateList) collectImmediately(detailModel.playlistList, ::updateList)
collectImmediately(detailModel.editedPlaylist, ::updateEditedPlaylist) collectImmediately(detailModel.editedPlaylist, ::updateEditedList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
} }
@ -275,41 +273,11 @@ class PlaylistDetailFragment :
playlistHeaderAdapter.setParent(playlist) playlistHeaderAdapter.setParent(playlist)
} }
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
// Prefer songs that are playing from this playlist.
playlistListAdapter.setPlaying(
song.takeIf { parent == detailModel.currentPlaylist.value }, isPlaying)
}
private fun handleNavigation(item: Music?) {
when (item) {
is Song -> {
logD("Navigating to another song")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.actionShowAlbum(item.album.uid))
}
is Album -> {
logD("Navigating to another album")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.actionShowAlbum(item.uid))
}
is Artist -> {
logD("Navigating to another artist")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.actionShowArtist(item.uid))
}
is Playlist -> {
navModel.exploreNavigationItem.consume()
}
else -> {}
}
}
private fun updateList(list: List<Item>) { private fun updateList(list: List<Item>) {
playlistListAdapter.update(list, detailModel.playlistInstructions.consume()) playlistListAdapter.update(list, detailModel.playlistInstructions.consume())
} }
private fun updateEditedPlaylist(editedPlaylist: List<Song>?) { private fun updateEditedList(editedPlaylist: List<Song>?) {
playlistListAdapter.setEditing(editedPlaylist != null) playlistListAdapter.setEditing(editedPlaylist != null)
playlistHeaderAdapter.setEditedPlaylist(editedPlaylist) playlistHeaderAdapter.setEditedPlaylist(editedPlaylist)
selectionModel.drop() selectionModel.drop()
@ -324,6 +292,74 @@ class PlaylistDetailFragment :
updateMultiToolbar() updateMultiToolbar()
} }
private fun handleShow(show: Show?) {
when (show) {
is Show.SongDetails -> {
logD("Navigating to ${show.song}")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.showSong(show.song.uid))
}
// Songs should be scrolled to if the album matches, or a new detail
// fragment should be launched otherwise.
is Show.SongAlbumDetails -> {
logD("Navigating to the album of ${show.song}")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.song.album.uid))
}
// If the album matches, no need to do anything. Otherwise launch a new
// detail fragment.
is Show.AlbumDetails -> {
logD("Navigating to ${show.album}")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.showAlbum(show.album.uid))
}
// Always launch a new ArtistDetailFragment.
is Show.ArtistDetails -> {
logD("Navigating to ${show.artist}")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.artist.uid))
}
is Show.SongArtistDetails -> {
logD("Navigating to artist choices for ${show.song}")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.song.uid))
}
is Show.AlbumArtistDetails -> {
logD("Navigating to artist choices for ${show.album}")
findNavController()
.navigateSafe(PlaylistDetailFragmentDirections.showArtist(show.album.uid))
}
is Show.PlaylistDetails -> {
logD("Navigated to this playlist")
}
is Show.GenreDetails -> {
error("Unexpected show command $show")
}
null -> {}
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
// Prefer songs that are playing from this playlist.
playlistListAdapter.setPlaying(
song.takeIf { parent == detailModel.currentPlaylist.value }, isPlaying)
}
private fun handlePlayFromArtist(song: Song?) {
if (song == null) return
logD("Launching play from artist dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromArtist(song.uid))
}
private fun handlePlayFromGenre(song: Song?) {
if (song == null) return
logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
}
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {
playlistListAdapter.setSelected(selected.toSet()) playlistListAdapter.setSelected(selected.toSet())

View file

@ -70,6 +70,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
// DetailViewModel handles most initialization from the navigation argument. // DetailViewModel handles most initialization from the navigation argument.
detailModel.setSong(args.songUid) detailModel.setSong(args.songUid)
collectImmediately(detailModel.currentSong, detailModel.songAudioProperties, ::updateSong) collectImmediately(detailModel.currentSong, detailModel.songAudioProperties, ::updateSong)
collectImmediately(detailModel.toShow.flow, ::handleShow)
} }
private fun updateSong(song: Song?, info: AudioProperties?) { private fun updateSong(song: Song?, info: AudioProperties?) {
@ -125,6 +126,15 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
} }
} }
private fun handleShow(show: Show?) {
if (show == null) return
if (show is Show.SongDetails) {
logD("Navigated to this song")
} else {
error("Unexpected show command $show")
}
}
private fun <T : Music> T.zipName(context: Context): String { private fun <T : Music> T.zipName(context: Context): String {
val name = name val name = name
return if (name is Name.Known && name.sort != null) { return if (name is Name.Known && name.sort != null) {

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2023 Auxio Project * Copyright (c) 2023 Auxio Project
* ArtistNavigationChoiceAdapter.kt is part of Auxio. * ArtistShowChoice.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.navigation.picker package org.oxycblt.auxio.detail.picker
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -31,11 +31,11 @@ import org.oxycblt.auxio.util.inflater
/** /**
* A [FlexibleListAdapter] that displays a list of [Artist] navigation choices, for use with * A [FlexibleListAdapter] that displays a list of [Artist] navigation choices, for use with
* [NavigateToArtistDialog]. * [ShowArtistDialog].
* *
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
*/ */
class ArtistNavigationChoiceAdapter(private val listener: ClickableListListener<Artist>) : class ArtistShowChoice(private val listener: ClickableListListener<Artist>) :
FlexibleListAdapter<Artist, ArtistNavigationChoiceViewHolder>( FlexibleListAdapter<Artist, ArtistNavigationChoiceViewHolder>(
ArtistNavigationChoiceViewHolder.DIFF_CALLBACK) { ArtistNavigationChoiceViewHolder.DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
@ -48,7 +48,7 @@ class ArtistNavigationChoiceAdapter(private val listener: ClickableListListener<
/** /**
* A [DialogRecyclerView.ViewHolder] that displays a smaller variant of a typical [Artist] item, for * A [DialogRecyclerView.ViewHolder] that displays a smaller variant of a typical [Artist] item, for
* use [ArtistNavigationChoiceAdapter]. Use [from] to create an instance. * use [ArtistShowChoice]. Use [from] to create an instance.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.navigation.picker package org.oxycblt.auxio.detail.picker
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -28,7 +28,9 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/** /**
* A [ViewModel] that stores the current information required for navigation picker dialogs * A [ViewModel] that stores the current information required for navigation picker dialogs
@ -38,9 +40,9 @@ import org.oxycblt.auxio.util.logD
@HiltViewModel @HiltViewModel
class NavigationPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) : class NavigationPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
ViewModel(), MusicRepository.UpdateListener { ViewModel(), MusicRepository.UpdateListener {
private val _artistChoices = MutableStateFlow<ArtistNavigationChoices?>(null) private val _artistChoices = MutableStateFlow<ArtistShowChoices?>(null)
/** The current set of [Artist] choices to show in the picker, or null if to show nothing. */ /** The current set of [Artist] choices to show in the picker, or null if to show nothing. */
val artistChoices: StateFlow<ArtistNavigationChoices?> val artistChoices: StateFlow<ArtistShowChoices?>
get() = _artistChoices get() = _artistChoices
init { init {
@ -51,18 +53,7 @@ class NavigationPickerViewModel @Inject constructor(private val musicRepository:
if (!changes.deviceLibrary) return if (!changes.deviceLibrary) return
val deviceLibrary = musicRepository.deviceLibrary ?: return val deviceLibrary = musicRepository.deviceLibrary ?: return
// Need to sanitize different items depending on the current set of choices. // Need to sanitize different items depending on the current set of choices.
_artistChoices.value = _artistChoices.value = _artistChoices.value?.sanitize(deviceLibrary)
when (val choices = _artistChoices.value) {
is SongArtistNavigationChoices ->
deviceLibrary.findSong(choices.song.uid)?.let {
SongArtistNavigationChoices(it)
}
is AlbumArtistNavigationChoices ->
deviceLibrary.findAlbum(choices.album.uid)?.let {
AlbumArtistNavigationChoices(it)
}
else -> null
}
logD("Updated artist choices: ${_artistChoices.value}") logD("Updated artist choices: ${_artistChoices.value}")
} }
@ -83,14 +74,14 @@ class NavigationPickerViewModel @Inject constructor(private val musicRepository:
when (val music = musicRepository.find(itemUid)) { when (val music = musicRepository.find(itemUid)) {
is Song -> { is Song -> {
logD("Creating navigation choices for song") logD("Creating navigation choices for song")
SongArtistNavigationChoices(music) ArtistShowChoices.FromSong(music)
} }
is Album -> { is Album -> {
logD("Creating navigation choices for album") logD("Creating navigation choices for album")
AlbumArtistNavigationChoices(music) ArtistShowChoices.FromAlbum(music)
} }
else -> { else -> {
logD("Given song/album UID was invalid") logW("Given song/album UID was invalid")
null null
} }
} }
@ -102,20 +93,29 @@ class NavigationPickerViewModel @Inject constructor(private val musicRepository:
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed interface ArtistNavigationChoices { sealed interface ArtistShowChoices {
/** The UID of the item. */
val uid: Music.UID
/** The current [Artist] choices. */ /** The current [Artist] choices. */
val choices: List<Artist> val choices: List<Artist>
} /** Sanitize this instance with a [DeviceLibrary]. */
fun sanitize(newLibrary: DeviceLibrary): ArtistShowChoices?
/** Backing implementation of [ArtistNavigationChoices] that is based on a [Song]. */ /** Backing implementation of [ArtistShowChoices] that is based on a [Song]. */
private data class SongArtistNavigationChoices(val song: Song) : ArtistNavigationChoices { class FromSong(val song: Song) : ArtistShowChoices {
override val choices = song.artists override val uid = song.uid
} override val choices = song.artists
override fun sanitize(newLibrary: DeviceLibrary) =
newLibrary.findSong(uid)?.let { FromSong(it) }
}
/** /**
* Backing implementation of [ArtistNavigationChoices] that is based on an * Backing implementation of [ArtistShowChoices] that is based on an [AlbumArtistShowChoices].
* [AlbumArtistNavigationChoices]. */
*/ data class FromAlbum(val album: Album) : ArtistShowChoices {
private data class AlbumArtistNavigationChoices(val album: Album) : ArtistNavigationChoices { override val uid = album.uid
override val choices = album.artists override val choices = album.artists
override fun sanitize(newLibrary: DeviceLibrary) =
newLibrary.findAlbum(uid)?.let { FromAlbum(it) }
}
} }

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2022 Auxio Project * Copyright (c) 2022 Auxio Project
* NavigateToArtistDialog.kt is part of Auxio. * ShowArtistDialog.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.navigation.picker package org.oxycblt.auxio.detail.picker
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -29,27 +29,27 @@ import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicChoicesBinding import org.oxycblt.auxio.databinding.DialogMusicChoicesBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
/** /**
* A picker [ViewBindingDialogFragment] intended for when [Artist] navigation is ambiguous. * A picker [ViewBindingDialogFragment] intended for when the [Artist] to show is ambiguous.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class NavigateToArtistDialog : class ShowArtistDialog :
ViewBindingDialogFragment<DialogMusicChoicesBinding>(), ClickableListListener<Artist> { ViewBindingDialogFragment<DialogMusicChoicesBinding>(), ClickableListListener<Artist> {
private val navigationModel: NavigationViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val pickerModel: NavigationPickerViewModel by viewModels() private val pickerModel: NavigationPickerViewModel by viewModels()
// Information about what artists to show choices for is initially within the navigation // Information about what artists to show choices for is initially within the navigation
// arguments as UIDs, as that is the only safe way to parcel an artist. // arguments as UIDs, as that is the only safe way to parcel an artist.
private val args: NavigateToArtistDialogArgs by navArgs() private val args: ShowArtistDialogArgs by navArgs()
private val choiceAdapter = ArtistNavigationChoiceAdapter(this) private val choiceAdapter = ArtistShowChoice(this)
override fun onConfigDialog(builder: AlertDialog.Builder) { override fun onConfigDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.lbl_artists).setNegativeButton(R.string.lbl_cancel, null) builder.setTitle(R.string.lbl_artists).setNegativeButton(R.string.lbl_cancel, null)
@ -83,7 +83,7 @@ class NavigateToArtistDialog :
override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) { override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) {
// User made a choice, navigate to the artist. // User made a choice, navigate to the artist.
navigationModel.exploreNavigateTo(item) detailModel.showArtist(item)
findNavController().navigateUp() findNavController().navigateUp()
} }
} }

View file

@ -43,9 +43,10 @@ import dagger.hilt.android.AndroidEntryPoint
import java.lang.reflect.Field import java.lang.reflect.Field
import kotlin.math.abs import kotlin.math.abs
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeBinding import org.oxycblt.auxio.databinding.FragmentHomeBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.detail.Show
import org.oxycblt.auxio.home.list.AlbumListFragment import org.oxycblt.auxio.home.list.AlbumListFragment
import org.oxycblt.auxio.home.list.ArtistListFragment import org.oxycblt.auxio.home.list.ArtistListFragment
import org.oxycblt.auxio.home.list.GenreListFragment import org.oxycblt.auxio.home.list.GenreListFragment
@ -56,9 +57,6 @@ import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.selection.SelectionFragment import org.oxycblt.auxio.list.selection.SelectionFragment
import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.IndexingProgress import org.oxycblt.auxio.music.IndexingProgress
import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.IndexingState
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -67,10 +65,7 @@ import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.NoAudioPermissionException import org.oxycblt.auxio.music.NoAudioPermissionException
import org.oxycblt.auxio.music.NoMusicException import org.oxycblt.auxio.music.NoMusicException
import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.MainNavigationAction
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -94,7 +89,7 @@ class HomeFragment :
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -177,7 +172,7 @@ class HomeFragment :
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab)
collectImmediately(musicModel.indexingState, ::updateIndexerState) collectImmediately(musicModel.indexingState, ::updateIndexerState)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
} }
@ -218,19 +213,17 @@ class HomeFragment :
R.id.action_search -> { R.id.action_search -> {
logD("Navigating to search") logD("Navigating to search")
setupAxisTransitions(MaterialSharedAxis.Z) setupAxisTransitions(MaterialSharedAxis.Z)
findNavController().navigateSafe(HomeFragmentDirections.actionShowSearch()) findNavController().navigateSafe(HomeFragmentDirections.search())
true true
} }
R.id.action_settings -> { R.id.action_settings -> {
logD("Navigating to settings") logD("Navigating to preferences")
navModel.mainNavigateTo( findNavController().navigateSafe(HomeFragmentDirections.preferences())
MainNavigationAction.Directions(MainFragmentDirections.actionShowSettings()))
true true
} }
R.id.action_about -> { R.id.action_about -> {
logD("Navigating to about") logD("Navigating to about")
navModel.mainNavigateTo( findNavController().navigateSafe(HomeFragmentDirections.about())
MainNavigationAction.Directions(MainFragmentDirections.actionShowAbout()))
true true
} }
@ -500,19 +493,55 @@ class HomeFragment :
} }
} }
private fun handleNavigation(item: Music?) { private fun handleShow(show: Show?) {
val action = when (show) {
when (item) { is Show.SongDetails -> {
is Song -> HomeFragmentDirections.actionShowAlbum(item.album.uid) logD("Navigating to ${show.song}")
is Album -> HomeFragmentDirections.actionShowAlbum(item.uid) findNavController().navigateSafe(HomeFragmentDirections.showSong(show.song.uid))
is Artist -> HomeFragmentDirections.actionShowArtist(item.uid)
is Genre -> HomeFragmentDirections.actionShowGenre(item.uid)
is Playlist -> HomeFragmentDirections.actionShowPlaylist(item.uid)
null -> return
} }
setupAxisTransitions(MaterialSharedAxis.X) // Songs should be scrolled to if the album matches, or a new detail
findNavController().navigateSafe(action) // fragment should be launched otherwise.
is Show.SongAlbumDetails -> {
logD("Navigating to the album of ${show.song}")
setupAxisTransitions(MaterialSharedAxis.X)
findNavController()
.navigateSafe(HomeFragmentDirections.showAlbum(show.song.album.uid))
}
// If the album matches, no need to do anything. Otherwise launch a new
// detail fragment.
is Show.AlbumDetails -> {
logD("Navigating to ${show.album}")
setupAxisTransitions(MaterialSharedAxis.X)
findNavController().navigateSafe(HomeFragmentDirections.showAlbum(show.album.uid))
}
// Always launch a new ArtistDetailFragment.
is Show.ArtistDetails -> {
logD("Navigating to ${show.artist}")
setupAxisTransitions(MaterialSharedAxis.X)
findNavController().navigateSafe(HomeFragmentDirections.showArtist(show.artist.uid))
}
is Show.SongArtistDetails -> {
logD("Navigating to artist choices for ${show.song}")
findNavController().navigateSafe(HomeFragmentDirections.showArtist(show.song.uid))
}
is Show.AlbumArtistDetails -> {
logD("Navigating to artist choices for ${show.album}")
findNavController().navigateSafe(HomeFragmentDirections.showArtist(show.album.uid))
}
is Show.GenreDetails -> {
logD("Navigating to ${show.genre}")
findNavController().navigateSafe(HomeFragmentDirections.showGenre(show.genre.uid))
}
is Show.PlaylistDetails -> {
logD("Navigating to ${show.playlist}")
findNavController()
.navigateSafe(HomeFragmentDirections.showGenre(show.playlist.uid))
}
null -> {}
}
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -28,6 +28,7 @@ import dagger.hilt.android.AndroidEntryPoint
import java.util.Formatter import java.util.Formatter
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
@ -42,7 +43,6 @@ import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.playback.secsToMs
@ -59,7 +59,7 @@ class AlbumListFragment :
FastScrollRecyclerView.Listener, FastScrollRecyclerView.Listener,
FastScrollRecyclerView.PopupProvider { FastScrollRecyclerView.PopupProvider {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
@ -138,7 +138,7 @@ class AlbumListFragment :
} }
override fun onRealClick(item: Album) { override fun onRealClick(item: Album) {
navModel.exploreNavigateTo(item) detailModel.showAlbum(item)
} }
override fun onOpenMenu(item: Album, anchor: View) { override fun onOpenMenu(item: Album, anchor: View) {

View file

@ -26,6 +26,7 @@ import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
@ -40,7 +41,6 @@ import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -57,7 +57,7 @@ class ArtistListFragment :
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
@ -114,7 +114,7 @@ class ArtistListFragment :
} }
override fun onRealClick(item: Artist) { override fun onRealClick(item: Artist) {
navModel.exploreNavigateTo(item) detailModel.showArtist(item)
} }
override fun onOpenMenu(item: Artist, anchor: View) { override fun onOpenMenu(item: Artist, anchor: View) {

View file

@ -26,6 +26,7 @@ import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
@ -40,7 +41,6 @@ import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -56,7 +56,7 @@ class GenreListFragment :
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
@ -113,7 +113,7 @@ class GenreListFragment :
} }
override fun onRealClick(item: Genre) { override fun onRealClick(item: Genre) {
navModel.exploreNavigateTo(item) detailModel.showGenre(item)
} }
override fun onOpenMenu(item: Genre, anchor: View) { override fun onOpenMenu(item: Genre, anchor: View) {

View file

@ -25,6 +25,7 @@ import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
@ -39,7 +40,6 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -54,7 +54,7 @@ class PlaylistListFragment :
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
@ -111,7 +111,7 @@ class PlaylistListFragment :
} }
override fun onRealClick(item: Playlist) { override fun onRealClick(item: Playlist) {
navModel.exploreNavigateTo(item) detailModel.showPlaylist(item)
} }
override fun onOpenMenu(item: Playlist, anchor: View) { override fun onOpenMenu(item: Playlist, anchor: View) {

View file

@ -28,6 +28,7 @@ import dagger.hilt.android.AndroidEntryPoint
import java.util.Formatter import java.util.Formatter
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListFragment
@ -41,7 +42,6 @@ import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.playback.secsToMs
@ -58,7 +58,7 @@ class SongListFragment :
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener { FastScrollRecyclerView.Listener {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
override val navModel: NavigationViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()

View file

@ -24,8 +24,8 @@ import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuCompat import androidx.core.view.MenuCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.selection.SelectionFragment import org.oxycblt.auxio.list.selection.SelectionFragment
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
@ -33,8 +33,6 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.MainNavigationAction
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.share import org.oxycblt.auxio.util.share
@ -47,7 +45,7 @@ import org.oxycblt.auxio.util.showToast
*/ */
abstract class ListFragment<in T : Music, VB : ViewBinding> : abstract class ListFragment<in T : Music, VB : ViewBinding> :
SelectionFragment<VB>(), SelectableListListener<T> { SelectionFragment<VB>(), SelectableListListener<T> {
protected abstract val navModel: NavigationViewModel protected abstract val detailModel: DetailViewModel
private var currentMenu: PopupMenu? = null private var currentMenu: PopupMenu? = null
override fun onDestroyBinding(binding: VB) { override fun onDestroyBinding(binding: VB) {
@ -103,11 +101,11 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
true true
} }
R.id.action_go_artist -> { R.id.action_go_artist -> {
navModel.exploreNavigateToParentArtist(song) detailModel.showArtist(song)
true true
} }
R.id.action_go_album -> { R.id.action_go_album -> {
navModel.exploreNavigateTo(song.album) detailModel.showAlbum(song.album)
true true
} }
R.id.action_share -> { R.id.action_share -> {
@ -119,9 +117,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
true true
} }
R.id.action_song_detail -> { R.id.action_song_detail -> {
navModel.mainNavigateTo( detailModel.showSong(song)
MainNavigationAction.Directions(
MainFragmentDirections.actionShowDetails(song.uid)))
true true
} }
else -> { else -> {
@ -166,7 +162,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
true true
} }
R.id.action_go_artist -> { R.id.action_go_artist -> {
navModel.exploreNavigateToParentArtist(album) detailModel.showArtist(album)
true true
} }
R.id.action_playlist_add -> { R.id.action_playlist_add -> {

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2023 Auxio Project
* MainNavigationAction.kt is part of Auxio.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.navigation
import androidx.navigation.NavDirections
/**
* Represents the possible actions within the main navigation graph. This can be used with
* [NavigationViewModel] to initiate navigation in the main navigation graph from anywhere in the
* app, including outside the main navigation graph.
*
* @author Alexander Capehart (OxygenCobalt)
*/
sealed interface MainNavigationAction {
/** Expand the playback panel. */
object OpenPlaybackPanel : MainNavigationAction
/** Collapse the playback bottom sheet. */
object ClosePlaybackPanel : MainNavigationAction
/**
* Navigate to the given [NavDirections].
*
* @param directions The [NavDirections] to navigate to. Assumed to be part of the main
* navigation graph.
*/
data class Directions(val directions: NavDirections) : MainNavigationAction
}

View file

@ -1,127 +0,0 @@
/*
* Copyright (c) 2022 Auxio Project
* NavigationViewModel.kt is part of Auxio.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.navigation
import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.Event
import org.oxycblt.auxio.util.MutableEvent
import org.oxycblt.auxio.util.logD
/**
* A [ViewModel] that handles complicated navigation functionality.
*
* @author Alexander Capehart (OxygenCobalt)
*
* TODO: Unwind this into ViewModel-specific actions, and then reference those.
*/
class NavigationViewModel : ViewModel() {
private val _mainNavigationAction = MutableEvent<MainNavigationAction>()
/**
* Flag for navigation within the main navigation graph. Only intended for use by MainFragment.
*/
val mainNavigationAction: Event<MainNavigationAction>
get() = _mainNavigationAction
private val _exploreNavigationItem = MutableEvent<Music>()
/**
* Flag for navigation within the explore navigation graph. Observe this to coordinate
* navigation to a specific [Music] item.
*/
val exploreNavigationItem: Event<Music>
get() = _exploreNavigationItem
private val _exploreArtistNavigationItem = MutableEvent<Music>()
/**
* Variation of [exploreNavigationItem] for situations where the choice of parent [Artist] to
* navigate to is ambiguous. Only intended for use by MainFragment, as the resolved choice will
* eventually be assigned to [exploreNavigationItem].
*/
val exploreArtistNavigationItem: Event<Music>
get() = _exploreArtistNavigationItem
/**
* Navigate to something in the main navigation graph. This can be used by UIs in the explore
* navigation graph to trigger navigation in the higher-level main navigation graph. Will do
* nothing if already navigating.
*
* @param action The [MainNavigationAction] to perform.
*/
fun mainNavigateTo(action: MainNavigationAction) {
if (_mainNavigationAction.flow.value != null) {
logD("Already navigating, not doing main action")
return
}
logD("Navigating with action $action")
_mainNavigationAction.put(action)
}
/**
* Navigate to a given [Music] item. Will do nothing if already navigating.
*
* @param music The [Music] to navigate to.
*/
fun exploreNavigateTo(music: Music) {
if (_exploreNavigationItem.flow.value != null) {
logD("Already navigating, not doing explore action")
return
}
logD("Navigating to ${music.name}")
_exploreNavigationItem.put(music)
}
/**
* Navigate to one of the parent [Artist]'s of the given [Song].
*
* @param song The [Song] to navigate with. If there are multiple parent [Artist]s, a picker
* dialog will be shown.
*/
fun exploreNavigateToParentArtist(song: Song) {
logD("Navigating to parent artist of $song")
exploreNavigateToParentArtistImpl(song, song.artists)
}
/**
* Navigate to one of the parent [Artist]'s of the given [Album].
*
* @param album The [Album] to navigate with. If there are multiple parent [Artist]s, a picker
* dialog will be shown.
*/
fun exploreNavigateToParentArtist(album: Album) {
logD("Navigating to parent artist of $album")
exploreNavigateToParentArtistImpl(album, album.artists)
}
private fun exploreNavigateToParentArtistImpl(item: Music, artists: List<Artist>) {
if (_exploreArtistNavigationItem.flow.value != null) {
logD("Already navigating, not doing explore action")
return
}
if (artists.size == 1) {
exploreNavigateTo(artists[0])
} else {
logD("Navigating to a choice of ${artists.map { it.name }}")
_exploreArtistNavigationItem.put(item)
}
}
}

View file

@ -25,10 +25,9 @@ import com.google.android.material.R as MR
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.navigation.MainNavigationAction
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -44,7 +43,7 @@ import org.oxycblt.auxio.util.logD
@AndroidEntryPoint @AndroidEntryPoint
class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() { class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) = override fun onCreateBinding(inflater: LayoutInflater) =
FragmentPlaybackBarBinding.inflate(inflater) FragmentPlaybackBarBinding.inflate(inflater)
@ -58,9 +57,9 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
// --- UI SETUP --- // --- UI SETUP ---
binding.root.apply { binding.root.apply {
setOnClickListener { navModel.mainNavigateTo(MainNavigationAction.OpenPlaybackPanel) } setOnClickListener { playbackModel.openPlayback() }
setOnLongClickListener { setOnLongClickListener {
playbackModel.song.value?.let(navModel::exploreNavigateTo) playbackModel.song.value?.let(detailModel::showAlbum)
true true
} }
} }

View file

@ -30,15 +30,13 @@ import androidx.appcompat.widget.Toolbar
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.navigation.MainNavigationAction
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.ui.StyledSeekBar import org.oxycblt.auxio.playback.ui.StyledSeekBar
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
@ -63,7 +61,7 @@ class PlaybackPanelFragment :
StyledSeekBar.Listener { StyledSeekBar.Listener {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private var equalizerLauncher: ActivityResultLauncher<Intent>? = null private var equalizerLauncher: ActivityResultLauncher<Intent>? = null
override fun onCreateBinding(inflater: LayoutInflater) = override fun onCreateBinding(inflater: LayoutInflater) =
@ -90,9 +88,7 @@ class PlaybackPanelFragment :
} }
binding.playbackToolbar.apply { binding.playbackToolbar.apply {
setNavigationOnClickListener { setNavigationOnClickListener { playbackModel.openMain() }
navModel.mainNavigateTo(MainNavigationAction.ClosePlaybackPanel)
}
setOnMenuItemClickListener(this@PlaybackPanelFragment) setOnMenuItemClickListener(this@PlaybackPanelFragment)
} }
@ -100,7 +96,7 @@ class PlaybackPanelFragment :
// respective item. // respective item.
binding.playbackSong.apply { binding.playbackSong.apply {
isSelected = true isSelected = true
setOnClickListener { playbackModel.song.value?.let(navModel::exploreNavigateTo) } setOnClickListener { playbackModel.song.value?.let(detailModel::showAlbum) }
} }
binding.playbackArtist.apply { binding.playbackArtist.apply {
isSelected = true isSelected = true
@ -176,11 +172,7 @@ class PlaybackPanelFragment :
true true
} }
R.id.action_song_detail -> { R.id.action_song_detail -> {
playbackModel.song.value?.let { song -> playbackModel.song.value?.let(detailModel::showSong)
navModel.mainNavigateTo(
MainNavigationAction.Directions(
MainFragmentDirections.actionShowDetails(song.uid)))
}
true true
} }
R.id.action_share -> { R.id.action_share -> {
@ -237,12 +229,10 @@ class PlaybackPanelFragment :
} }
private fun navigateToCurrentArtist() { private fun navigateToCurrentArtist() {
val song = playbackModel.song.value ?: return playbackModel.song.value?.let(detailModel::showArtist)
navModel.exploreNavigateToParentArtist(song)
} }
private fun navigateToCurrentAlbum() { private fun navigateToCurrentAlbum() {
val song = playbackModel.song.value ?: return playbackModel.song.value?.let(detailModel::showAlbum)
navModel.exploreNavigateTo(song.album)
} }
} }

View file

@ -89,6 +89,10 @@ constructor(
val isShuffled: StateFlow<Boolean> val isShuffled: StateFlow<Boolean>
get() = _isShuffled get() = _isShuffled
private val _openPanel = MutableEvent<Panel>()
val openPanel: Event<Panel>
get() = _openPanel
private val _artistPlaybackPickerSong = MutableEvent<Song>() private val _artistPlaybackPickerSong = MutableEvent<Song>()
/** /**
* Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing a * Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing a
@ -555,6 +559,20 @@ constructor(
playbackManager.repeatMode = playbackManager.repeatMode.increment() playbackManager.repeatMode = playbackManager.repeatMode.increment()
} }
// --- UI CONTROL ---
fun openMain() = openImpl(Panel.Main)
fun openPlayback() = openImpl(Panel.Playback)
fun openQueue() = openImpl(Panel.Queue)
private fun openImpl(panel: Panel) {
val existing = openPanel.flow.value
if (existing != null) {
logD("Already opening $existing, ignoring opening $panel")
return
}
_openPanel.put(panel)
}
// --- SAVE/RESTORE FUNCTIONS --- // --- SAVE/RESTORE FUNCTIONS ---
/** /**
@ -598,3 +616,9 @@ constructor(
} }
} }
} }
sealed interface Panel {
object Main : Panel
object Playback : Panel
object Queue : Panel
}

View file

@ -34,6 +34,8 @@ import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.detail.Show
import org.oxycblt.auxio.list.Divider import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
@ -47,7 +49,6 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
@ -67,7 +68,7 @@ import org.oxycblt.auxio.util.setFullWidthLookup
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class SearchFragment : ListFragment<Music, FragmentSearchBinding>() { class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
override val navModel: NavigationViewModel by activityViewModels() override val detailModel: DetailViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
@ -137,7 +138,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
collectImmediately(searchModel.searchResults, ::updateSearchResults) collectImmediately(searchModel.searchResults, ::updateSearchResults)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
} }
@ -167,8 +168,11 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
override fun onRealClick(item: Music) { override fun onRealClick(item: Music) {
when (item) { when (item) {
is MusicParent -> navModel.exploreNavigateTo(item)
is Song -> playbackModel.playFrom(item, searchModel.playbackMode) is Song -> playbackModel.playFrom(item, searchModel.playbackMode)
is Album -> detailModel.showAlbum(item)
is Artist -> detailModel.showArtist(item)
is Genre -> detailModel.showGenre(item)
is Playlist -> detailModel.showPlaylist(item)
} }
} }
@ -200,19 +204,57 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
searchAdapter.setPlaying(parent ?: song, isPlaying) searchAdapter.setPlaying(parent ?: song, isPlaying)
} }
private fun handleNavigation(item: Music?) { private fun handleShow(show: Show?) {
val action = when (show) {
when (item) { is Show.SongDetails -> {
is Song -> SearchFragmentDirections.actionShowAlbum(item.album.uid) logD("Navigating to ${show.song}")
is Album -> SearchFragmentDirections.actionShowAlbum(item.uid) findNavController().navigateSafe(SearchFragmentDirections.showSong(show.song.uid))
is Artist -> SearchFragmentDirections.actionShowArtist(item.uid)
is Genre -> SearchFragmentDirections.actionShowGenre(item.uid)
is Playlist -> SearchFragmentDirections.actionShowPlaylist(item.uid)
null -> return
} }
// Songs should be scrolled to if the album matches, or a new detail
// fragment should be launched otherwise.
is Show.SongAlbumDetails -> {
logD("Navigating to the album of ${show.song}")
findNavController()
.navigateSafe(SearchFragmentDirections.showAlbum(show.song.album.uid))
}
// If the album matches, no need to do anything. Otherwise launch a new
// detail fragment.
is Show.AlbumDetails -> {
logD("Navigating to ${show.album}")
findNavController().navigateSafe(SearchFragmentDirections.showAlbum(show.album.uid))
}
// Always launch a new ArtistDetailFragment.
is Show.ArtistDetails -> {
logD("Navigating to ${show.artist}")
findNavController()
.navigateSafe(SearchFragmentDirections.showArtist(show.artist.uid))
}
is Show.SongArtistDetails -> {
logD("Navigating to artist choices for ${show.song}")
findNavController().navigateSafe(SearchFragmentDirections.showArtist(show.song.uid))
}
is Show.AlbumArtistDetails -> {
logD("Navigating to artist choices for ${show.album}")
findNavController()
.navigateSafe(SearchFragmentDirections.showArtist(show.album.uid))
}
is Show.GenreDetails -> {
logD("Navigating to ${show.genre}")
findNavController().navigateSafe(SearchFragmentDirections.showGenre(show.genre.uid))
}
is Show.PlaylistDetails -> {
logD("Navigating to ${show.playlist}")
findNavController()
.navigateSafe(SearchFragmentDirections.showGenre(show.playlist.uid))
}
null -> {}
}
// Keyboard is no longer needed. // Keyboard is no longer needed.
hideKeyboard() hideKeyboard()
findNavController().navigateSafe(action)
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -55,7 +55,7 @@ class RootPreferenceFragment : BasePreferenceFragment(R.xml.preferences_root) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_music_dirs)) { if (preference.key == getString(R.string.set_key_music_dirs)) {
findNavController().navigate(RootPreferenceFragmentDirections.goToMusicDirsDialog()) findNavController().navigate(RootPreferenceFragmentDirections.musicDirsSettings())
} }
} }
@ -66,23 +66,21 @@ class RootPreferenceFragment : BasePreferenceFragment(R.xml.preferences_root) {
when (preference.key) { when (preference.key) {
getString(R.string.set_key_ui) -> { getString(R.string.set_key_ui) -> {
logD("Navigating to UI preferences") logD("Navigating to UI preferences")
findNavController() findNavController().navigateSafe(RootPreferenceFragmentDirections.uiPreferences())
.navigateSafe(RootPreferenceFragmentDirections.goToUiPreferences())
} }
getString(R.string.set_key_personalize) -> { getString(R.string.set_key_personalize) -> {
logD("Navigating to personalization preferences") logD("Navigating to personalization preferences")
findNavController() findNavController()
.navigateSafe(RootPreferenceFragmentDirections.goToPersonalizePreferences()) .navigateSafe(RootPreferenceFragmentDirections.personalizePreferences())
} }
getString(R.string.set_key_music) -> { getString(R.string.set_key_music) -> {
logD("Navigating to music preferences") logD("Navigating to music preferences")
findNavController() findNavController()
.navigateSafe(RootPreferenceFragmentDirections.goToMusicPreferences()) .navigateSafe(RootPreferenceFragmentDirections.musicPreferences())
} }
getString(R.string.set_key_audio) -> { getString(R.string.set_key_audio) -> {
logD("Navigating to audio preferences") logD("Navigating to audio preferences")
findNavController() findNavController().navigateSafe(RootPreferenceFragmentDirections.audioPeferences())
.navigateSafe(RootPreferenceFragmentDirections.goToAudioPreferences())
} }
getString(R.string.set_key_reindex) -> musicModel.refresh() getString(R.string.set_key_reindex) -> musicModel.refresh()
getString(R.string.set_key_rescan) -> musicModel.rescan() getString(R.string.set_key_rescan) -> musicModel.rescan()

View file

@ -35,7 +35,7 @@ class AudioPreferenceFragment : BasePreferenceFragment(R.xml.preferences_audio)
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_pre_amp)) { if (preference.key == getString(R.string.set_key_pre_amp)) {
logD("Navigating to pre-amp dialog") logD("Navigating to pre-amp dialog")
findNavController().navigateSafe(AudioPreferenceFragmentDirections.goToPreAmpDialog()) findNavController().navigateSafe(AudioPreferenceFragmentDirections.preAmpSettings())
} }
} }
} }

View file

@ -41,8 +41,7 @@ class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music)
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_separators)) { if (preference.key == getString(R.string.set_key_separators)) {
logD("Navigating to separator dialog") logD("Navigating to separator dialog")
findNavController() findNavController().navigateSafe(MusicPreferenceFragmentDirections.separatorsSettings())
.navigateSafe(MusicPreferenceFragmentDirections.goToSeparatorsDialog())
} }
} }

View file

@ -34,8 +34,7 @@ class PersonalizePreferenceFragment : BasePreferenceFragment(R.xml.preferences_p
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_home_tabs)) { if (preference.key == getString(R.string.set_key_home_tabs)) {
logD("Navigating to home tab dialog") logD("Navigating to home tab dialog")
findNavController() findNavController().navigateSafe(PersonalizePreferenceFragmentDirections.tabSettings())
.navigateSafe(PersonalizePreferenceFragmentDirections.goToTabDialog())
} }
} }
} }

View file

@ -43,7 +43,7 @@ class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_accent)) { if (preference.key == getString(R.string.set_key_accent)) {
logD("Navigating to accent dialog") logD("Navigating to accent dialog")
findNavController().navigateSafe(UIPreferenceFragmentDirections.goToAccentDialog()) findNavController().navigateSafe(UIPreferenceFragmentDirections.accentSettings())
} }
} }

View file

@ -13,7 +13,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="org.oxycblt.auxio.ui.BottomSheetContentBehavior" app:layout_behavior="org.oxycblt.auxio.ui.BottomSheetContentBehavior"
app:navGraph="@navigation/nav_explore" app:navGraph="@navigation/main"
app:defaultNavHost="true"
tools:layout="@layout/fragment_home" /> tools:layout="@layout/fragment_home" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout

View file

@ -3,10 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_host" android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="org.oxycblt.auxio.MainFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_main"
tools:layout="@layout/fragment_main" /> tools:layout="@layout/fragment_main" />

View file

@ -14,7 +14,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="org.oxycblt.auxio.ui.BottomSheetContentBehavior" app:layout_behavior="org.oxycblt.auxio.ui.BottomSheetContentBehavior"
app:navGraph="@navigation/nav_explore" app:navGraph="@navigation/main"
app:defaultNavHost="true"
tools:layout="@layout/fragment_home" /> tools:layout="@layout/fragment_home" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout

View file

@ -0,0 +1,388 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/home_fragment"
android:name="org.oxycblt.auxio.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/search"
app:destination="@id/search_fragment" />
<action
android:id="@+id/preferences"
app:destination="@id/root_preferences_fragment" />
<action
android:id="@+id/about"
app:destination="@id/about_fragment" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/show_album"
app:destination="@id/album_detail_fragment" />
<action
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_genre"
app:destination="@id/genre_detail_fragment" />
<action
android:id="@+id/show_playlist"
app:destination="@id/playlist_detail_fragment" />
<action
android:id="@+id/new_playlist"
app:destination="@id/new_playlist_dialog" />
<action
android:id="@+id/rename_playlist"
app:destination="@id/rename_playlist_dialog" />
<action
android:id="@+id/delete_playlist"
app:destination="@id/delete_playlist_dialog" />
<action
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
<action
android:id="@+id/play_from_genre"
app:destination="@id/play_from_genre_dialog" />
</fragment>
<dialog
android:id="@+id/song_detail_dialog"
android:name="org.oxycblt.auxio.detail.SongDetailDialog"
android:label="song_detail_dialog"
tools:layout="@layout/dialog_song_detail">
<argument
android:name="songUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<fragment
android:id="@+id/search_fragment"
android:name="org.oxycblt.auxio.search.SearchFragment"
android:label="SearchFragment"
tools:layout="@layout/fragment_search">
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/show_album"
app:destination="@id/album_detail_fragment" />
<action
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_genre"
app:destination="@id/genre_detail_fragment" />
<action
android:id="@+id/show_playlist"
app:destination="@id/playlist_detail_fragment" />
<action
android:id="@+id/rename_playlist"
app:destination="@id/rename_playlist_dialog" />
<action
android:id="@+id/delete_playlist"
app:destination="@id/delete_playlist_dialog" />
<action
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
<action
android:id="@+id/play_from_genre"
app:destination="@id/play_from_genre_dialog" />
</fragment>
<fragment
android:id="@+id/album_detail_fragment"
android:name="org.oxycblt.auxio.detail.AlbumDetailFragment"
android:label="AlbumDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="albumUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/show_album"
app:destination="@id/album_detail_fragment" />
<action
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
<action
android:id="@+id/play_from_genre"
app:destination="@id/play_from_genre_dialog" />
</fragment>
<fragment
android:id="@+id/artist_detail_fragment"
android:name="org.oxycblt.auxio.detail.ArtistDetailFragment"
android:label="ArtistDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/show_album"
app:destination="@id/album_detail_fragment" />
<action
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/play_from_genre"
app:destination="@id/play_from_genre_dialog" />
</fragment>
<fragment
android:id="@+id/genre_detail_fragment"
android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
android:label="GenreDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/show_album"
app:destination="@id/album_detail_fragment" />
<action
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
<action
android:id="@+id/add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
</fragment>
<fragment
android:id="@+id/playlist_detail_fragment"
android:name="org.oxycblt.auxio.detail.PlaylistDetailFragment"
android:label="PlaylistDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/show_album"
app:destination="@id/album_detail_fragment" />
<action
android:id="@+id/show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/show_artists"
app:destination="@id/show_artists_dialog" />
<action
android:id="@+id/rename_playlist"
app:destination="@id/rename_playlist_dialog" />
<action
android:id="@+id/delete_playlist"
app:destination="@id/delete_playlist_dialog" />
<action
android:id="@+id/play_from_artist"
app:destination="@id/play_from_artist_dialog" />
<action
android:id="@+id/play_from_genre"
app:destination="@id/play_from_genre_dialog" />
</fragment>
<dialog
android:id="@+id/new_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.NewPlaylistDialog"
android:label="new_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="songUids"
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
</dialog>
<dialog
android:id="@+id/rename_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.RenamePlaylistDialog"
android:label="rename_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/delete_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.DeletePlaylistDialog"
android:label="delete_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/add_to_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.AddToPlaylistDialog"
android:label="new_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="songUids"
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
<action
android:id="@+id/new_playlist"
app:destination="@id/new_playlist_dialog" />
</dialog>
<dialog
android:id="@+id/show_artists_dialog"
android:name="org.oxycblt.auxio.detail.picker.ShowArtistDialog"
android:label="show_artists_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="itemUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/play_from_artist_dialog"
android:name="org.oxycblt.auxio.playback.picker.PlayFromArtistDialog"
android:label="play_from_artist_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/play_from_genre_dialog"
android:name="org.oxycblt.auxio.playback.picker.PlayFromGenreDialog"
android:label="play_from_genre_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<fragment
android:id="@+id/root_preferences_fragment"
android:name="org.oxycblt.auxio.settings.RootPreferenceFragment"
android:label="fragment_settings">
<action
android:id="@+id/ui_preferences"
app:destination="@id/ui_preferences_fragment" />
<action
android:id="@+id/personalize_preferences"
app:destination="@id/personalize_preferences_fragment" />
<action
android:id="@+id/music_preferences"
app:destination="@id/music_preferences_fragment" />
<action
android:id="@+id/audio_peferences"
app:destination="@id/audio_preferences_fragment" />
<action
android:id="@+id/music_dirs_settings"
app:destination="@id/music_dirs_dialog" />
</fragment>
<fragment
android:id="@+id/ui_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.UIPreferenceFragment"
android:label="fragment_ui_preferences">
<action
android:id="@+id/accent_settings"
app:destination="@id/accent_dialog" />
</fragment>
<fragment
android:id="@+id/personalize_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.PersonalizePreferenceFragment"
android:label="fragment_personalize_preferences">
<action
android:id="@+id/tab_settings"
app:destination="@id/tab_dialog" />
</fragment>
<fragment
android:id="@+id/music_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.MusicPreferenceFragment"
android:label="fragment_personalize_preferences">
<action
android:id="@+id/separators_settings"
app:destination="@id/separators_dialog" />
</fragment>
<fragment
android:id="@+id/audio_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.AudioPreferenceFragment"
android:label="fragment_personalize_preferences">
<action
android:id="@+id/pre_amp_settings"
app:destination="@id/pre_amp_dialog" />
</fragment>
<dialog
android:id="@+id/accent_dialog"
android:name="org.oxycblt.auxio.ui.accent.AccentCustomizeDialog"
android:label="accent_dialog"
tools:layout="@layout/dialog_accent" />
<dialog
android:id="@+id/tab_dialog"
android:name="org.oxycblt.auxio.home.tabs.TabCustomizeDialog"
android:label="tab_dialog"
tools:layout="@layout/dialog_tabs" />
<dialog
android:id="@+id/pre_amp_dialog"
android:name="org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog"
android:label="pre_amp_dialog"
tools:layout="@layout/dialog_pre_amp" />
<dialog
android:id="@+id/music_dirs_dialog"
android:name="org.oxycblt.auxio.music.fs.MusicDirsDialog"
android:label="music_dirs_dialog"
tools:layout="@layout/dialog_music_dirs" />
<dialog
android:id="@+id/separators_dialog"
android:name="org.oxycblt.auxio.music.metadata.SeparatorsDialog"
android:label="music_dirs_dialog"
tools:layout="@layout/dialog_separators" />
<fragment
android:id="@+id/about_fragment"
android:name="org.oxycblt.auxio.settings.AboutFragment"
android:label="dialog_about"
tools:layout="@layout/fragment_about" />
</navigation>

View file

@ -1,106 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/artist_detail_fragment"
android:name="org.oxycblt.auxio.detail.ArtistDetailFragment"
android:label="ArtistDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment" />
</fragment>
<fragment
android:id="@+id/album_detail_fragment"
android:name="org.oxycblt.auxio.detail.AlbumDetailFragment"
android:label="AlbumDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="albumUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment" />
</fragment>
<fragment
android:id="@+id/genre_detail_fragment"
android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
android:label="GenreDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment" />
</fragment>
<fragment
android:id="@+id/playlist_detail_fragment"
android:name="org.oxycblt.auxio.detail.PlaylistDetailFragment"
android:label="PlaylistDetailFragment"
tools:layout="@layout/fragment_detail">
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment" />
</fragment>
<fragment
android:id="@+id/search_fragment"
android:name="org.oxycblt.auxio.search.SearchFragment"
android:label="SearchFragment"
tools:layout="@layout/fragment_search">
<action
android:id="@+id/action_show_playlist"
app:destination="@id/playlist_detail_fragment" />
<action
android:id="@+id/action_show_genre"
app:destination="@id/genre_detail_fragment" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment" />
</fragment>
<fragment
android:id="@+id/home_fragment"
android:name="org.oxycblt.auxio.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_show_search"
app:destination="@id/search_fragment" />
<action
android:id="@+id/action_show_playlist"
app:destination="@id/playlist_detail_fragment" />
<action
android:id="@+id/action_show_genre"
app:destination="@id/genre_detail_fragment" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment" />
</fragment>
</navigation>

View file

@ -1,214 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/main_fragment">
<fragment
android:id="@+id/main_fragment"
android:name="org.oxycblt.auxio.MainFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_show_settings"
app:destination="@id/root_preferences_fragment" />
<action
android:id="@+id/action_show_about"
app:destination="@id/about_fragment" />
<action
android:id="@+id/action_show_details"
app:destination="@id/song_detail_dialog" />
<action
android:id="@+id/action_new_playlist"
app:destination="@id/new_playlist_dialog" />
<action
android:id="@+id/action_rename_playlist"
app:destination="@id/rename_playlist_dialog" />
<action
android:id="@+id/action_delete_playlist"
app:destination="@id/delete_playlist_dialog" />
<action
android:id="@+id/action_add_to_playlist"
app:destination="@id/add_to_playlist_dialog" />
<action
android:id="@+id/action_pick_navigation_artist"
app:destination="@id/navigate_to_artist_dialog" />
<action
android:id="@+id/action_pick_playback_artist"
app:destination="@id/play_from_artist_dialog" />
<action
android:id="@+id/action_pick_playback_genre"
app:destination="@id/play_from_genre_dialog" />
</fragment>
<dialog
android:id="@+id/song_detail_dialog"
android:name="org.oxycblt.auxio.detail.SongDetailDialog"
android:label="song_detail_dialog"
tools:layout="@layout/dialog_song_detail">
<argument
android:name="songUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/new_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.NewPlaylistDialog"
android:label="new_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="songUids"
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
</dialog>
<dialog
android:id="@+id/rename_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.RenamePlaylistDialog"
android:label="rename_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/delete_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.DeletePlaylistDialog"
android:label="delete_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/add_to_playlist_dialog"
android:name="org.oxycblt.auxio.music.picker.AddToPlaylistDialog"
android:label="new_playlist_dialog"
tools:layout="@layout/dialog_playlist_name">
<argument
android:name="songUids"
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
<action
android:id="@+id/action_new_playlist"
app:destination="@id/new_playlist_dialog" />
</dialog>
<dialog
android:id="@+id/navigate_to_artist_dialog"
android:name="org.oxycblt.auxio.navigation.picker.NavigateToArtistDialog"
android:label="navigate_to_artist_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="itemUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/play_from_artist_dialog"
android:name="org.oxycblt.auxio.playback.picker.PlayFromArtistDialog"
android:label="play_from_artist_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/play_from_genre_dialog"
android:name="org.oxycblt.auxio.playback.picker.PlayFromGenreDialog"
android:label="play_from_genre_dialog"
tools:layout="@layout/dialog_music_choices">
<argument
android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<fragment
android:id="@+id/root_preferences_fragment"
android:name="org.oxycblt.auxio.settings.RootPreferenceFragment"
android:label="fragment_settings">
<action
android:id="@+id/go_to_ui_preferences"
app:destination="@id/ui_preferences_fragment" />
<action
android:id="@+id/go_to_personalize_preferences"
app:destination="@id/personalize_preferences_fragment" />
<action
android:id="@+id/go_to_music_preferences"
app:destination="@id/music_preferences_fragment" />
<action
android:id="@+id/go_to_audio_preferences"
app:destination="@id/audio_preferences_fragment" />
<action
android:id="@+id/go_to_music_dirs_dialog"
app:destination="@id/music_dirs_dialog" />
</fragment>
<fragment
android:id="@+id/ui_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.UIPreferenceFragment"
android:label="fragment_ui_preferences">
<action
android:id="@+id/go_to_accent_dialog"
app:destination="@id/accent_dialog" />
</fragment>
<fragment
android:id="@+id/personalize_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.PersonalizePreferenceFragment"
android:label="fragment_personalize_preferences">
<action
android:id="@+id/go_to_tab_dialog"
app:destination="@id/tab_dialog" />
</fragment>
<fragment
android:id="@+id/music_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.MusicPreferenceFragment"
android:label="fragment_personalize_preferences">
<action
android:id="@+id/go_to_separators_dialog"
app:destination="@id/separators_dialog" />
</fragment>
<fragment
android:id="@+id/audio_preferences_fragment"
android:name="org.oxycblt.auxio.settings.categories.AudioPreferenceFragment"
android:label="fragment_personalize_preferences">
<action
android:id="@+id/go_to_pre_amp_dialog"
app:destination="@id/pre_amp_dialog" />
</fragment>
<dialog
android:id="@+id/accent_dialog"
android:name="org.oxycblt.auxio.ui.accent.AccentCustomizeDialog"
android:label="accent_dialog"
tools:layout="@layout/dialog_accent" />
<dialog
android:id="@+id/tab_dialog"
android:name="org.oxycblt.auxio.home.tabs.TabCustomizeDialog"
android:label="tab_dialog"
tools:layout="@layout/dialog_tabs" />
<dialog
android:id="@+id/pre_amp_dialog"
android:name="org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog"
android:label="pre_amp_dialog"
tools:layout="@layout/dialog_pre_amp" />
<dialog
android:id="@+id/music_dirs_dialog"
android:name="org.oxycblt.auxio.music.fs.MusicDirsDialog"
android:label="music_dirs_dialog"
tools:layout="@layout/dialog_music_dirs" />
<dialog
android:id="@+id/separators_dialog"
android:name="org.oxycblt.auxio.music.metadata.SeparatorsDialog"
android:label="music_dirs_dialog"
tools:layout="@layout/dialog_separators" />
<fragment
android:id="@+id/about_fragment"
android:name="org.oxycblt.auxio.settings.AboutFragment"
android:label="dialog_about"
tools:layout="@layout/fragment_about" />
</navigation>

View file

@ -1,7 +1,7 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.8.22' kotlin_version = '1.8.22'
navigation_version = "2.5.0" navigation_version = "2.6.0"
hilt_version = '2.46.1' hilt_version = '2.46.1'
} }