From f2a90bf0af3f9b3829dcd24d9b8fc33b2fe8d889 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sat, 25 Mar 2023 21:01:21 -0600 Subject: [PATCH] picker: refactor into module-specific impls Refactor the weird picker god module into specific sub-impls in playback and a new navigation package. I cannot keep this unified. The needs are too different among each picker. Better to keep it separate, especially in preparation for the playlist dialogs. --- CHANGELOG.md | 3 + .../java/org/oxycblt/auxio/MainActivity.kt | 3 + .../java/org/oxycblt/auxio/MainFragment.kt | 4 +- .../auxio/detail/AlbumDetailFragment.kt | 2 +- .../auxio/detail/ArtistDetailFragment.kt | 2 +- .../auxio/detail/GenreDetailFragment.kt | 2 +- .../auxio/detail/PlaylistDetailFragment.kt | 2 +- .../oxycblt/auxio/detail/SongDetailDialog.kt | 2 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 4 +- .../auxio/home/list/AlbumListFragment.kt | 2 +- .../auxio/home/list/ArtistListFragment.kt | 2 +- .../auxio/home/list/GenreListFragment.kt | 2 +- .../auxio/home/list/PlaylistListFragment.kt | 2 +- .../auxio/home/list/SongListFragment.kt | 2 +- .../org/oxycblt/auxio/list/ListFragment.kt | 4 +- .../auxio/navigation/MainNavigationAction.kt | 44 +++++++++ .../{ui => navigation}/NavigationViewModel.kt | 34 ++----- .../picker/ArtistNavigationPickerDialog.kt | 27 ++++-- .../picker/NavigationPickerViewModel.kt | 93 ++++++++++++++++++ .../auxio/picker/ArtistChoiceAdapter.kt | 90 ----------------- .../org/oxycblt/auxio/picker/ChoiceAdapter.kt | 87 +++++++++++++++++ .../auxio/picker/GenreChoiceAdapter.kt | 90 ----------------- .../org/oxycblt/auxio/picker/PickerChoices.kt | 31 ++++++ ...ickerDialog.kt => PickerDialogFragment.kt} | 48 ++++++---- .../oxycblt/auxio/picker/PickerViewModel.kt | 87 ----------------- .../auxio/playback/PlaybackBarFragment.kt | 4 +- .../auxio/playback/PlaybackPanelFragment.kt | 4 +- .../picker/ArtistPlaybackPickerDialog.kt | 27 ++++-- .../picker/GenrePlaybackPickerDialog.kt | 52 +++------- .../picker/PlaybackPickerViewModel.kt | 96 +++++++++++++++++++ .../oxycblt/auxio/search/SearchFragment.kt | 2 +- .../oxycblt/auxio/widgets/WidgetComponent.kt | 4 +- app/src/main/res/navigation/nav_main.xml | 14 +-- 33 files changed, 472 insertions(+), 400 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/navigation/MainNavigationAction.kt rename app/src/main/java/org/oxycblt/auxio/{ui => navigation}/NavigationViewModel.kt (83%) rename app/src/main/java/org/oxycblt/auxio/{ => navigation}/picker/ArtistNavigationPickerDialog.kt (66%) create mode 100644 app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/picker/ArtistChoiceAdapter.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/picker/ChoiceAdapter.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/picker/GenreChoiceAdapter.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/picker/PickerChoices.kt rename app/src/main/java/org/oxycblt/auxio/picker/{ArtistPickerDialog.kt => PickerDialogFragment.kt} (59%) delete mode 100644 app/src/main/java/org/oxycblt/auxio/picker/PickerViewModel.kt rename app/src/main/java/org/oxycblt/auxio/{ => playback}/picker/ArtistPlaybackPickerDialog.kt (70%) rename app/src/main/java/org/oxycblt/auxio/{ => playback}/picker/GenrePlaybackPickerDialog.kt (50%) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index b18f594f3..dad347f8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## dev +## What's Fixed +- Fixed inconsistent corner radius on widget cover art + ## What's Improved - Added ability to click on the playback bar to exit the queue view diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 1fb9733f2..410ea7904 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -48,6 +48,9 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * TODO: Use proper material attributes (Not the weird dimen attributes I currently have) * TODO: Migrate to material animation system * TODO: Unit testing + * TODO: Use sealed interface where applicable + * TODO: Fix UID naming + * TODO: Leverage FlexibleListAdapter more in dialogs (Disable item anims) */ @AndroidEntryPoint class MainActivity : AppCompatActivity() { diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 8ce51e915..817ef494e 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -41,11 +41,11 @@ import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.navigation.MainNavigationAction +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.* diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index e253f0752..0e81847ad 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -44,8 +44,8 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.* /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index ed3ff8f13..7e7b70816 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -43,8 +43,8 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.* /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index ebcc60a02..3335e6068 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -39,8 +39,8 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.* /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt index f0e8e64ba..635574c13 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -39,8 +39,8 @@ import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.* /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index 337759103..8de46fb72 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -66,7 +66,7 @@ class SongDetailDialog : ViewBindingDialogFragment() { super.onBindingCreated(binding, savedInstanceState) binding.detailProperties.adapter = detailAdapter // DetailViewModel handles most initialization from the navigation argument. - detailModel.setSongUid(args.itemUid) + detailModel.setSongUid(args.songUid) collectImmediately(detailModel.currentSong, detailModel.songAudioInfo, ::updateSong) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 0827a00b9..291340b76 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -52,9 +52,9 @@ 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.* +import org.oxycblt.auxio.navigation.MainNavigationAction +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.* /** diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index b5b9135dd..2266f56a1 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -37,10 +37,10 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.secsToMs -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.collectImmediately /** diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index 29e490dc6..8b3a5c369 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -38,9 +38,9 @@ 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.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.nonZeroOrNull diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index f9b1f9aba..b6ba1d9cf 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -38,9 +38,9 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt index 12d21c7c9..c8df535f2 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt @@ -37,9 +37,9 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Playlist +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 9dc512b99..d24f6abab 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -40,10 +40,10 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent 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 -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.collectImmediately /** diff --git a/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt index c30909c33..d9c48151a 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt @@ -29,8 +29,8 @@ import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.list.selection.SelectionFragment import org.oxycblt.auxio.music.* -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel +import org.oxycblt.auxio.navigation.MainNavigationAction +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.showToast diff --git a/app/src/main/java/org/oxycblt/auxio/navigation/MainNavigationAction.kt b/app/src/main/java/org/oxycblt/auxio/navigation/MainNavigationAction.kt new file mode 100644 index 000000000..36eec45f7 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/navigation/MainNavigationAction.kt @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +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 class 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() +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt b/app/src/main/java/org/oxycblt/auxio/navigation/NavigationViewModel.kt similarity index 83% rename from app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/navigation/NavigationViewModel.kt index 17d5b9d23..27125123f 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/navigation/NavigationViewModel.kt @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio.navigation import androidx.lifecycle.ViewModel -import androidx.navigation.NavDirections import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music @@ -28,7 +27,13 @@ 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. */ +/** + * A [ViewModel] that handles complicated navigation functionality. + * + * @author Alexander Capehart (OxygenCobalt) + * + * TODO: This whole system is very jankily designed, perhaps it's time for a refactor? + */ class NavigationViewModel : ViewModel() { private val _mainNavigationAction = MutableEvent() /** @@ -118,26 +123,3 @@ class NavigationViewModel : ViewModel() { } } } - -/** - * 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 class 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() -} diff --git a/app/src/main/java/org/oxycblt/auxio/picker/ArtistNavigationPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/navigation/picker/ArtistNavigationPickerDialog.kt similarity index 66% rename from app/src/main/java/org/oxycblt/auxio/picker/ArtistNavigationPickerDialog.kt rename to app/src/main/java/org/oxycblt/auxio/navigation/picker/ArtistNavigationPickerDialog.kt index a43fc61a0..24d49a8cf 100644 --- a/app/src/main/java/org/oxycblt/auxio/picker/ArtistNavigationPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/navigation/picker/ArtistNavigationPickerDialog.kt @@ -16,32 +16,41 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.picker +package org.oxycblt.auxio.navigation.picker -import android.os.Bundle import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint -import org.oxycblt.auxio.databinding.DialogMusicPickerBinding +import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.ui.NavigationViewModel +import org.oxycblt.auxio.navigation.NavigationViewModel +import org.oxycblt.auxio.picker.PickerChoices +import org.oxycblt.auxio.picker.PickerDialogFragment /** - * An [ArtistPickerDialog] intended for when [Artist] navigation is ambiguous. + * A [PickerDialogFragment] intended for when [Artist] navigation is ambiguous. * * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class ArtistNavigationPickerDialog : ArtistPickerDialog() { +class ArtistNavigationPickerDialog : PickerDialogFragment() { private val navModel: NavigationViewModel by activityViewModels() + private val pickerModel: NavigationPickerViewModel by viewModels() // Information about what Song to show choices for is initially within the navigation arguments // as UIDs, as that is the only safe way to parcel a Song. private val args: ArtistNavigationPickerDialogArgs by navArgs() - override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { - pickerModel.setItemUid(args.itemUid) - super.onBindingCreated(binding, savedInstanceState) + override val titleRes: Int + get() = R.string.lbl_artists + + override val pickerChoices: StateFlow?> + get() = pickerModel.currentArtistChoices + + override fun initChoices() { + pickerModel.setArtistChoiceUid(args.artistUid) } override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) { diff --git a/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt new file mode 100644 index 000000000..dc14b8ea0 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 Auxio Project + * NavigationPickerViewModel.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 . + */ + +package org.oxycblt.auxio.navigation.picker + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.picker.PickerChoices + +/** + * A [ViewModel] that stores the current information required for [ArtistNavigationPickerDialog]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@HiltViewModel +class NavigationPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) : + ViewModel(), MusicRepository.UpdateListener { + private val _currentArtistChoices = MutableStateFlow(null) + /** The current set of [Artist] choices to show in the picker, or null if to show nothing. */ + val currentArtistChoices: StateFlow?> + get() = _currentArtistChoices + + init { + musicRepository.addUpdateListener(this) + } + + override fun onMusicChanges(changes: MusicRepository.Changes) { + if (!changes.deviceLibrary) return + val deviceLibrary = musicRepository.deviceLibrary ?: return + // Need to sanitize different items depending on the current set of choices. + _currentArtistChoices.value = + when (val choices = _currentArtistChoices.value) { + is ArtistNavigationChoices.FromSong -> + deviceLibrary.findSong(choices.song.uid)?.let { + ArtistNavigationChoices.FromSong(it) + } + is ArtistNavigationChoices.FromAlbum -> + deviceLibrary.findAlbum(choices.album.uid)?.let { + ArtistNavigationChoices.FromAlbum(it) + } + else -> null + } + } + + override fun onCleared() { + super.onCleared() + musicRepository.removeUpdateListener(this) + } + + /** + * Set the [Music.UID] of the item to show artist choices for. + * + * @param uid The [Music.UID] of the item to show. Must be a [Song] or [Album]. + */ + fun setArtistChoiceUid(uid: Music.UID) { + // Support Songs and Albums, which have parent artists. + _currentArtistChoices.value = + when (val music = musicRepository.find(uid)) { + is Song -> ArtistNavigationChoices.FromSong(music) + is Album -> ArtistNavigationChoices.FromAlbum(music) + else -> null + } + } + + private sealed interface ArtistNavigationChoices : PickerChoices { + data class FromSong(val song: Song) : ArtistNavigationChoices { + override val choices = song.artists + } + + data class FromAlbum(val album: Album) : ArtistNavigationChoices { + override val choices = album.artists + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/picker/ArtistChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/picker/ArtistChoiceAdapter.kt deleted file mode 100644 index 5c9ad275b..000000000 --- a/app/src/main/java/org/oxycblt/auxio/picker/ArtistChoiceAdapter.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2022 Auxio Project - * ArtistChoiceAdapter.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 . - */ - -package org.oxycblt.auxio.picker - -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding -import org.oxycblt.auxio.list.ClickableListListener -import org.oxycblt.auxio.list.recycler.DialogRecyclerView -import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.util.inflater - -/** - * An [RecyclerView.Adapter] that displays a list of [Artist] choices. - * - * @param listener A [ClickableListListener] to bind interactions to. - * @author OxygenCobalt. - */ -class ArtistChoiceAdapter(private val listener: ClickableListListener) : - RecyclerView.Adapter() { - private var artists = listOf() - - override fun getItemCount() = artists.size - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = - ArtistChoiceViewHolder.from(parent) - - override fun onBindViewHolder(holder: ArtistChoiceViewHolder, position: Int) = - holder.bind(artists[position], listener) - - /** - * Immediately update the [Artist] choices. - * - * @param newArtists The new [Artist]s to show. - */ - fun submitList(newArtists: List) { - if (newArtists != artists) { - artists = newArtists - @Suppress("NotifyDataSetChanged") notifyDataSetChanged() - } - } -} - -/** - * A [DialogRecyclerView.ViewHolder] that displays a smaller variant of a typical [Artist] item, for - * use with [ArtistChoiceAdapter]. Use [from] to create an instance. - */ -class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : - DialogRecyclerView.ViewHolder(binding.root) { - /** - * Bind new data to this instance. - * - * @param artist The new [Artist] to bind. - * @param listener A [ClickableListListener] to bind interactions to. - */ - fun bind(artist: Artist, listener: ClickableListListener) { - listener.bind(artist, this) - binding.pickerImage.bind(artist) - binding.pickerName.text = artist.resolveName(binding.context) - } - - companion object { - /** - * Create a new instance. - * - * @param parent The parent to inflate this instance from. - * @return A new instance. - */ - fun from(parent: View) = - ArtistChoiceViewHolder(ItemPickerChoiceBinding.inflate(parent.context.inflater)) - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/picker/ChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/picker/ChoiceAdapter.kt new file mode 100644 index 000000000..31cd2c085 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/picker/ChoiceAdapter.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 Auxio Project + * ChoiceAdapter.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 . + */ + +package org.oxycblt.auxio.picker + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding +import org.oxycblt.auxio.list.ClickableListListener +import org.oxycblt.auxio.list.adapter.FlexibleListAdapter +import org.oxycblt.auxio.list.adapter.SimpleDiffCallback +import org.oxycblt.auxio.list.recycler.DialogRecyclerView +import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.util.context +import org.oxycblt.auxio.util.inflater + +/** A [RecyclerView.Adapter] that shows a list */ +class ChoiceAdapter(private val listener: ClickableListListener) : + FlexibleListAdapter>(ChoiceViewHolder.diffCallback()) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + ChoiceViewHolder.from(parent) + + override fun onBindViewHolder(holder: ChoiceViewHolder, position: Int) = + holder.bind(getItem(position), listener) +} + +/** + * A [DialogRecyclerView.ViewHolder] that displays a smaller variant of a typical [T] item, for use + * with [ChoiceAdapter]. Use [from] to create an instance. + */ +class ChoiceViewHolder +private constructor(private val binding: ItemPickerChoiceBinding) : + DialogRecyclerView.ViewHolder(binding.root) { + /** + * Bind new data to this instance. + * + * @param music The new [T] to bind. + * @param listener A [ClickableListListener] to bind interactions to. + */ + fun bind(music: T, listener: ClickableListListener) { + listener.bind(music, this) + // ImageGroup is not generic, so we must downcast to specific types for now. + when (music) { + is Song -> binding.pickerImage.bind(music) + is Album -> binding.pickerImage.bind(music) + is Artist -> binding.pickerImage.bind(music) + is Genre -> binding.pickerImage.bind(music) + is Playlist -> binding.pickerImage.bind(music) + } + binding.pickerName.text = music.resolveName(binding.context) + } + + companion object { + + /** + * Create a new instance. + * + * @param parent The parent to inflate this instance from. + * @return A new instance. + */ + fun from(parent: View) = + ChoiceViewHolder(ItemPickerChoiceBinding.inflate(parent.context.inflater)) + + /** Get a comparator that can be used with DiffUtil. */ + fun diffCallback() = + object : SimpleDiffCallback() { + override fun areContentsTheSame(oldItem: T, newItem: T) = + oldItem.rawName == newItem.rawName + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/picker/GenreChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/picker/GenreChoiceAdapter.kt deleted file mode 100644 index b01b42928..000000000 --- a/app/src/main/java/org/oxycblt/auxio/picker/GenreChoiceAdapter.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2022 Auxio Project - * GenreChoiceAdapter.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 . - */ - -package org.oxycblt.auxio.picker - -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding -import org.oxycblt.auxio.list.ClickableListListener -import org.oxycblt.auxio.list.recycler.DialogRecyclerView -import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.util.inflater - -/** - * An [RecyclerView.Adapter] that displays a list of [Genre] choices. - * - * @param listener A [ClickableListListener] to bind interactions to. - * @author OxygenCobalt. - */ -class GenreChoiceAdapter(private val listener: ClickableListListener) : - RecyclerView.Adapter() { - private var genres = listOf() - - override fun getItemCount() = genres.size - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = - GenreChoiceViewHolder.from(parent) - - override fun onBindViewHolder(holder: GenreChoiceViewHolder, position: Int) = - holder.bind(genres[position], listener) - - /** - * Immediately update the [Genre] choices. - * - * @param newGenres The new [Genre]s to show. - */ - fun submitList(newGenres: List) { - if (newGenres != genres) { - genres = newGenres - @Suppress("NotifyDataSetChanged") notifyDataSetChanged() - } - } -} - -/** - * A [DialogRecyclerView.ViewHolder] that displays a smaller variant of a typical [Genre] item, for - * use with [GenreChoiceAdapter]. Use [from] to create an instance. - */ -class GenreChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : - DialogRecyclerView.ViewHolder(binding.root) { - /** - * Bind new data to this instance. - * - * @param genre The new [Genre] to bind. - * @param listener A [ClickableListListener] to bind interactions to. - */ - fun bind(genre: Genre, listener: ClickableListListener) { - listener.bind(genre, this) - binding.pickerImage.bind(genre) - binding.pickerName.text = genre.resolveName(binding.context) - } - - companion object { - /** - * Create a new instance. - * - * @param parent The parent to inflate this instance from. - * @return A new instance. - */ - fun from(parent: View) = - GenreChoiceViewHolder(ItemPickerChoiceBinding.inflate(parent.context.inflater)) - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/picker/PickerChoices.kt b/app/src/main/java/org/oxycblt/auxio/picker/PickerChoices.kt new file mode 100644 index 000000000..b2de58fd5 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/picker/PickerChoices.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Auxio Project + * PickerChoices.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 . + */ + +package org.oxycblt.auxio.picker + +import org.oxycblt.auxio.music.Music + +/** + * Represents a list of [Music] to show in a picker UI. + * + * @author Alexander Capehart (OxygenCobalt) + */ +interface PickerChoices { + /** The list of choices to show. */ + val choices: List +} diff --git a/app/src/main/java/org/oxycblt/auxio/picker/ArtistPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/picker/PickerDialogFragment.kt similarity index 59% rename from app/src/main/java/org/oxycblt/auxio/picker/ArtistPickerDialog.kt rename to app/src/main/java/org/oxycblt/auxio/picker/PickerDialogFragment.kt index bf8e48265..abbf498fe 100644 --- a/app/src/main/java/org/oxycblt/auxio/picker/ArtistPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/picker/PickerDialogFragment.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022 Auxio Project - * ArtistPickerDialog.kt is part of Auxio. + * Copyright (c) 2023 Auxio Project + * PickerDialogFragment.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 @@ -21,45 +21,53 @@ package org.oxycblt.auxio.picker import android.os.Bundle import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView -import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.StateFlow import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.list.ClickableListListener -import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.list.adapter.UpdateInstructions +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.util.collectImmediately /** - * The base class for dialogs that implements common behavior across all [Artist] pickers. These are - * shown whenever what to do with an item's [Artist] is ambiguous, as there are multiple [Artist]'s - * to choose from. + * A [ViewBindingDialogFragment] that acts as the base for a "picker" UI, shown when a given choice + * is ambiguous. * * @author Alexander Capehart (OxygenCobalt) */ -@AndroidEntryPoint -abstract class ArtistPickerDialog : - ViewBindingDialogFragment(), ClickableListListener { - protected val pickerModel: PickerViewModel by viewModels() +abstract class PickerDialogFragment : + ViewBindingDialogFragment(), ClickableListListener { // Okay to leak this since the Listener will not be called until after initialization. - private val artistAdapter = ArtistChoiceAdapter(@Suppress("LeakingThis") this) + private val choiceAdapter = ChoiceAdapter(@Suppress("LeakingThis") this) + + /** The string resource to use in the dialog title. */ + abstract val titleRes: Int + /** The [StateFlow] of choices to show in the picker. */ + abstract val pickerChoices: StateFlow?> + /** Called when the choice list should be initialized from the stored arguments. */ + abstract fun initChoices() override fun onCreateBinding(inflater: LayoutInflater) = DialogMusicPickerBinding.inflate(inflater) override fun onConfigDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.lbl_artists).setNegativeButton(R.string.lbl_cancel, null) + builder.setTitle(titleRes).setNegativeButton(R.string.lbl_cancel, null) } override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { - binding.pickerRecycler.adapter = artistAdapter + binding.pickerRecycler.apply { + itemAnimator = null + adapter = choiceAdapter + } - collectImmediately(pickerModel.artistChoices) { artists -> - if (artists.isNotEmpty()) { - // Make sure the artist choices align with any changes in the music library. - artistAdapter.submitList(artists) + initChoices() + collectImmediately(pickerChoices) { item -> + if (item != null) { + // Make sure the choices align with any changes in the music library. + choiceAdapter.update(item.choices, UpdateInstructions.Diff) } else { // Not showing any choices, navigate up. findNavController().navigateUp() @@ -71,7 +79,7 @@ abstract class ArtistPickerDialog : binding.pickerRecycler.adapter = null } - override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) { + override fun onClick(item: T, viewHolder: RecyclerView.ViewHolder) { findNavController().navigateUp() } } diff --git a/app/src/main/java/org/oxycblt/auxio/picker/PickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/picker/PickerViewModel.kt deleted file mode 100644 index 0ddb37953..000000000 --- a/app/src/main/java/org/oxycblt/auxio/picker/PickerViewModel.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2022 Auxio Project - * PickerViewModel.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 . - */ - -package org.oxycblt.auxio.picker - -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import org.oxycblt.auxio.music.* - -/** - * a [ViewModel] that manages the current music picker state. Make it so that the dialogs just - * contain the music themselves and then exit if the library changes. - * - * @author Alexander Capehart (OxygenCobalt) - */ -@HiltViewModel -class PickerViewModel @Inject constructor(private val musicRepository: MusicRepository) : - ViewModel(), MusicRepository.UpdateListener { - - private val _currentItem = MutableStateFlow(null) - /** The current item whose artists should be shown in the picker. Null if there is no item. */ - val currentItem: StateFlow - get() = _currentItem - - private val _artistChoices = MutableStateFlow>(listOf()) - /** The current [Artist] choices. Empty if no item is shown in the picker. */ - val artistChoices: StateFlow> - get() = _artistChoices - - private val _genreChoices = MutableStateFlow>(listOf()) - /** The current [Genre] choices. Empty if no item is shown in the picker. */ - val genreChoices: StateFlow> - get() = _genreChoices - - init { - musicRepository.addUpdateListener(this) - } - - override fun onCleared() { - musicRepository.removeUpdateListener(this) - } - - override fun onMusicChanges(changes: MusicRepository.Changes) { - if (changes.deviceLibrary && musicRepository.deviceLibrary != null) { - refreshChoices() - } - } - - /** - * Set a new [currentItem] from it's [Music.UID]. - * - * @param uid The [Music.UID] of the [Song] to update to. - */ - fun setItemUid(uid: Music.UID) { - _currentItem.value = musicRepository.find(uid) - refreshChoices() - } - - private fun refreshChoices() { - when (val item = _currentItem.value) { - is Song -> { - _artistChoices.value = item.artists - _genreChoices.value = item.genres - } - is Album -> _artistChoices.value = item.artists - else -> {} - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index 29c0d889e..b33a973ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -26,9 +26,9 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding 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.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.getAttrColorCompat diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index c185d0db7..f76a5da9a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -36,10 +36,10 @@ import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.music.MusicParent 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.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.showToast diff --git a/app/src/main/java/org/oxycblt/auxio/picker/ArtistPlaybackPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/picker/ArtistPlaybackPickerDialog.kt similarity index 70% rename from app/src/main/java/org/oxycblt/auxio/picker/ArtistPlaybackPickerDialog.kt rename to app/src/main/java/org/oxycblt/auxio/playback/picker/ArtistPlaybackPickerDialog.kt index 36a73aa81..ff8dbabfa 100644 --- a/app/src/main/java/org/oxycblt/auxio/picker/ArtistPlaybackPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/picker/ArtistPlaybackPickerDialog.kt @@ -16,18 +16,19 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.picker +package org.oxycblt.auxio.playback.picker -import android.os.Bundle import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint -import org.oxycblt.auxio.databinding.DialogMusicPickerBinding +import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.picker.PickerChoices +import org.oxycblt.auxio.picker.PickerDialogFragment import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.util.requireIs import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -36,21 +37,27 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class ArtistPlaybackPickerDialog : ArtistPickerDialog() { +class ArtistPlaybackPickerDialog : PickerDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() + private val pickerModel: PlaybackPickerViewModel by viewModels() // Information about what Song to show choices for is initially within the navigation arguments // as UIDs, as that is the only safe way to parcel a Song. private val args: ArtistPlaybackPickerDialogArgs by navArgs() - override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { - pickerModel.setItemUid(args.itemUid) - super.onBindingCreated(binding, savedInstanceState) + override val titleRes: Int + get() = R.string.lbl_artists + + override val pickerChoices: StateFlow?> + get() = pickerModel.currentArtistChoices + + override fun initChoices() { + pickerModel.setArtistChoiceUid(args.artistUid) } override fun onClick(item: Artist, viewHolder: RecyclerView.ViewHolder) { super.onClick(item, viewHolder) // User made a choice, play the given song from that artist. - val song = requireIs(unlikelyToBeNull(pickerModel.currentItem.value)) + val song = unlikelyToBeNull(pickerModel.currentArtistChoices.value).song playbackModel.playFromArtist(song, item) } } diff --git a/app/src/main/java/org/oxycblt/auxio/picker/GenrePlaybackPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/picker/GenrePlaybackPickerDialog.kt similarity index 50% rename from app/src/main/java/org/oxycblt/auxio/picker/GenrePlaybackPickerDialog.kt rename to app/src/main/java/org/oxycblt/auxio/playback/picker/GenrePlaybackPickerDialog.kt index 17441f9a9..e4af11d41 100644 --- a/app/src/main/java/org/oxycblt/auxio/picker/GenrePlaybackPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/picker/GenrePlaybackPickerDialog.kt @@ -16,26 +16,20 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.picker +package org.oxycblt.auxio.playback.picker -import android.os.Bundle -import android.view.LayoutInflater -import androidx.appcompat.app.AlertDialog import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.StateFlow import org.oxycblt.auxio.R -import org.oxycblt.auxio.databinding.DialogMusicPickerBinding -import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.picker.PickerChoices +import org.oxycblt.auxio.picker.PickerDialogFragment import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.ViewBindingDialogFragment -import org.oxycblt.auxio.util.collectImmediately -import org.oxycblt.auxio.util.requireIs import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -44,45 +38,27 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class GenrePlaybackPickerDialog : - ViewBindingDialogFragment(), ClickableListListener { - private val pickerModel: PickerViewModel by viewModels() +class GenrePlaybackPickerDialog : PickerDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() + private val pickerModel: PlaybackPickerViewModel by viewModels() // Information about what Song to show choices for is initially within the navigation arguments // as UIDs, as that is the only safe way to parcel a Song. private val args: GenrePlaybackPickerDialogArgs by navArgs() - // Okay to leak this since the Listener will not be called until after initialization. - private val genreAdapter = GenreChoiceAdapter(@Suppress("LeakingThis") this) - override fun onCreateBinding(inflater: LayoutInflater) = - DialogMusicPickerBinding.inflate(inflater) + override val titleRes: Int + get() = R.string.lbl_genres - override fun onConfigDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.lbl_genres).setNegativeButton(R.string.lbl_cancel, null) - } + override val pickerChoices: StateFlow?> + get() = pickerModel.currentGenreChoices - override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { - binding.pickerRecycler.adapter = genreAdapter - - pickerModel.setItemUid(args.itemUid) - collectImmediately(pickerModel.genreChoices) { genres -> - if (genres.isNotEmpty()) { - // Make sure the genre choices align with any changes in the music library. - genreAdapter.submitList(genres) - } else { - // Not showing any choices, navigate up. - findNavController().navigateUp() - } - } - } - - override fun onDestroyBinding(binding: DialogMusicPickerBinding) { - binding.pickerRecycler.adapter = null + override fun initChoices() { + pickerModel.setGenreChoiceUid(args.genreUid) } override fun onClick(item: Genre, viewHolder: RecyclerView.ViewHolder) { + super.onClick(item, viewHolder) // User made a choice, play the given song from that genre. - val song = requireIs(unlikelyToBeNull(pickerModel.currentItem.value)) + val song = unlikelyToBeNull(pickerModel.currentGenreChoices.value).song playbackModel.playFromGenre(song, item) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt new file mode 100644 index 000000000..2fcd20e13 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Auxio Project + * PlaybackPickerViewModel.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 . + */ + +package org.oxycblt.auxio.playback.picker + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.picker.PickerChoices + +/** + * A [ViewModel] that stores the choices shown in the playback picker dialogs. + * + * @author OxygenCobalt (Alexander Capehart) + */ +@HiltViewModel +class PlaybackPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) : + ViewModel(), MusicRepository.UpdateListener { + private val _currentArtistChoices = MutableStateFlow(null) + /** The current set of [Artist] choices to show in the picker, or null if to show nothing. */ + val currentArtistChoices: StateFlow + get() = _currentArtistChoices + + private val _currentGenreChoices = MutableStateFlow(null) + /** The current set of [Genre] choices to show in the picker, or null if to show nothing. */ + val currentGenreChoices: StateFlow + get() = _currentGenreChoices + + init { + musicRepository.addUpdateListener(this) + } + + override fun onMusicChanges(changes: MusicRepository.Changes) { + if (!changes.deviceLibrary) return + val deviceLibrary = musicRepository.deviceLibrary ?: return + _currentArtistChoices.value = + _currentArtistChoices.value?.run { + deviceLibrary.findSong(song.uid)?.let { newSong -> ArtistPlaybackChoices(newSong) } + } + _currentGenreChoices.value = + _currentGenreChoices.value?.run { + deviceLibrary.findSong(song.uid)?.let { newSong -> GenrePlaybackChoices(newSong) } + } + } + + override fun onCleared() { + super.onCleared() + musicRepository.removeUpdateListener(this) + } + + /** + * Set the [Music.UID] of the item to show [Artist] choices for. + * + * @param uid The [Music.UID] of the item to show. Must be a [Song]. + */ + fun setArtistChoiceUid(uid: Music.UID) { + _currentArtistChoices.value = + musicRepository.deviceLibrary?.findSong(uid)?.let { ArtistPlaybackChoices(it) } + } + + /** + * Set the [Music.UID] of the item to show [Genre] choices for. + * + * @param uid The [Music.UID] of the item to show. Must be a [Song]. + */ + fun setGenreChoiceUid(uid: Music.UID) { + _currentGenreChoices.value = + musicRepository.deviceLibrary?.findSong(uid)?.let { GenrePlaybackChoices(it) } + } +} + +data class ArtistPlaybackChoices(val song: Song) : PickerChoices { + override val choices = song.artists +} + +data class GenrePlaybackChoices(val song: Song) : PickerChoices { + override val choices = song.genres +} diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index b7b693a85..6bf06014d 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -37,8 +37,8 @@ import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.selection.SelectionViewModel import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.navigation.NavigationViewModel import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.util.* /** diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 8bc813c48..6c1e9e91b 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -98,8 +98,8 @@ constructor( builder .size(getSafeRemoteViewsImageSize(context, 10f)) .transformations( - SquareFrameTransform.INSTANCE, - RoundedCornersTransformation(cornerRadius.toFloat())) + SquareFrameTransform.INSTANCE, + RoundedCornersTransformation(cornerRadius.toFloat())) } else { builder.size(getSafeRemoteViewsImageSize(context)) } diff --git a/app/src/main/res/navigation/nav_main.xml b/app/src/main/res/navigation/nav_main.xml index 69a13a74c..22d5f7343 100644 --- a/app/src/main/res/navigation/nav_main.xml +++ b/app/src/main/res/navigation/nav_main.xml @@ -30,31 +30,31 @@ @@ -64,7 +64,7 @@ android:label="song_detail_dialog" tools:layout="@layout/dialog_song_detail">