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.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import com.google.android.material.R as MR
import com.google.android.material.bottomsheet.BackportBottomSheetBehavior
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.list.selection.SelectionViewModel
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.navigation.MainNavigationAction
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.Panel
import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.auxio.util.unlikelyToBeNull
@ -76,8 +70,6 @@ class MainFragment :
ViewBindingFragment<FragmentMainBinding>(),
ViewTreeObserver.OnPreDrawListener,
NavController.OnDestinationChangedListener {
private val navModel: NavigationViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels()
private val selectionModel: SelectionViewModel 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.
logD("Configuring stacked bottom sheets")
unlikelyToBeNull(binding.queueHandleWrapper).setOnClickListener {
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
}
playbackModel.openQueue()
}
} else {
// Dual-pane mode, manually style the static queue sheet.
@ -175,16 +163,8 @@ class MainFragment :
// --- VIEWMODEL SETUP ---
collectImmediately(detailModel.editedPlaylist, detailBackCallback::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)
collect(playbackModel.artistPickerSong.flow, ::handlePlaybackArtistPicker)
collect(playbackModel.genrePickerSong.flow, ::handlePlaybackGenrePicker)
collectImmediately(playbackModel.openPanel.flow, ::handlePanel)
}
override fun onStart() {
@ -322,33 +302,6 @@ class MainFragment :
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?) {
if (song != null) {
tryShowSheets()
@ -357,56 +310,15 @@ class MainFragment :
}
}
private fun handleNewPlaylist(songs: List<Song>?) {
if (songs != null) {
findNavController()
.navigateSafe(
MainFragmentDirections.actionNewPlaylist(songs.map { it.uid }.toTypedArray()))
musicModel.newPlaylistSongs.consume()
}
}
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()
private fun handlePanel(panel: Panel?) {
if (panel == null) return
logD("Trying to update panel to $panel")
when (panel) {
is Panel.Main -> tryClosePlaybackPanel()
is Panel.Playback -> tryOpenPlaybackPanel()
is Panel.Queue -> tryOpenQueuePanel()
}
playbackModel.openPanel.consume()
}
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() {
val binding = requireBinding()
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.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.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.collect
@ -72,11 +70,10 @@ class AlbumDetailFragment :
ListFragment<Song, FragmentDetailBinding>(),
AlbumDetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Song> {
private 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 detailModel: DetailViewModel 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
// as a UID, as that is the only safe way to parcel an album.
private val args: AlbumDetailFragmentArgs by navArgs()
@ -125,10 +122,12 @@ class AlbumDetailFragment :
detailModel.setAlbum(args.albumUid)
collectImmediately(detailModel.currentAlbum, ::updateAlbum)
collectImmediately(detailModel.albumList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation)
collectImmediately(selectionModel.selected, ::updateSelection)
collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
}
override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -221,7 +220,7 @@ class AlbumDetailFragment :
}
override fun onNavigateToParentArtist() {
navModel.exploreNavigateToParentArtist(unlikelyToBeNull(detailModel.currentAlbum.value))
detailModel.showAlbum(unlikelyToBeNull(detailModel.currentAlbum.value))
}
private fun updateAlbum(album: Album?) {
@ -234,53 +233,88 @@ class AlbumDetailFragment :
albumHeaderAdapter.setParent(album)
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
albumListAdapter.setPlaying(
song.takeIf { parent == detailModel.currentAlbum.value }, isPlaying)
private fun updateList(list: List<Item>) {
albumListAdapter.update(list, detailModel.albumInstructions.consume())
}
private fun handleNavigation(item: Music?) {
private fun handleShow(show: Show?) {
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
// fragment should be launched otherwise.
is Song -> {
if (unlikelyToBeNull(detailModel.currentAlbum.value) == item.album) {
logD("Navigating to a song in this album")
scrollToAlbumSong(item)
navModel.exploreNavigationItem.consume()
is Show.SongAlbumDetails -> {
if (unlikelyToBeNull(detailModel.currentAlbum.value) == show.song.album) {
logD("Navigating to a ${show.song} in this album")
scrollToAlbumSong(show.song)
detailModel.toShow.consume()
} else {
logD("Navigating to another album")
logD("Navigating to the album of ${show.song}")
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
// detail fragment.
is Album -> {
if (unlikelyToBeNull(detailModel.currentAlbum.value) == item) {
is Show.AlbumDetails -> {
if (unlikelyToBeNull(detailModel.currentAlbum.value) == show.album) {
logD("Navigating to the top of this album")
binding.detailRecycler.scrollToPosition(0)
navModel.exploreNavigationItem.consume()
detailModel.toShow.consume()
} else {
logD("Navigating to another album")
logD("Navigating to ${show.album}")
findNavController()
.navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.uid))
.navigateSafe(AlbumDetailFragmentDirections.showAlbum(show.album.uid))
}
}
// Always launch a new ArtistDetailFragment.
is Artist -> {
logD("Navigating to another artist")
is Show.ArtistDetails -> {
logD("Navigating to ${show.artist}")
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 -> {}
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) {
// Calculate where the item for the currently played song is
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>) {
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.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
@ -69,11 +68,10 @@ class ArtistDetailFragment :
ListFragment<Music, FragmentDetailBinding>(),
DetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Music> {
private 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 detailModel: DetailViewModel 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
// as a UID, as that is the only safe way to parcel an artist.
private val args: ArtistDetailFragmentArgs by navArgs()
@ -125,9 +123,11 @@ class ArtistDetailFragment :
detailModel.setArtist(args.artistUid)
collectImmediately(detailModel.currentArtist, ::updateArtist)
collectImmediately(detailModel.artistList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(
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)
}
@ -174,7 +174,7 @@ class ArtistDetailFragment :
override fun onRealClick(item: Music) {
when (item) {
is Album -> navModel.exploreNavigateTo(item)
is Album -> detailModel.showAlbum(item)
is Song -> {
val playbackMode = detailModel.playbackMode
if (playbackMode != null) {
@ -257,6 +257,57 @@ class ArtistDetailFragment :
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) {
val currentArtist = unlikelyToBeNull(detailModel.currentArtist.value)
val playingItem =
@ -272,43 +323,16 @@ class ArtistDetailFragment :
artistListAdapter.setPlaying(playingItem, isPlaying)
}
private fun handleNavigation(item: Music?) {
val binding = requireBinding()
when (item) {
// 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 handlePlayFromArtist(song: Song?) {
if (song == null) return
logD("Launching play from artist dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromArtist(song.uid))
}
private fun updateList(list: List<Item>) {
artistListAdapter.update(list, detailModel.artistInstructions.consume())
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>) {

View file

@ -70,6 +70,10 @@ constructor(
private val musicSettings: MusicSettings,
private val playbackSettings: PlaybackSettings
) : ViewModel(), MusicRepository.UpdateListener {
private val _toShow = MutableEvent<Show>()
val toShow: Event<Show>
get() = _toShow
// --- SONG ---
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
* be updated to align with the new [Song].
@ -582,3 +623,14 @@ constructor(
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.Sort
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.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
@ -70,11 +68,10 @@ class GenreDetailFragment :
ListFragment<Music, FragmentDetailBinding>(),
DetailHeaderAdapter.Listener,
DetailListAdapter.Listener<Music> {
private 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 detailModel: DetailViewModel 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
// as a UID, as that is the only safe way to parcel an genre.
private val args: GenreDetailFragmentArgs by navArgs()
@ -124,9 +121,11 @@ class GenreDetailFragment :
detailModel.setGenre(args.genreUid)
collectImmediately(detailModel.currentGenre, ::updatePlaylist)
collectImmediately(detailModel.genreList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(
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)
}
@ -173,7 +172,7 @@ class GenreDetailFragment :
override fun onRealClick(item: Music) {
when (item) {
is Artist -> navModel.exploreNavigateTo(item)
is Artist -> detailModel.showArtist(item)
is Song -> {
val playbackMode = detailModel.playbackMode
if (playbackMode != null) {
@ -242,6 +241,60 @@ class GenreDetailFragment :
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) {
val currentGenre = unlikelyToBeNull(detailModel.currentGenre.value)
val playingItem =
@ -257,32 +310,16 @@ class GenreDetailFragment :
genreListAdapter.setPlaying(playingItem, isPlaying)
}
private fun handleNavigation(item: Music?) {
when (item) {
is Song -> {
logD("Navigating to another song")
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 handlePlayFromArtist(song: Song?) {
if (song == null) return
logD("Launching play from artist dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromArtist(song.uid))
}
private fun updateList(list: List<Item>) {
genreListAdapter.update(list, detailModel.genreInstructions.consume())
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>) {

View file

@ -44,14 +44,11 @@ import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment
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.MusicParent
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
@ -74,11 +71,10 @@ class PlaylistDetailFragment :
DetailHeaderAdapter.Listener,
PlaylistDetailListAdapter.Listener,
NavController.OnDestinationChangedListener {
private 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 detailModel: DetailViewModel 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
// as a UID, as that is the only safe way to parcel an playlist.
private val args: PlaylistDetailFragmentArgs by navArgs()
@ -139,10 +135,12 @@ class PlaylistDetailFragment :
detailModel.setPlaylist(args.playlistUid)
collectImmediately(detailModel.currentPlaylist, ::updatePlaylist)
collectImmediately(detailModel.playlistList, ::updateList)
collectImmediately(detailModel.editedPlaylist, ::updateEditedPlaylist)
collectImmediately(detailModel.editedPlaylist, ::updateEditedList)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(
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)
}
@ -275,41 +273,11 @@ class PlaylistDetailFragment :
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>) {
playlistListAdapter.update(list, detailModel.playlistInstructions.consume())
}
private fun updateEditedPlaylist(editedPlaylist: List<Song>?) {
private fun updateEditedList(editedPlaylist: List<Song>?) {
playlistListAdapter.setEditing(editedPlaylist != null)
playlistHeaderAdapter.setEditedPlaylist(editedPlaylist)
selectionModel.drop()
@ -324,6 +292,74 @@ class PlaylistDetailFragment :
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>) {
playlistListAdapter.setSelected(selected.toSet())

View file

@ -70,6 +70,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
// DetailViewModel handles most initialization from the navigation argument.
detailModel.setSong(args.songUid)
collectImmediately(detailModel.currentSong, detailModel.songAudioProperties, ::updateSong)
collectImmediately(detailModel.toShow.flow, ::handleShow)
}
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 {
val name = name
return if (name is Name.Known && name.sort != null) {

View file

@ -1,6 +1,6 @@
/*
* 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
* 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/>.
*/
package org.oxycblt.auxio.navigation.picker
package org.oxycblt.auxio.detail.picker
import android.view.View
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
* [NavigateToArtistDialog].
* [ShowArtistDialog].
*
* @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>(
ArtistNavigationChoiceViewHolder.DIFF_CALLBACK) {
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
* use [ArtistNavigationChoiceAdapter]. Use [from] to create an instance.
* use [ArtistShowChoice]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt)
*/

View file

@ -16,7 +16,7 @@
* 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 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.MusicRepository
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.device.DeviceLibrary
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/**
* A [ViewModel] that stores the current information required for navigation picker dialogs
@ -38,9 +40,9 @@ import org.oxycblt.auxio.util.logD
@HiltViewModel
class NavigationPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
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. */
val artistChoices: StateFlow<ArtistNavigationChoices?>
val artistChoices: StateFlow<ArtistShowChoices?>
get() = _artistChoices
init {
@ -51,18 +53,7 @@ class NavigationPickerViewModel @Inject constructor(private val musicRepository:
if (!changes.deviceLibrary) return
val deviceLibrary = musicRepository.deviceLibrary ?: return
// Need to sanitize different items depending on the current set of choices.
_artistChoices.value =
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
}
_artistChoices.value = _artistChoices.value?.sanitize(deviceLibrary)
logD("Updated artist choices: ${_artistChoices.value}")
}
@ -83,14 +74,14 @@ class NavigationPickerViewModel @Inject constructor(private val musicRepository:
when (val music = musicRepository.find(itemUid)) {
is Song -> {
logD("Creating navigation choices for song")
SongArtistNavigationChoices(music)
ArtistShowChoices.FromSong(music)
}
is Album -> {
logD("Creating navigation choices for album")
AlbumArtistNavigationChoices(music)
ArtistShowChoices.FromAlbum(music)
}
else -> {
logD("Given song/album UID was invalid")
logW("Given song/album UID was invalid")
null
}
}
@ -102,20 +93,29 @@ class NavigationPickerViewModel @Inject constructor(private val musicRepository:
*
* @author Alexander Capehart (OxygenCobalt)
*/
sealed interface ArtistNavigationChoices {
sealed interface ArtistShowChoices {
/** The UID of the item. */
val uid: Music.UID
/** The current [Artist] choices. */
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]. */
private data class SongArtistNavigationChoices(val song: Song) : ArtistNavigationChoices {
override val choices = song.artists
}
/** Backing implementation of [ArtistShowChoices] that is based on a [Song]. */
class FromSong(val song: Song) : ArtistShowChoices {
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
* [AlbumArtistNavigationChoices].
*/
private data class AlbumArtistNavigationChoices(val album: Album) : ArtistNavigationChoices {
override val choices = album.artists
/**
* Backing implementation of [ArtistShowChoices] that is based on an [AlbumArtistShowChoices].
*/
data class FromAlbum(val album: Album) : ArtistShowChoices {
override val uid = album.uid
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
* NavigateToArtistDialog.kt is part of Auxio.
* ShowArtistDialog.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
@ -16,7 +16,7 @@
* 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.view.LayoutInflater
@ -29,27 +29,27 @@ import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicChoicesBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
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)
*/
@AndroidEntryPoint
class NavigateToArtistDialog :
class ShowArtistDialog :
ViewBindingDialogFragment<DialogMusicChoicesBinding>(), ClickableListListener<Artist> {
private val navigationModel: NavigationViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
private val pickerModel: NavigationPickerViewModel by viewModels()
// 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.
private val args: NavigateToArtistDialogArgs by navArgs()
private val choiceAdapter = ArtistNavigationChoiceAdapter(this)
private val args: ShowArtistDialogArgs by navArgs()
private val choiceAdapter = ArtistShowChoice(this)
override fun onConfigDialog(builder: AlertDialog.Builder) {
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) {
// User made a choice, navigate to the artist.
navigationModel.exploreNavigateTo(item)
detailModel.showArtist(item)
findNavController().navigateUp()
}
}

View file

@ -43,9 +43,10 @@ import dagger.hilt.android.AndroidEntryPoint
import java.lang.reflect.Field
import kotlin.math.abs
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.detail.Show
import org.oxycblt.auxio.home.list.AlbumListFragment
import org.oxycblt.auxio.home.list.ArtistListFragment
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.selection.SelectionFragment
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.IndexingState
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.NoMusicException
import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO
import org.oxycblt.auxio.music.Playlist
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.util.collect
import org.oxycblt.auxio.util.collectImmediately
@ -94,7 +89,7 @@ class HomeFragment :
override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel 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
override fun onCreate(savedInstanceState: Bundle?) {
@ -177,7 +172,7 @@ class HomeFragment :
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab)
collectImmediately(musicModel.indexingState, ::updateIndexerState)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
}
@ -218,19 +213,17 @@ class HomeFragment :
R.id.action_search -> {
logD("Navigating to search")
setupAxisTransitions(MaterialSharedAxis.Z)
findNavController().navigateSafe(HomeFragmentDirections.actionShowSearch())
findNavController().navigateSafe(HomeFragmentDirections.search())
true
}
R.id.action_settings -> {
logD("Navigating to settings")
navModel.mainNavigateTo(
MainNavigationAction.Directions(MainFragmentDirections.actionShowSettings()))
logD("Navigating to preferences")
findNavController().navigateSafe(HomeFragmentDirections.preferences())
true
}
R.id.action_about -> {
logD("Navigating to about")
navModel.mainNavigateTo(
MainNavigationAction.Directions(MainFragmentDirections.actionShowAbout()))
findNavController().navigateSafe(HomeFragmentDirections.about())
true
}
@ -500,19 +493,55 @@ class HomeFragment :
}
}
private fun handleNavigation(item: Music?) {
val action =
when (item) {
is Song -> HomeFragmentDirections.actionShowAlbum(item.album.uid)
is Album -> HomeFragmentDirections.actionShowAlbum(item.uid)
is Artist -> HomeFragmentDirections.actionShowArtist(item.uid)
is Genre -> HomeFragmentDirections.actionShowGenre(item.uid)
is Playlist -> HomeFragmentDirections.actionShowPlaylist(item.uid)
null -> return
private fun handleShow(show: Show?) {
when (show) {
is Show.SongDetails -> {
logD("Navigating to ${show.song}")
findNavController().navigateSafe(HomeFragmentDirections.showSong(show.song.uid))
}
setupAxisTransitions(MaterialSharedAxis.X)
findNavController().navigateSafe(action)
// 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}")
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>) {

View file

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

View file

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

View file

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

View file

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

View file

@ -28,6 +28,7 @@ import dagger.hilt.android.AndroidEntryPoint
import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
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.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs
@ -58,7 +58,7 @@ class SongListFragment :
FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.Listener {
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 musicModel: MusicViewModel 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.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.selection.SelectionFragment
import org.oxycblt.auxio.music.Album
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.Playlist
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.logW
import org.oxycblt.auxio.util.share
@ -47,7 +45,7 @@ import org.oxycblt.auxio.util.showToast
*/
abstract class ListFragment<in T : Music, VB : ViewBinding> :
SelectionFragment<VB>(), SelectableListListener<T> {
protected abstract val navModel: NavigationViewModel
protected abstract val detailModel: DetailViewModel
private var currentMenu: PopupMenu? = null
override fun onDestroyBinding(binding: VB) {
@ -103,11 +101,11 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
true
}
R.id.action_go_artist -> {
navModel.exploreNavigateToParentArtist(song)
detailModel.showArtist(song)
true
}
R.id.action_go_album -> {
navModel.exploreNavigateTo(song.album)
detailModel.showAlbum(song.album)
true
}
R.id.action_share -> {
@ -119,9 +117,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
true
}
R.id.action_song_detail -> {
navModel.mainNavigateTo(
MainNavigationAction.Directions(
MainFragmentDirections.actionShowDetails(song.uid)))
detailModel.showSong(song)
true
}
else -> {
@ -166,7 +162,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
true
}
R.id.action_go_artist -> {
navModel.exploreNavigateToParentArtist(album)
detailModel.showArtist(album)
true
}
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 org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song
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.ui.ViewBindingFragment
import org.oxycblt.auxio.util.collectImmediately
@ -44,7 +43,7 @@ import org.oxycblt.auxio.util.logD
@AndroidEntryPoint
class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val navModel: NavigationViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) =
FragmentPlaybackBarBinding.inflate(inflater)
@ -58,9 +57,9 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
// --- UI SETUP ---
binding.root.apply {
setOnClickListener { navModel.mainNavigateTo(MainNavigationAction.OpenPlaybackPanel) }
setOnClickListener { playbackModel.openPlayback() }
setOnLongClickListener {
playbackModel.song.value?.let(navModel::exploreNavigateTo)
playbackModel.song.value?.let(detailModel::showAlbum)
true
}
}

View file

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

View file

@ -89,6 +89,10 @@ constructor(
val isShuffled: StateFlow<Boolean>
get() = _isShuffled
private val _openPanel = MutableEvent<Panel>()
val openPanel: Event<Panel>
get() = _openPanel
private val _artistPlaybackPickerSong = MutableEvent<Song>()
/**
* 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()
}
// --- 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 ---
/**
@ -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 org.oxycblt.auxio.R
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.Header
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.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.navigation.NavigationViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
@ -67,7 +68,7 @@ import org.oxycblt.auxio.util.setFullWidthLookup
*/
@AndroidEntryPoint
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 musicModel: MusicViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels()
@ -137,7 +138,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
collectImmediately(searchModel.searchResults, ::updateSearchResults)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem.flow, ::handleNavigation)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
}
@ -167,8 +168,11 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
override fun onRealClick(item: Music) {
when (item) {
is MusicParent -> navModel.exploreNavigateTo(item)
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)
}
private fun handleNavigation(item: Music?) {
val action =
when (item) {
is Song -> SearchFragmentDirections.actionShowAlbum(item.album.uid)
is Album -> SearchFragmentDirections.actionShowAlbum(item.uid)
is Artist -> SearchFragmentDirections.actionShowArtist(item.uid)
is Genre -> SearchFragmentDirections.actionShowGenre(item.uid)
is Playlist -> SearchFragmentDirections.actionShowPlaylist(item.uid)
null -> return
private fun handleShow(show: Show?) {
when (show) {
is Show.SongDetails -> {
logD("Navigating to ${show.song}")
findNavController().navigateSafe(SearchFragmentDirections.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(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.
hideKeyboard()
findNavController().navigateSafe(action)
}
private fun updateSelection(selected: List<Music>) {

View file

@ -55,7 +55,7 @@ class RootPreferenceFragment : BasePreferenceFragment(R.xml.preferences_root) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
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) {
getString(R.string.set_key_ui) -> {
logD("Navigating to UI preferences")
findNavController()
.navigateSafe(RootPreferenceFragmentDirections.goToUiPreferences())
findNavController().navigateSafe(RootPreferenceFragmentDirections.uiPreferences())
}
getString(R.string.set_key_personalize) -> {
logD("Navigating to personalization preferences")
findNavController()
.navigateSafe(RootPreferenceFragmentDirections.goToPersonalizePreferences())
.navigateSafe(RootPreferenceFragmentDirections.personalizePreferences())
}
getString(R.string.set_key_music) -> {
logD("Navigating to music preferences")
findNavController()
.navigateSafe(RootPreferenceFragmentDirections.goToMusicPreferences())
.navigateSafe(RootPreferenceFragmentDirections.musicPreferences())
}
getString(R.string.set_key_audio) -> {
logD("Navigating to audio preferences")
findNavController()
.navigateSafe(RootPreferenceFragmentDirections.goToAudioPreferences())
findNavController().navigateSafe(RootPreferenceFragmentDirections.audioPeferences())
}
getString(R.string.set_key_reindex) -> musicModel.refresh()
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) {
if (preference.key == getString(R.string.set_key_pre_amp)) {
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) {
if (preference.key == getString(R.string.set_key_separators)) {
logD("Navigating to separator dialog")
findNavController()
.navigateSafe(MusicPreferenceFragmentDirections.goToSeparatorsDialog())
findNavController().navigateSafe(MusicPreferenceFragmentDirections.separatorsSettings())
}
}

View file

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

View file

@ -43,7 +43,7 @@ class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_accent)) {
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_height="match_parent"
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" />
<androidx.coordinatorlayout.widget.CoordinatorLayout

View file

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

View file

@ -14,7 +14,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
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" />
<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 {
ext {
kotlin_version = '1.8.22'
navigation_version = "2.5.0"
navigation_version = "2.6.0"
hilt_version = '2.46.1'
}