From 32d01f2027c1c4090112ecb11d747ff5036b77a3 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sun, 18 Dec 2022 10:57:33 -0700 Subject: [PATCH] all: refactor list management Refactor list management (largely callbacks) into a declarative system. This should make it easier to re-use selection components across the app. --- .../java/org/oxycblt/auxio/MainFragment.kt | 46 ++-- .../auxio/detail/AlbumDetailFragment.kt | 88 +++---- .../auxio/detail/ArtistDetailFragment.kt | 78 ++++--- .../auxio/detail/DetailAppBarLayout.kt | 2 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 4 +- .../auxio/detail/GenreDetailFragment.kt | 51 ++-- .../oxycblt/auxio/detail/SongDetailDialog.kt | 2 +- .../detail/recycler/AlbumDetailAdapter.kt | 45 ++-- .../detail/recycler/ArtistDetailAdapter.kt | 35 ++- .../auxio/detail/recycler/DetailAdapter.kt | 39 ++-- .../detail/recycler/GenreDetailAdapter.kt | 23 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 217 ++++++++---------- .../org/oxycblt/auxio/home/HomeViewModel.kt | 8 +- .../fastscroll/FastScrollPopupView.kt | 2 +- .../fastscroll/FastScrollRecyclerView.kt | 25 +- .../auxio/home/list/AlbumListFragment.kt | 65 ++++-- .../auxio/home/list/ArtistListFragment.kt | 49 ++-- .../auxio/home/list/GenreListFragment.kt | 49 ++-- .../auxio/home/list/HomeListFragment.kt | 86 ------- .../auxio/home/list/SongListFragment.kt | 51 ++-- .../org/oxycblt/auxio/home/tabs/TabAdapter.kt | 20 +- .../auxio/home/tabs/TabCustomizeDialog.kt | 10 +- .../main/java/org/oxycblt/auxio/list/List.kt | 56 +++++ .../{ui/fragment => list}/MenuFragment.kt | 55 ++--- .../oxycblt/auxio/list/SelectionFragment.kt | 70 ++++++ .../SelectionToolbarOverlay.kt | 37 +-- .../selection => list}/SelectionViewModel.kt | 18 +- .../recycler/AuxioRecyclerView.kt | 2 +- .../recycler/DialogRecyclerView.kt | 2 +- .../recycler/PlayingIndicatorAdapter.kt | 3 +- .../recycler/SelectionIndicatorAdapter.kt | 4 +- .../auxio/list/recycler/SimpleItemCallback.kt | 29 +++ .../recycler/SyncListDiffer.kt} | 36 +-- .../{ui => list}/recycler/ViewHolders.kt | 79 ++++++- .../java/org/oxycblt/auxio/music/Music.kt | 2 +- .../org/oxycblt/auxio/music/MusicStore.kt | 3 - .../auxio/music/extractor/SeparatorsDialog.kt | 2 +- .../auxio/music/picker/ArtistChoiceAdapter.kt | 12 +- .../picker/ArtistNavigationPickerDialog.kt | 13 +- .../auxio/music/picker/ArtistPickerDialog.kt | 16 +- .../picker/ArtistPlaybackPickerDialog.kt | 7 +- .../auxio/music/storage/MusicDirAdapter.kt | 2 +- .../auxio/music/storage/MusicDirsDialog.kt | 11 +- .../music/system/IndexerNotifications.kt | 2 +- .../auxio/music/system/IndexerService.kt | 2 +- .../auxio/playback/PlaybackBarFragment.kt | 39 ++-- ...vior.kt => PlaybackBottomSheetBehavior.kt} | 6 +- .../auxio/playback/PlaybackPanelFragment.kt | 44 ++-- .../auxio/playback/queue/QueueAdapter.kt | 6 +- ...ehavior.kt => QueueBottomSheetBehavior.kt} | 6 +- .../auxio/playback/queue/QueueFragment.kt | 24 +- .../replaygain/PreAmpCustomizeDialog.kt | 2 +- .../playback/system/NotificationComponent.kt | 2 +- .../auxio/playback/system/PlaybackService.kt | 2 +- .../auxio/playback/ui/StyledSeekBar.kt | 11 +- .../org/oxycblt/auxio/search/SearchAdapter.kt | 13 +- .../oxycblt/auxio/search/SearchFragment.kt | 162 ++++++------- .../oxycblt/auxio/search/SearchViewModel.kt | 4 +- .../oxycblt/auxio/settings/AboutFragment.kt | 2 +- .../org/oxycblt/auxio/settings/Settings.kt | 2 +- .../auxio/settings/SettingsFragment.kt | 2 +- .../auxio/{ui => settings}/accent/Accent.kt | 5 +- .../{ui => settings}/accent/AccentAdapter.kt | 16 +- .../accent/AccentCustomizeDialog.kt | 16 +- .../accent/AccentGridLayoutManager.kt | 2 +- .../settings/prefs/PreferenceFragment.kt | 2 +- .../auxio/{ui => shared}/AuxioAppBarLayout.kt | 2 +- .../AuxioBottomSheetBehavior.kt} | 4 +- .../BottomSheetContentBehavior.kt | 2 +- .../system => shared}/ForegroundManager.kt | 2 +- .../{ui => shared}/NavigationViewModel.kt | 4 +- .../system => shared}/ServiceNotification.kt | 2 +- .../ViewBindingDialogFragment.kt | 2 +- .../ViewBindingFragment.kt | 2 +- .../res/layout-w600dp-land/fragment_main.xml | 4 +- app/src/main/res/layout/dialog_accent.xml | 4 +- .../main/res/layout/dialog_music_picker.xml | 2 +- app/src/main/res/layout/dialog_tabs.xml | 2 +- app/src/main/res/layout/fragment_about.xml | 4 +- app/src/main/res/layout/fragment_detail.xml | 2 +- app/src/main/res/layout/fragment_home.xml | 8 +- .../main/res/layout/fragment_home_list.xml | 2 +- app/src/main/res/layout/fragment_main.xml | 8 +- app/src/main/res/layout/fragment_queue.xml | 2 +- app/src/main/res/layout/fragment_search.xml | 10 +- app/src/main/res/layout/fragment_settings.xml | 4 +- app/src/main/res/navigation/nav_main.xml | 2 +- 87 files changed, 989 insertions(+), 911 deletions(-) rename app/src/main/java/org/oxycblt/auxio/{ui => home}/fastscroll/FastScrollPopupView.kt (99%) rename app/src/main/java/org/oxycblt/auxio/{ui => home}/fastscroll/FastScrollRecyclerView.kt (96%) delete mode 100644 app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/list/List.kt rename app/src/main/java/org/oxycblt/auxio/{ui/fragment => list}/MenuFragment.kt (83%) create mode 100644 app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt rename app/src/main/java/org/oxycblt/auxio/{ui/selection => list}/SelectionToolbarOverlay.kt (83%) rename app/src/main/java/org/oxycblt/auxio/{ui/selection => list}/SelectionViewModel.kt (80%) rename app/src/main/java/org/oxycblt/auxio/{ui => list}/recycler/AuxioRecyclerView.kt (98%) rename app/src/main/java/org/oxycblt/auxio/{ui => list}/recycler/DialogRecyclerView.kt (98%) rename app/src/main/java/org/oxycblt/auxio/{ui => list}/recycler/PlayingIndicatorAdapter.kt (97%) rename app/src/main/java/org/oxycblt/auxio/{ui => list}/recycler/SelectionIndicatorAdapter.kt (96%) create mode 100644 app/src/main/java/org/oxycblt/auxio/list/recycler/SimpleItemCallback.kt rename app/src/main/java/org/oxycblt/auxio/{ui/recycler/Data.kt => list/recycler/SyncListDiffer.kt} (80%) rename app/src/main/java/org/oxycblt/auxio/{ui => list}/recycler/ViewHolders.kt (74%) rename app/src/main/java/org/oxycblt/auxio/playback/{PlaybackSheetBehavior.kt => PlaybackBottomSheetBehavior.kt} (91%) rename app/src/main/java/org/oxycblt/auxio/playback/queue/{QueueSheetBehavior.kt => QueueBottomSheetBehavior.kt} (92%) rename app/src/main/java/org/oxycblt/auxio/{ui => settings}/accent/Accent.kt (97%) rename app/src/main/java/org/oxycblt/auxio/{ui => settings}/accent/AccentAdapter.kt (88%) rename app/src/main/java/org/oxycblt/auxio/{ui => settings}/accent/AccentCustomizeDialog.kt (84%) rename app/src/main/java/org/oxycblt/auxio/{ui => settings}/accent/AccentGridLayoutManager.kt (98%) rename app/src/main/java/org/oxycblt/auxio/{ui => shared}/AuxioAppBarLayout.kt (99%) rename app/src/main/java/org/oxycblt/auxio/{ui/AuxioSheetBehavior.kt => shared/AuxioBottomSheetBehavior.kt} (96%) rename app/src/main/java/org/oxycblt/auxio/{ui => shared}/BottomSheetContentBehavior.kt (99%) rename app/src/main/java/org/oxycblt/auxio/{ui/system => shared}/ForegroundManager.kt (98%) rename app/src/main/java/org/oxycblt/auxio/{ui => shared}/NavigationViewModel.kt (98%) rename app/src/main/java/org/oxycblt/auxio/{ui/system => shared}/ServiceNotification.kt (98%) rename app/src/main/java/org/oxycblt/auxio/{ui/fragment => shared}/ViewBindingDialogFragment.kt (99%) rename app/src/main/java/org/oxycblt/auxio/{ui/fragment => shared}/ViewBindingFragment.kt (99%) diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index d29110e55..2a44c3193 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -26,8 +26,6 @@ import androidx.core.view.ViewCompat import androidx.core.view.isInvisible import androidx.core.view.updatePadding 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.bottomsheet.NeoBottomSheetBehavior @@ -39,13 +37,12 @@ import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.playback.PlaybackSheetBehavior +import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.playback.queue.QueueSheetBehavior -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel -import org.oxycblt.auxio.ui.fragment.ViewBindingFragment -import org.oxycblt.auxio.ui.selection.SelectionViewModel +import org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior +import org.oxycblt.auxio.shared.MainNavigationAction +import org.oxycblt.auxio.shared.NavigationViewModel +import org.oxycblt.auxio.shared.ViewBindingFragment import org.oxycblt.auxio.util.* /** @@ -83,17 +80,17 @@ class MainFragment : insets } - // Send meaningful accessibility events for bottom sheets ViewCompat.setAccessibilityPaneTitle( binding.playbackSheet, context.getString(R.string.lbl_playback)) ViewCompat.setAccessibilityPaneTitle( binding.queueSheet, context.getString(R.string.lbl_queue)) - val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + val queueSheetBehavior = + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? if (queueSheetBehavior != null) { val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior unlikelyToBeNull(binding.handleWrapper).setOnClickListener { if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED && @@ -146,7 +143,7 @@ class MainFragment : val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f) @@ -154,7 +151,8 @@ class MainFragment : val halfOutRatio = min(playbackRatio * 2, 1f) val halfInPlaybackRatio = max(playbackRatio - 0.5f, 0f) * 2 - val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + val queueSheetBehavior = + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? if (queueSheetBehavior != null) { // Queue sheet, take queue into account so the playback bar is shown and the playback @@ -262,7 +260,7 @@ class MainFragment : private fun tryExpandAll() { val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) { // State is collapsed and non-hidden, expand @@ -273,12 +271,12 @@ class MainFragment : private fun tryCollapseAll() { val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) { // Make sure the queue is also collapsed here. val queueSheetBehavior = - binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED queueSheetBehavior?.state = NeoBottomSheetBehavior.STATE_COLLAPSED @@ -288,11 +286,11 @@ class MainFragment : private fun tryUnhideAll() { val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_HIDDEN) { val queueSheetBehavior = - binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? // Queue sheet behavior is either collapsed or expanded, no hiding needed queueSheetBehavior?.isDraggable = true @@ -308,11 +306,11 @@ class MainFragment : private fun tryHideAll() { val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior if (playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN) { val queueSheetBehavior = - binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? // Make these views non-draggable so the user can't halt the hiding event. @@ -336,9 +334,9 @@ class MainFragment : override fun handleOnBackPressed() { val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior val queueSheetBehavior = - binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? if (queueSheetBehavior != null && queueSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED && @@ -361,9 +359,9 @@ class MainFragment : fun updateEnabledState() { val binding = requireBinding() val playbackSheetBehavior = - binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior + binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior val queueSheetBehavior = - binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? + binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior? val exploreNavController = binding.exploreNavHost.findNavController() 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 b851b697b..c4e644c61 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -22,7 +22,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View -import androidx.appcompat.widget.Toolbar import androidx.core.view.children import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController @@ -32,6 +31,8 @@ import com.google.android.material.transition.MaterialSharedAxis import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.MenuFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music @@ -40,8 +41,6 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.MenuFragment -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately @@ -54,16 +53,23 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * A fragment that shows information for a particular [Album]. * @author OxygenCobalt */ -class AlbumDetailFragment : - MenuFragment(), - Toolbar.OnMenuItemClickListener, - AlbumDetailAdapter.Listener { +class AlbumDetailFragment : MenuFragment() { private val detailModel: DetailViewModel by activityViewModels() private val args: AlbumDetailFragmentArgs by navArgs() - private val detailAdapter = AlbumDetailAdapter(this) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } + private val detailAdapter = + AlbumDetailAdapter( + AlbumDetailAdapter.Callback( + ::handleClick, + ::handleOpenItemMenu, + {}, + ::handlePlay, + ::handleShuffle, + ::handleOpenSortMenu, + ::handleArtistNavigation)) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) @@ -80,14 +86,17 @@ class AlbumDetailFragment : binding.detailToolbar.apply { inflateMenu(R.menu.menu_album_detail) setNavigationOnClickListener { findNavController().navigateUp() } - setOnMenuItemClickListener(this@AlbumDetailFragment) + setOnMenuItemClickListener { + handleDetailMenuItem(it) + true + } } binding.detailRecycler.adapter = detailAdapter // -- VIEWMODEL SETUP --- - collectImmediately(detailModel.currentAlbum, ::handleItemChange) + collectImmediately(detailModel.currentAlbum, ::updateItem) collectImmediately(detailModel.albumData, detailAdapter::submitList) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -96,35 +105,10 @@ class AlbumDetailFragment : override fun onDestroyBinding(binding: FragmentDetailBinding) { super.onDestroyBinding(binding) - binding.detailToolbar.apply { - setNavigationOnClickListener(null) - setOnMenuItemClickListener(null) - } - binding.detailRecycler.adapter = null } - override fun onMenuItemClick(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_play_next -> { - playbackModel.playNext(unlikelyToBeNull(detailModel.currentAlbum.value)) - requireContext().showToast(R.string.lng_queue_added) - true - } - R.id.action_queue_add -> { - playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentAlbum.value)) - requireContext().showToast(R.string.lng_queue_added) - true - } - R.id.action_go_artist -> { - onNavigateToArtist() - true - } - else -> false - } - } - - override fun onItemClick(item: Item) { + private fun handleClick(item: Item) { check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" } when (settings.detailPlaybackMode) { null, @@ -135,21 +119,21 @@ class AlbumDetailFragment : } } - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenItemMenu(item: Item, anchor: View) { check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" } - musicMenu(anchor, R.menu.menu_album_song_actions, item) + openMusicMenu(anchor, R.menu.menu_album_song_actions, item) } - override fun onPlayParent() { + private fun handlePlay() { playbackModel.play(unlikelyToBeNull(detailModel.currentAlbum.value)) } - override fun onShuffleParent() { + private fun handleShuffle() { playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value)) } - override fun onShowSortMenu(anchor: View) { - menu(anchor, R.menu.menu_album_sort) { + private fun handleOpenSortMenu(anchor: View) { + openMenu(anchor, R.menu.menu_album_sort) { val sort = detailModel.albumSort unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true unlikelyToBeNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending @@ -166,11 +150,27 @@ class AlbumDetailFragment : } } - override fun onNavigateToArtist() { + private fun handleArtistNavigation() { navModel.exploreNavigateTo(unlikelyToBeNull(detailModel.currentAlbum.value).artists) } - private fun handleItemChange(album: Album?) { + private fun handleDetailMenuItem(item: MenuItem) { + when (item.itemId) { + R.id.action_play_next -> { + playbackModel.playNext(unlikelyToBeNull(detailModel.currentAlbum.value)) + requireContext().showToast(R.string.lng_queue_added) + } + R.id.action_queue_add -> { + playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentAlbum.value)) + requireContext().showToast(R.string.lng_queue_added) + } + R.id.action_go_artist -> { + handleArtistNavigation() + } + } + } + + private fun updateItem(album: Album?) { if (album == null) { findNavController().navigateUp() return 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 3b20d74f6..4207a6d2f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -21,7 +21,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View -import androidx.appcompat.widget.Toolbar import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -30,6 +29,8 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter import org.oxycblt.auxio.detail.recycler.DetailAdapter +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.MenuFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music @@ -38,8 +39,6 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.MenuFragment -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context @@ -51,14 +50,22 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * A fragment that shows information for a particular [Artist]. * @author OxygenCobalt */ -class ArtistDetailFragment : - MenuFragment(), Toolbar.OnMenuItemClickListener, DetailAdapter.Listener { +class ArtistDetailFragment : MenuFragment() { private val detailModel: DetailViewModel by activityViewModels() private val args: ArtistDetailFragmentArgs by navArgs() - private val detailAdapter = ArtistDetailAdapter(this) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } + private val detailAdapter = + ArtistDetailAdapter( + DetailAdapter.Callback( + ::handleClick, + ::handleOpenItemMenu, + {}, + ::handlePlay, + ::handleShuffle, + ::handleOpenSortMenu)) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) @@ -75,14 +82,17 @@ class ArtistDetailFragment : binding.detailToolbar.apply { inflateMenu(R.menu.menu_genre_artist_detail) setNavigationOnClickListener { findNavController().navigateUp() } - setOnMenuItemClickListener(this@ArtistDetailFragment) + setOnMenuItemClickListener { + handleDetailMenuItem(it) + true + } } binding.detailRecycler.adapter = detailAdapter // --- VIEWMODEL SETUP --- - collectImmediately(detailModel.currentArtist, ::handleItemChange) + collectImmediately(detailModel.currentArtist, ::updateItem) collectImmediately(detailModel.artistData, detailAdapter::submitList) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -91,31 +101,10 @@ class ArtistDetailFragment : override fun onDestroyBinding(binding: FragmentDetailBinding) { super.onDestroyBinding(binding) - binding.detailToolbar.apply { - setNavigationOnClickListener(null) - setOnMenuItemClickListener(null) - } - binding.detailRecycler.adapter = null } - override fun onMenuItemClick(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_play_next -> { - playbackModel.playNext(unlikelyToBeNull(detailModel.currentArtist.value)) - requireContext().showToast(R.string.lng_queue_added) - true - } - R.id.action_queue_add -> { - playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentArtist.value)) - requireContext().showToast(R.string.lng_queue_added) - true - } - else -> false - } - } - - override fun onItemClick(item: Item) { + private fun handleClick(item: Item) { when (item) { is Song -> { when (settings.detailPlaybackMode) { @@ -133,24 +122,24 @@ class ArtistDetailFragment : } } - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenItemMenu(item: Item, anchor: View) { when (item) { - is Song -> musicMenu(anchor, R.menu.menu_artist_song_actions, item) - is Album -> musicMenu(anchor, R.menu.menu_artist_album_actions, item) + is Song -> openMusicMenu(anchor, R.menu.menu_artist_song_actions, item) + is Album -> openMusicMenu(anchor, R.menu.menu_artist_album_actions, item) else -> error("Unexpected datatype: ${item::class.simpleName}") } } - override fun onPlayParent() { + private fun handlePlay() { playbackModel.play(unlikelyToBeNull(detailModel.currentArtist.value)) } - override fun onShuffleParent() { + private fun handleShuffle() { playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value)) } - override fun onShowSortMenu(anchor: View) { - menu(anchor, R.menu.menu_artist_sort) { + private fun handleOpenSortMenu(anchor: View) { + openMenu(anchor, R.menu.menu_artist_sort) { val sort = detailModel.artistSort unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true unlikelyToBeNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending @@ -169,7 +158,20 @@ class ArtistDetailFragment : } } - private fun handleItemChange(artist: Artist?) { + private fun handleDetailMenuItem(item: MenuItem) { + when (item.itemId) { + R.id.action_play_next -> { + playbackModel.playNext(unlikelyToBeNull(detailModel.currentArtist.value)) + requireContext().showToast(R.string.lng_queue_added) + } + R.id.action_queue_add -> { + playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentArtist.value)) + requireContext().showToast(R.string.lng_queue_added) + } + } + } + + private fun updateItem(artist: Artist?) { if (artist == null) { findNavController().navigateUp() return diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index 568dcb10a..3c355789b 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -31,7 +31,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout import java.lang.reflect.Field import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.AuxioAppBarLayout +import org.oxycblt.auxio.shared.AuxioAppBarLayout import org.oxycblt.auxio.util.lazyReflectedField /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 950565bd2..e0ca3863c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.yield import org.oxycblt.auxio.R +import org.oxycblt.auxio.list.Header +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -40,8 +42,6 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.storage.MimeType import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.recycler.Header -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW 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 04166535d..5273e1c9d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -21,7 +21,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View -import androidx.appcompat.widget.Toolbar import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -30,6 +29,8 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.detail.recycler.DetailAdapter import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.MenuFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -39,8 +40,6 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.MenuFragment -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context @@ -52,14 +51,21 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * A fragment that shows information for a particular [Genre]. * @author OxygenCobalt */ -class GenreDetailFragment : - MenuFragment(), Toolbar.OnMenuItemClickListener, DetailAdapter.Listener { +class GenreDetailFragment : MenuFragment() { private val detailModel: DetailViewModel by activityViewModels() private val args: GenreDetailFragmentArgs by navArgs() - private val detailAdapter = GenreDetailAdapter(this) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } + private val detailAdapter = + GenreDetailAdapter( + DetailAdapter.Callback( + ::handleClick, + ::handleOpenItemMenu, + {}, + ::handlePlay, + ::handleShuffle, + ::handleOpenSortMenu)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) @@ -76,7 +82,10 @@ class GenreDetailFragment : binding.detailToolbar.apply { inflateMenu(R.menu.menu_genre_artist_detail) setNavigationOnClickListener { findNavController().navigateUp() } - setOnMenuItemClickListener(this@GenreDetailFragment) + setOnMenuItemClickListener { + handleDetailMenuItem(it) + true + } } binding.detailRecycler.adapter = detailAdapter @@ -92,31 +101,23 @@ class GenreDetailFragment : override fun onDestroyBinding(binding: FragmentDetailBinding) { super.onDestroyBinding(binding) - binding.detailToolbar.apply { - setNavigationOnClickListener(null) - setOnMenuItemClickListener(null) - } - binding.detailRecycler.adapter = null } - override fun onMenuItemClick(item: MenuItem): Boolean { - return when (item.itemId) { + private fun handleDetailMenuItem(item: MenuItem) { + when (item.itemId) { R.id.action_play_next -> { playbackModel.playNext(unlikelyToBeNull(detailModel.currentGenre.value)) requireContext().showToast(R.string.lng_queue_added) - true } R.id.action_queue_add -> { playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentGenre.value)) requireContext().showToast(R.string.lng_queue_added) - true } - else -> false } } - override fun onItemClick(item: Item) { + private fun handleClick(item: Item) { when (item) { is Artist -> navModel.exploreNavigateTo(item) is Song -> @@ -133,24 +134,24 @@ class GenreDetailFragment : } } - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenItemMenu(item: Item, anchor: View) { when (item) { - is Artist -> musicMenu(anchor, R.menu.menu_artist_actions, item) - is Song -> musicMenu(anchor, R.menu.menu_song_actions, item) + is Artist -> openMusicMenu(anchor, R.menu.menu_artist_actions, item) + is Song -> openMusicMenu(anchor, R.menu.menu_song_actions, item) else -> error("Unexpected datatype: ${item::class.simpleName}") } } - override fun onPlayParent() { + private fun handlePlay() { playbackModel.play(unlikelyToBeNull(detailModel.currentGenre.value)) } - override fun onShuffleParent() { + private fun handleShuffle() { playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value)) } - override fun onShowSortMenu(anchor: View) { - menu(anchor, R.menu.menu_genre_sort) { + private fun handleOpenSortMenu(anchor: View) { + openMenu(anchor, R.menu.menu_genre_sort) { val sort = detailModel.genreSort unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true unlikelyToBeNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending 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 cc7da68ea..1836df8f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -27,7 +27,7 @@ import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSongDetailBinding import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index 34972aea3..017c741d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -27,13 +27,12 @@ import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding import org.oxycblt.auxio.detail.DiscHeader +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SimpleItemCallback import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.PlayingIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater @@ -42,8 +41,8 @@ import org.oxycblt.auxio.util.inflater * An adapter for displaying [Album] information and it's children. * @author OxygenCobalt */ -class AlbumDetailAdapter(private val listener: Listener) : - DetailAdapter(listener, DIFFER) { +class AlbumDetailAdapter(private val callback: AlbumDetailAdapter.Callback) : + DetailAdapter(callback, DIFFER) { override fun getItemViewType(position: Int) = when (differ.currentList[position]) { @@ -70,9 +69,9 @@ class AlbumDetailAdapter(private val listener: Listener) : if (payloads.isEmpty()) { when (val item = differ.currentList[position]) { - is Album -> (holder as AlbumDetailViewHolder).bind(item, listener) + is Album -> (holder as AlbumDetailViewHolder).bind(item, callback) is DiscHeader -> (holder as DiscHeaderViewHolder).bind(item) - is Song -> (holder as AlbumSongViewHolder).bind(item, listener) + is Song -> (holder as AlbumSongViewHolder).bind(item, callback) } } } @@ -99,15 +98,23 @@ class AlbumDetailAdapter(private val listener: Listener) : } } - interface Listener : DetailAdapter.Listener { - fun onNavigateToArtist() - } + class Callback( + onClick: (Item) -> Unit, + onOpenItemMenu: (Item, View) -> Unit, + onSelect: (Item) -> Unit, + onPlay: () -> Unit, + onShuffle: () -> Unit, + onOpenSortMenu: (View) -> Unit, + val onNavigateToArtist: () -> Unit + ) : + DetailAdapter.Callback( + onClick, onOpenItemMenu, onSelect, onPlay, onShuffle, onOpenSortMenu) } private class AlbumDetailViewHolder private constructor(private val binding: ItemDetailBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Album, listener: AlbumDetailAdapter.Listener) { + fun bind(item: Album, callback: AlbumDetailAdapter.Callback) { binding.detailCover.bind(item) binding.detailType.text = binding.context.getString(item.releaseType.stringRes) @@ -115,7 +122,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite binding.detailSubhead.apply { text = item.resolveArtistContents(context) - setOnClickListener { listener.onNavigateToArtist() } + setOnClickListener { callback.onNavigateToArtist() } } binding.detailInfo.apply { @@ -128,8 +135,8 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite text = context.getString(R.string.fmt_three, date, songCount, duration) } - binding.detailPlayButton.setOnClickListener { listener.onPlayParent() } - binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() } + binding.detailPlayButton.setOnClickListener { callback.onPlay() } + binding.detailShuffleButton.setOnClickListener { callback.onShuffle() } } companion object { @@ -174,7 +181,7 @@ class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) : private class AlbumSongViewHolder private constructor(private val binding: ItemAlbumSongBinding) : PlayingIndicatorAdapter.ViewHolder(binding.root) { - fun bind(item: Song, listener: MenuItemListener) { + fun bind(item: Song, callback: AlbumDetailAdapter.Callback) { // Hide the track number view if the song does not have a track. if (item.track != null) { binding.songTrack.apply { @@ -193,11 +200,11 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA binding.songName.text = item.resolveName(binding.context) binding.songDuration.text = item.durationMs.formatDurationMs(false) - binding.songMenu.setOnClickListener { listener.onOpenMenu(item, it) } + binding.songMenu.setOnClickListener { callback.onOpenMenu(item, it) } binding.root.apply { - setOnClickListener { listener.onItemClick(item) } + setOnClickListener { callback.onClick(item) } setOnLongClickListener { - listener.onSelect(item) + callback.onSelect(item) true } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt index 827d2f5a2..2ee1c891c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt @@ -26,13 +26,13 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.databinding.ItemParentBinding import org.oxycblt.auxio.databinding.ItemSongBinding +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.ItemMenuCallback +import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SimpleItemCallback import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.PlayingIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater @@ -42,8 +42,7 @@ import org.oxycblt.auxio.util.inflater * one actually contains both album information and song information. * @author OxygenCobalt */ -class ArtistDetailAdapter(private val listener: Listener) : - DetailAdapter(listener, DIFFER) { +class ArtistDetailAdapter(private val callback: Callback) : DetailAdapter(callback, DIFFER) { override fun getItemViewType(position: Int) = when (differ.currentList[position]) { @@ -70,9 +69,9 @@ class ArtistDetailAdapter(private val listener: Listener) : if (payloads.isEmpty()) { when (val item = differ.currentList[position]) { - is Artist -> (holder as ArtistDetailViewHolder).bind(item, listener) - is Album -> (holder as ArtistAlbumViewHolder).bind(item, listener) - is Song -> (holder as ArtistSongViewHolder).bind(item, listener) + is Artist -> (holder as ArtistDetailViewHolder).bind(item, callback) + is Album -> (holder as ArtistAlbumViewHolder).bind(item, callback) + is Song -> (holder as ArtistSongViewHolder).bind(item, callback) } } } @@ -103,7 +102,7 @@ class ArtistDetailAdapter(private val listener: Listener) : private class ArtistDetailViewHolder private constructor(private val binding: ItemDetailBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Artist, listener: DetailAdapter.Listener) { + fun bind(item: Artist, callback: DetailAdapter.Callback) { binding.detailCover.bind(item) binding.detailType.text = binding.context.getString(R.string.lbl_artist) binding.detailName.text = item.resolveName(binding.context) @@ -132,8 +131,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It binding.detailShuffleButton.isVisible = false } - binding.detailPlayButton.setOnClickListener { listener.onPlayParent() } - binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() } + binding.detailPlayButton.setOnClickListener { callback.onPlay() } + binding.detailShuffleButton.setOnClickListener { callback.onShuffle() } } companion object { @@ -155,13 +154,13 @@ private class ArtistDetailViewHolder private constructor(private val binding: It private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) : PlayingIndicatorAdapter.ViewHolder(binding.root) { - fun bind(item: Album, listener: MenuItemListener) { + fun bind(item: Album, callback: ItemMenuCallback) { binding.parentImage.bind(item) binding.parentName.text = item.resolveName(binding.context) binding.parentInfo.text = item.date?.resolveDate(binding.context) ?: binding.context.getString(R.string.def_date) - binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) } - binding.root.setOnClickListener { listener.onItemClick(item) } + binding.parentMenu.setOnClickListener { callback.onOpenMenu(item, it) } + binding.root.setOnClickListener { callback.onClick(item) } } override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { @@ -185,12 +184,12 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) : PlayingIndicatorAdapter.ViewHolder(binding.root) { - fun bind(item: Song, listener: MenuItemListener) { + fun bind(item: Song, callback: ItemMenuCallback) { binding.songAlbumCover.bind(item) binding.songName.text = item.resolveName(binding.context) binding.songInfo.text = item.album.resolveName(binding.context) - binding.songMenu.setOnClickListener { listener.onOpenMenu(item, it) } - binding.root.setOnClickListener { listener.onItemClick(item) } + binding.songMenu.setOnClickListener { callback.onOpenMenu(item, it) } + binding.root.setOnClickListener { callback.onClick(item) } } override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt index c3fb3896b..a455742ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt @@ -26,22 +26,20 @@ import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.databinding.ItemSortHeaderBinding import org.oxycblt.auxio.detail.SortHeader -import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView -import org.oxycblt.auxio.ui.recycler.Header -import org.oxycblt.auxio.ui.recycler.HeaderViewHolder -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.PlayingIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SimpleItemCallback +import org.oxycblt.auxio.list.Header +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.ItemSelectCallback +import org.oxycblt.auxio.list.recycler.AuxioRecyclerView +import org.oxycblt.auxio.list.recycler.HeaderViewHolder +import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.inflater -abstract class DetailAdapter( - private val listener: L, +abstract class DetailAdapter( + private val callback: Callback, diffCallback: DiffUtil.ItemCallback ) : PlayingIndicatorAdapter(), AuxioRecyclerView.SpanSizeLookup { - private var isPlaying = false - @Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size override fun getItemViewType(position: Int) = @@ -71,7 +69,7 @@ abstract class DetailAdapter( if (payloads.isEmpty()) { when (item) { is Header -> (holder as HeaderViewHolder).bind(item) - is SortHeader -> (holder as SortHeaderViewHolder).bind(item, listener) + is SortHeader -> (holder as SortHeaderViewHolder).bind(item, callback) } } @@ -107,20 +105,23 @@ abstract class DetailAdapter( } } - interface Listener : MenuItemListener { - fun onPlayParent() - fun onShuffleParent() - fun onShowSortMenu(anchor: View) - } + open class Callback( + onClick: (Item) -> Unit, + onOpenItemMenu: (Item, View) -> Unit, + onSelect: (Item) -> Unit, + val onPlay: () -> Unit, + val onShuffle: () -> Unit, + val onOpenSortMenu: (View) -> Unit + ) : ItemSelectCallback(onClick, onOpenItemMenu, onSelect) } class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: SortHeader, listener: DetailAdapter.Listener) { + fun bind(item: SortHeader, callback: DetailAdapter.Callback) { binding.headerTitle.text = binding.context.getString(item.string) binding.headerButton.apply { TooltipCompat.setTooltipText(this, contentDescription) - setOnClickListener(listener::onShowSortMenu) + setOnClickListener(callback.onOpenSortMenu) } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt index fadc2604a..1e63f39f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt @@ -24,13 +24,13 @@ import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemDetailBinding +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.recycler.ArtistViewHolder +import org.oxycblt.auxio.list.recycler.SimpleItemCallback +import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.ui.recycler.ArtistViewHolder -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.SimpleItemCallback -import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater @@ -39,8 +39,7 @@ import org.oxycblt.auxio.util.inflater * An adapter for displaying genre information and it's children. * @author OxygenCobalt */ -class GenreDetailAdapter(private val listener: Listener) : - DetailAdapter(listener, DIFFER) { +class GenreDetailAdapter(private val callback: Callback) : DetailAdapter(callback, DIFFER) { override fun getItemViewType(position: Int) = when (differ.currentList[position]) { is Genre -> GenreDetailViewHolder.VIEW_TYPE @@ -66,9 +65,9 @@ class GenreDetailAdapter(private val listener: Listener) : if (payloads.isEmpty()) { when (val item = differ.currentList[position]) { - is Genre -> (holder as GenreDetailViewHolder).bind(item, listener) - is Artist -> (holder as ArtistViewHolder).bind(item, listener) - is Song -> (holder as SongViewHolder).bind(item, listener) + is Genre -> (holder as GenreDetailViewHolder).bind(item, callback) + is Artist -> (holder as ArtistViewHolder).bind(item, callback) + is Song -> (holder as SongViewHolder).bind(item, callback) } } } @@ -98,7 +97,7 @@ class GenreDetailAdapter(private val listener: Listener) : private class GenreDetailViewHolder private constructor(private val binding: ItemDetailBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Genre, listener: DetailAdapter.Listener) { + fun bind(item: Genre, callback: DetailAdapter.Callback) { binding.detailCover.bind(item) binding.detailType.text = binding.context.getString(R.string.lbl_genre) binding.detailName.text = item.resolveName(binding.context) @@ -109,8 +108,8 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite binding.context.getPlural(R.plurals.fmt_artist_count, item.artists.size), binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) - binding.detailPlayButton.setOnClickListener { listener.onPlayParent() } - binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() } + binding.detailPlayButton.setOnClickListener { callback.onPlay() } + binding.detailShuffleButton.setOnClickListener { callback.onShuffle() } } companion object { 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 0dcc1c96a..a7d38ee8b 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -23,7 +23,6 @@ import android.view.MenuItem import android.view.View import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.widget.Toolbar import androidx.core.view.isVisible import androidx.core.view.iterator import androidx.core.view.updatePadding @@ -46,6 +45,7 @@ import org.oxycblt.auxio.home.list.AlbumListFragment import org.oxycblt.auxio.home.list.ArtistListFragment import org.oxycblt.auxio.home.list.GenreListFragment import org.oxycblt.auxio.home.list.SongListFragment +import org.oxycblt.auxio.list.SelectionFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -55,12 +55,7 @@ import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.system.Indexer -import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel -import org.oxycblt.auxio.ui.fragment.ViewBindingFragment -import org.oxycblt.auxio.ui.selection.SelectionToolbarOverlay -import org.oxycblt.auxio.ui.selection.SelectionViewModel +import org.oxycblt.auxio.shared.MainNavigationAction import org.oxycblt.auxio.util.* /** @@ -68,12 +63,9 @@ import org.oxycblt.auxio.util.* * respective item. * @author OxygenCobalt */ -class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuItemClickListener, SelectionToolbarOverlay.Callback { - private val playbackModel: PlaybackViewModel by androidActivityViewModels() +class HomeFragment : SelectionFragment() { private val homeModel: HomeViewModel by androidActivityViewModels() private val musicModel: MusicViewModel by activityViewModels() - private val navModel: NavigationViewModel by activityViewModels() - private val selectionModel: SelectionViewModel by activityViewModels() // lifecycleObject builds this in the creation step, so doing this is okay. private val storagePermissionLauncher: ActivityResultLauncher by lifecycleObject { @@ -103,22 +95,14 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI override fun onCreateBinding(inflater: LayoutInflater) = FragmentHomeBinding.inflate(inflater) override fun onBindingCreated(binding: FragmentHomeBinding, savedInstanceState: Bundle?) { - binding.homeAppbar.apply { - addOnOffsetChangedListener { _, offset -> - val range = binding.homeAppbar.totalScrollRange - - binding.homeToolbarOverlay.alpha = - 1f - (abs(offset.toFloat()) / (range.toFloat() / 2)) - - binding.homeContent.updatePadding( - bottom = binding.homeAppbar.totalScrollRange + offset) - } + binding.homeAppbar.addOnOffsetChangedListener { _, it -> handleAppBarAnimation(it) } + setupOverlay(binding.homeToolbarOverlay) + binding.homeToolbar.setOnMenuItemClickListener { + handleHomeMenuItem(it) + true } - binding.homeToolbarOverlay.callback = this - binding.homeToolbar.setOnMenuItemClickListener(this@HomeFragment) - - updateTabConfiguration() + setupTabs(binding) // Load the track color in manually as it's unclear whether the track actually supports // using a ColorStateList in the resources @@ -128,17 +112,11 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI binding.homePager.apply { adapter = HomePagerAdapter() - // We know that there will only be a fixed amount of tabs, so we manually set this - // limit to that. This also prevents the appbar lift state from being confused during - // page transitions. - offscreenPageLimit = homeModel.tabs.size - - reduceSensitivity(3) - registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) = - homeModel.updateCurrentTab(position) + override fun onPageSelected(position: Int) { + homeModel.setCurrentTab(position) + } }) TabLayoutMediator(binding.homeTabs, this, AdaptiveTabStrategy(context, homeModel)) @@ -148,6 +126,22 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI // insets applied to the indexing view before API 30. Fix this by overriding the // callback with a non-consuming listener. setOnApplyWindowInsetsListener { _, insets -> insets } + + // We know that there will only be a fixed amount of tabs, so we manually set this + // limit to that. This also prevents the appbar lift state from being confused during + // page transitions. + offscreenPageLimit = homeModel.tabs.size + + // By default, ViewPager2's sensitivity is high enough to result in vertical scroll + // events being + // registered as horizontal scroll events. Reflect into the internal recyclerview and + // change the + // touch slope so that touch actions will act more as a scroll than as a swipe. Derived + // from: + // https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414 + val recycler = VP_RECYCLER_FIELD.get(this@apply) + val slop = RV_TOUCH_SLOP_FIELD.get(recycler) as Int + RV_TOUCH_SLOP_FIELD.set(recycler, slop * 3) } binding.homeFab.setOnClickListener { playbackModel.shuffleAll() } @@ -157,9 +151,11 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI collect(homeModel.recreateTabs, ::handleRecreateTabs) collectImmediately(homeModel.currentTab, ::updateCurrentTab) collectImmediately(homeModel.songs, homeModel.isFastScrolling, ::updateFab) - collectImmediately(musicModel.indexerState, ::handleIndexerState) - collectImmediately(selectionModel.selected, ::updateSelection) + + collectImmediately(musicModel.indexerState, ::updateIndexerState) + collect(navModel.exploreNavigationItem, ::handleNavigation) + collectImmediately(selectionModel.selected, ::updateSelection) } override fun onSaveInstanceState(outState: Bundle) { @@ -171,13 +167,18 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI super.onSaveInstanceState(outState) } - override fun onDestroyBinding(binding: FragmentHomeBinding) { - super.onDestroyBinding(binding) - binding.homeToolbarOverlay.callback = null - binding.homeToolbar.setOnMenuItemClickListener(null) + private fun handleAppBarAnimation(verticalOffset: Int) { + val binding = requireBinding() + val range = binding.homeAppbar.totalScrollRange + + binding.homeToolbarOverlay.alpha = + 1f - (abs(verticalOffset.toFloat()) / (range.toFloat() / 2)) + + binding.homeContent.updatePadding( + bottom = binding.homeAppbar.totalScrollRange + verticalOffset) } - override fun onMenuItemClick(item: MenuItem): Boolean { + private fun handleHomeMenuItem(item: MenuItem) { when (item.itemId) { R.id.action_search -> { logD("Navigating to search") @@ -206,7 +207,6 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI .getSortForTab(homeModel.currentTab.value) .withAscending(item.isChecked)) } - else -> { // Sorting option was selected, mark it as selected and update the mode item.isChecked = true @@ -216,23 +216,6 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI .withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))) } } - - // Always handling an item - return true - } - - override fun onClearSelection() { - selectionModel.consume() - } - - override fun onPlaySelectionNext() { - playbackModel.playNext(selectionModel.consume()) - requireContext().showToast(R.string.lng_queue_added) - } - - override fun onAddSelectionToQueue() { - playbackModel.addToQueue(selectionModel.consume()) - requireContext().showToast(R.string.lng_queue_added) } private fun updateCurrentTab(tab: MusicMode) { @@ -263,7 +246,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } } - requireBinding().homeAppbar.liftOnScrollTargetViewId = getRecyclerId(tab) + requireBinding().homeAppbar.liftOnScrollTargetViewId = getTabRecyclerId(tab) } private fun updateSortMenu(mode: MusicMode, isVisible: (Int) -> Boolean) { @@ -285,34 +268,24 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI private fun handleRecreateTabs(recreate: Boolean) { if (recreate) { - requireBinding().homePager.recreate() - updateTabConfiguration() + val binding = requireBinding() + + binding.homePager.apply { + currentItem = 0 + adapter = HomePagerAdapter() + } + + setupTabs(binding) + homeModel.finishRecreateTabs() } } - private fun updateTabConfiguration() { - val binding = requireBinding() - val toolbarParams = binding.homeToolbarOverlay.layoutParams as AppBarLayout.LayoutParams - if (homeModel.tabs.size == 1) { - // A single tab makes the tab layout redundant, hide it and disable the collapsing - // behavior. - binding.homeTabs.isVisible = false - binding.homeAppbar.setExpanded(true, false) - toolbarParams.scrollFlags = 0 - } else { - binding.homeTabs.isVisible = true - toolbarParams.scrollFlags = - AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or - AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS - } - } - - private fun handleIndexerState(state: Indexer.State?) { + private fun updateIndexerState(state: Indexer.State?) { val binding = requireBinding() when (state) { - is Indexer.State.Complete -> handleIndexerResponse(binding, state.response) - is Indexer.State.Indexing -> handleIndexingState(binding, state.indexing) + is Indexer.State.Complete -> setupCompleteState(binding, state.response) + is Indexer.State.Indexing -> setupIndexingState(binding, state.indexing) null -> { logD("Indexer is in indeterminate state") binding.homeIndexingContainer.visibility = View.INVISIBLE @@ -320,7 +293,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } } - private fun handleIndexerResponse(binding: FragmentHomeBinding, response: Indexer.Response) { + private fun setupCompleteState(binding: FragmentHomeBinding, response: Indexer.Response) { if (response is Indexer.Response.Ok) { binding.homeFab.show() binding.homeIndexingContainer.visibility = View.INVISIBLE @@ -366,7 +339,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } } - private fun handleIndexingState(binding: FragmentHomeBinding, indexing: Indexer.Indexing) { + private fun setupIndexingState(binding: FragmentHomeBinding, indexing: Indexer.Indexing) { binding.homeIndexingContainer.visibility = View.VISIBLE binding.homeIndexingProgress.visibility = View.VISIBLE binding.homeIndexingAction.visibility = View.INVISIBLE @@ -399,17 +372,6 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } } - private fun updateSelection(selected: List) { - val binding = requireBinding() - if (binding.homeToolbarOverlay.updateSelectionAmount(selected.size) && - selected.isNotEmpty()) { - logD("Significant selection occurred, expanding AppBar") - // Significant enough change where we want to expand the RecyclerView - binding.homeAppbar.expandWithRecycler( - binding.homePager.findViewById(getRecyclerId(homeModel.currentTab.value))) - } - } - private fun handleNavigation(item: Music?) { val action = when (item) { @@ -426,6 +388,42 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI findNavController().navigate(action) } + private fun updateSelection(selected: List) { + val binding = requireBinding() + if (binding.homeToolbarOverlay.updateSelectionAmount(selected.size) && + selected.isNotEmpty()) { + logD("Significant selection occurred, expanding AppBar") + // Significant enough change where we want to expand the RecyclerView + binding.homeAppbar.expandWithRecycler( + binding.homePager.findViewById(getTabRecyclerId(homeModel.currentTab.value))) + } + } + + /** Returns the ID of a RecyclerView that the given [tab] contains */ + private fun getTabRecyclerId(tab: MusicMode) = + when (tab) { + MusicMode.SONGS -> R.id.home_song_recycler + MusicMode.ALBUMS -> R.id.home_album_recycler + MusicMode.ARTISTS -> R.id.home_artist_recycler + MusicMode.GENRES -> R.id.home_genre_recycler + } + + private fun setupTabs(binding: FragmentHomeBinding) { + val toolbarParams = binding.homeToolbarOverlay.layoutParams as AppBarLayout.LayoutParams + if (homeModel.tabs.size == 1) { + // A single tab makes the tab layout redundant, hide it and disable the collapsing + // behavior. + binding.homeTabs.isVisible = false + binding.homeAppbar.setExpanded(true, false) + toolbarParams.scrollFlags = 0 + } else { + binding.homeTabs.isVisible = true + toolbarParams.scrollFlags = + AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or + AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS + } + } + private fun initAxisTransitions(axis: Int) { // Sanity check check(axis == MaterialSharedAxis.X || axis == MaterialSharedAxis.Z) { @@ -437,35 +435,6 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI reenterTransition = MaterialSharedAxis(axis, false) } - /** - * Returns the ID of a RecyclerView that the given [tab] contains - */ - private fun getRecyclerId(tab: MusicMode) = - when (tab) { - MusicMode.SONGS -> R.id.home_song_recycler - MusicMode.ALBUMS -> R.id.home_album_recycler - MusicMode.ARTISTS -> R.id.home_artist_recycler - MusicMode.GENRES -> R.id.home_genre_recycler - } - - /** - * By default, ViewPager2's sensitivity is high enough to result in vertical scroll events being - * registered as horizontal scroll events. Reflect into the internal recyclerview and change the - * touch slope so that touch actions will act more as a scroll than as a swipe. Derived from: - * https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414 - */ - private fun ViewPager2.reduceSensitivity(by: Int) { - val recycler = VP_RECYCLER_FIELD.get(this@reduceSensitivity) - val slop = RV_TOUCH_SLOP_FIELD.get(recycler) as Int - RV_TOUCH_SLOP_FIELD.set(recycler, slop * by) - } - - /** Forces the view to recreate all fragments contained within it. */ - private fun ViewPager2.recreate() { - currentItem = 0 - adapter = HomePagerAdapter() - } - private inner class HomePagerAdapter : FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 3079570bb..15634e5d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -84,7 +84,7 @@ class HomeViewModel(application: Application) : } /** Update the current tab based off of the new ViewPager position. */ - fun updateCurrentTab(pos: Int) { + fun setCurrentTab(pos: Int) { logD("Updating current tab to ${tabs[pos]}") _currentTab.value = tabs[pos] } @@ -129,9 +129,9 @@ class HomeViewModel(application: Application) : * Update the fast scroll state. This is used to control the FAB visibility whenever the user * begins to fast scroll. */ - fun updateFastScrolling(scrolling: Boolean) { - logD("Updating fast scrolling state: $scrolling") - _isFastScrolling.value = scrolling + fun setFastScrolling(fastScrolling: Boolean) { + logD("Updating fast scrolling state: $fastScrolling") + _isFastScrolling.value = fastScrolling } // --- OVERRIDES --- diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt rename to app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt index e5b07584d..ad41c02f9 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.fastscroll +package org.oxycblt.auxio.home.fastscroll import android.content.Context import android.graphics.Canvas diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt rename to app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt index 478a35795..2d055d885 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.fastscroll +package org.oxycblt.auxio.home.fastscroll import android.content.Context import android.graphics.Canvas @@ -35,7 +35,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlin.math.abs import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView +import org.oxycblt.auxio.list.recycler.AuxioRecyclerView import org.oxycblt.auxio.util.getDimenSize import org.oxycblt.auxio.util.getDrawableCompat import org.oxycblt.auxio.util.isRtl @@ -137,33 +137,26 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr removeCallbacks(hideThumbRunnable) showScrollbar() showPopup() - listener?.onFastScrollStart() } else { postAutoHideScrollbar() hidePopup() - listener?.onFastScrollStop() } + + fastScrollCallback?.invoke(field) } private val tRect = Rect() - interface PopupProvider { - fun getPopup(pos: Int): String? - } - /** Callback to provide a string to be shown on the popup when an item is passed */ - var popupProvider: PopupProvider? = null + var popupProvider: ((Int) -> String?)? = null - interface OnFastScrollListener { - fun onFastScrollStart() - fun onFastScrollStop() - } + class FastScrollCallback(val onStart: () -> Unit, val onEnd: () -> Unit) /** - * A listener for when a drag event occurs. The value will be true if a drag has begun, and + * A callback for when a drag event occurs. The value will be true if a drag has begun, and * false if a drag ended. */ - var listener: OnFastScrollListener? = null + var fastScrollCallback: ((Boolean) -> Unit)? = null init { overlay.add(thumbView) @@ -225,7 +218,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr if (firstAdapterPos != NO_POSITION && provider != null) { popupView.isInvisible = false // Get the popup text. If there is none, we default to "?". - popupText = provider.getPopup(firstAdapterPos) ?: "?" + popupText = provider.invoke(firstAdapterPos) ?: "?" } else { // No valid position or provider, do not show the popup. popupView.isInvisible = true 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 304b3f4b8..e038d844a 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 @@ -19,11 +19,19 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.text.format.DateUtils +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import java.util.Formatter import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.* +import org.oxycblt.auxio.list.SelectionFragment +import org.oxycblt.auxio.list.recycler.AlbumViewHolder +import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicMode @@ -31,36 +39,56 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.secsToMs -import org.oxycblt.auxio.ui.recycler.AlbumViewHolder -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.SelectionIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.collectImmediately /** * A [HomeListFragment] for showing a list of [Album]s. * @author OxygenCobalt */ -class AlbumListFragment : HomeListFragment() { - private val homeAdapter = AlbumAdapter(this) +class AlbumListFragment : SelectionFragment() { + private val homeModel: HomeViewModel by activityViewModels() + + private val homeAdapter = + AlbumAdapter(ItemSelectCallback(::handleClick, ::handleOpenMenu, ::handleClick)) + private val formatterSb = StringBuilder(32) private val formatter = Formatter(formatterSb) + override fun onCreateBinding(inflater: LayoutInflater) = + FragmentHomeListBinding.inflate(inflater) + override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) binding.homeRecycler.apply { id = R.id.home_album_recycler adapter = homeAdapter + + popupProvider = ::updatePopup + fastScrollCallback = { homeModel.setFastScrolling(it) } } collectImmediately(homeModel.albums, homeAdapter::replaceList) - collectImmediately(selectionModel.selected, homeAdapter::updateSelection) - collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::handleParent) + collectImmediately(selectionModel.selected, homeAdapter::setSelected) + collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) } - override fun getPopup(pos: Int): String? { + override fun onDestroyBinding(binding: FragmentHomeListBinding) { + super.onDestroyBinding(binding) + binding.homeRecycler.adapter = null + } + + override fun onClick(music: Music) { + check(music is Album) { "Unexpected datatype: ${music::class.java}" } + navModel.exploreNavigateTo(music) + } + + private fun handleOpenMenu(item: Item, anchor: View) { + check(item is Album) { "Unexpected datatype: ${item::class.java}" } + openMusicMenu(anchor, R.menu.menu_album_actions, item) + } + + private fun updatePopup(pos: Int): String? { val album = homeModel.albums.value[pos] // Change how we display the popup depending on the mode. @@ -98,18 +126,7 @@ class AlbumListFragment : HomeListFragment() { else -> null } } - - override fun onItemClick(music: Music) { - check(music is Album) { "Unexpected datatype: ${music::class.java}" } - navModel.exploreNavigateTo(music) - } - - override fun onOpenMenu(item: Item, anchor: View) { - check(item is Album) { "Unexpected datatype: ${item::class.java}" } - musicMenu(anchor, R.menu.menu_album_actions, item) - } - - private fun handleParent(parent: MusicParent?, isPlaying: Boolean) { + private fun updatePlayback(parent: MusicParent?, isPlaying: Boolean) { if (parent is Album) { homeAdapter.updateIndicator(parent, isPlaying) } else { @@ -118,7 +135,7 @@ class AlbumListFragment : HomeListFragment() { } } - private class AlbumAdapter(private val listener: MenuItemListener) : + private class AlbumAdapter(private val callback: ItemSelectCallback) : SelectionIndicatorAdapter() { private val differ = SyncListDiffer(this, AlbumViewHolder.DIFFER) @@ -134,7 +151,7 @@ class AlbumListFragment : HomeListFragment() { super.onBindViewHolder(holder, position, payloads) if (payloads.isEmpty()) { - holder.bind(differ.currentList[position], listener) + holder.bind(differ.currentList[position], callback) } } 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 0e68f83ae..931b171f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -18,21 +18,24 @@ package org.oxycblt.auxio.home.list import android.os.Bundle +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.* +import org.oxycblt.auxio.list.SelectionFragment +import org.oxycblt.auxio.list.recycler.ArtistViewHolder +import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SyncListDiffer 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.Sort import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.recycler.ArtistViewHolder -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.SelectionIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.nonZeroOrNull @@ -40,8 +43,14 @@ import org.oxycblt.auxio.util.nonZeroOrNull * A [HomeListFragment] for showing a list of [Artist]s. * @author OxygenCobalt */ -class ArtistListFragment : HomeListFragment() { - private val homeAdapter = ArtistAdapter(this) +class ArtistListFragment : SelectionFragment() { + private val homeModel: HomeViewModel by activityViewModels() + + private val homeAdapter = + ArtistAdapter(ItemSelectCallback(::handleClick, ::handleOpenMenu, ::handleSelect)) + + override fun onCreateBinding(inflater: LayoutInflater) = + FragmentHomeListBinding.inflate(inflater) override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) @@ -49,14 +58,22 @@ class ArtistListFragment : HomeListFragment() { binding.homeRecycler.apply { id = R.id.home_artist_recycler adapter = homeAdapter + + popupProvider = ::updatePopup + fastScrollCallback = homeModel::setFastScrolling } collectImmediately(homeModel.artists, homeAdapter::replaceList) - collectImmediately(selectionModel.selected, homeAdapter::updateSelection) - collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::handleParent) + collectImmediately(selectionModel.selected, homeAdapter::setSelected) + collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) } - override fun getPopup(pos: Int): String? { + override fun onDestroyBinding(binding: FragmentHomeListBinding) { + super.onDestroyBinding(binding) + binding.homeRecycler.adapter = null + } + + private fun updatePopup(pos: Int): String? { val artist = homeModel.artists.value[pos] // Change how we display the popup depending on the mode. @@ -75,17 +92,17 @@ class ArtistListFragment : HomeListFragment() { } } - override fun onItemClick(music: Music) { + override fun onClick(music: Music) { check(music is Artist) { "Unexpected datatype: ${music::class.java}" } navModel.exploreNavigateTo(music) } - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenMenu(item: Item, anchor: View) { check(item is Artist) { "Unexpected datatype: ${item::class.java}" } - musicMenu(anchor, R.menu.menu_artist_actions, item) + openMusicMenu(anchor, R.menu.menu_artist_actions, item) } - private fun handleParent(parent: MusicParent?, isPlaying: Boolean) { + private fun updatePlayback(parent: MusicParent?, isPlaying: Boolean) { if (parent is Artist) { homeAdapter.updateIndicator(parent, isPlaying) } else { @@ -94,7 +111,7 @@ class ArtistListFragment : HomeListFragment() { } } - private class ArtistAdapter(private val listener: MenuItemListener) : + private class ArtistAdapter(private val callback: ItemSelectCallback) : SelectionIndicatorAdapter() { private val differ = SyncListDiffer(this, ArtistViewHolder.DIFFER) @@ -114,7 +131,7 @@ class ArtistListFragment : HomeListFragment() { super.onBindViewHolder(holder, position, payloads) if (payloads.isEmpty()) { - holder.bind(differ.currentList[position], listener) + holder.bind(differ.currentList[position], callback) } } 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 28d323dfb..7960927e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -18,29 +18,38 @@ package org.oxycblt.auxio.home.list import android.os.Bundle +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.* +import org.oxycblt.auxio.list.SelectionFragment +import org.oxycblt.auxio.list.recycler.GenreViewHolder +import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SyncListDiffer 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.music.Sort import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.recycler.GenreViewHolder -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.SelectionIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.collectImmediately /** * A [HomeListFragment] for showing a list of [Genre]s. * @author OxygenCobalt */ -class GenreListFragment : HomeListFragment() { - private val homeAdapter = GenreAdapter(this) +class GenreListFragment : SelectionFragment() { + private val homeModel: HomeViewModel by activityViewModels() + + private val homeAdapter = + GenreAdapter(ItemSelectCallback(::handleClick, ::handleOpenMenu, ::handleSelect)) + + override fun onCreateBinding(inflater: LayoutInflater) = + FragmentHomeListBinding.inflate(inflater) override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) @@ -48,14 +57,22 @@ class GenreListFragment : HomeListFragment() { binding.homeRecycler.apply { id = R.id.home_genre_recycler adapter = homeAdapter + + popupProvider = ::updatePopup + fastScrollCallback = homeModel::setFastScrolling } collectImmediately(homeModel.genres, homeAdapter::replaceList) - collectImmediately(selectionModel.selected, homeAdapter::updateSelection) - collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::handlePlayback) + collectImmediately(selectionModel.selected, homeAdapter::setSelected) + collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) } - override fun getPopup(pos: Int): String? { + override fun onDestroyBinding(binding: FragmentHomeListBinding) { + super.onDestroyBinding(binding) + binding.homeRecycler.adapter = null + } + + private fun updatePopup(pos: Int): String? { val genre = homeModel.genres.value[pos] // Change how we display the popup depending on the mode. @@ -74,17 +91,17 @@ class GenreListFragment : HomeListFragment() { } } - override fun onItemClick(music: Music) { + override fun onClick(music: Music) { check(music is Genre) { "Unexpected datatype: ${music::class.java}" } navModel.exploreNavigateTo(music) } - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenMenu(item: Item, anchor: View) { check(item is Genre) { "Unexpected datatype: ${item::class.java}" } - musicMenu(anchor, R.menu.menu_artist_actions, item) + openMusicMenu(anchor, R.menu.menu_artist_actions, item) } - private fun handlePlayback(parent: MusicParent?, isPlaying: Boolean) { + private fun updatePlayback(parent: MusicParent?, isPlaying: Boolean) { if (parent is Genre) { homeAdapter.updateIndicator(parent, isPlaying) } else { @@ -93,7 +110,7 @@ class GenreListFragment : HomeListFragment() { } } - private class GenreAdapter(private val listener: MenuItemListener) : + private class GenreAdapter(private val callback: ItemSelectCallback) : SelectionIndicatorAdapter() { private val differ = SyncListDiffer(this, GenreViewHolder.DIFFER) @@ -109,7 +126,7 @@ class GenreListFragment : HomeListFragment() { super.onBindViewHolder(holder, position, payloads) if (payloads.isEmpty()) { - holder.bind(differ.currentList[position], listener) + holder.bind(differ.currentList[position], callback) } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt deleted file mode 100644 index 7b4318ce6..000000000 --- a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2021 Auxio Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.oxycblt.auxio.home.list - -import android.os.Bundle -import android.view.LayoutInflater -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import org.oxycblt.auxio.databinding.FragmentHomeListBinding -import org.oxycblt.auxio.home.HomeViewModel -import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.ui.fastscroll.FastScrollRecyclerView -import org.oxycblt.auxio.ui.fragment.MenuFragment -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.selection.SelectionViewModel -import org.oxycblt.auxio.util.androidActivityViewModels - -/** - * A Base [Fragment] implementing the base features shared across all list fragments in the home UI. - * @author OxygenCobalt - */ -abstract class HomeListFragment : - MenuFragment(), - MenuItemListener, - FastScrollRecyclerView.PopupProvider, - FastScrollRecyclerView.OnFastScrollListener { - protected val homeModel: HomeViewModel by androidActivityViewModels() - protected val selectionModel: SelectionViewModel by activityViewModels() - - override fun onCreateBinding(inflater: LayoutInflater) = - FragmentHomeListBinding.inflate(inflater) - - override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { - binding.homeRecycler.popupProvider = this - binding.homeRecycler.listener = this - } - - override fun onDestroyBinding(binding: FragmentHomeListBinding) { - homeModel.updateFastScrolling(false) - binding.homeRecycler.apply { - adapter = null - popupProvider = null - listener = null - } - } - - override fun onFastScrollStart() { - homeModel.updateFastScrolling(true) - } - - override fun onFastScrollStop() { - homeModel.updateFastScrolling(false) - } - - abstract fun onItemClick(music: Music) - - override fun onItemClick(item: Item) { - check(item is Music) { "Unexpected datatype: ${item::class.java}" } - if (selectionModel.selected.value.isEmpty()) { - onItemClick(item) - } else { - onSelect(item) - } - } - - override fun onSelect(item: Item) { - check(item is Music) { "Unexpected datatype: ${item::class.java}" } - selectionModel.select(item) - } -} 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 39248e546..ffc68e4e4 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 @@ -19,11 +19,19 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.text.format.DateUtils +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.activityViewModels import java.util.Formatter import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.* +import org.oxycblt.auxio.list.SelectionFragment +import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SongViewHolder +import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent @@ -32,11 +40,6 @@ import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.recycler.SelectionIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SongViewHolder -import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context @@ -44,27 +47,43 @@ import org.oxycblt.auxio.util.context * A [HomeListFragment] for showing a list of [Song]s. * @author OxygenCobalt */ -class SongListFragment : HomeListFragment() { - private val homeAdapter = SongAdapter(this) +class SongListFragment : SelectionFragment() { + private val homeModel: HomeViewModel by activityViewModels() + + private val homeAdapter = + SongAdapter(ItemSelectCallback(::handleClick, ::handleOpenMenu, ::handleSelect)) + private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } + private val formatterSb = StringBuilder(50) private val formatter = Formatter(formatterSb) + override fun onCreateBinding(inflater: LayoutInflater) = + FragmentHomeListBinding.inflate(inflater) + override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) binding.homeRecycler.apply { id = R.id.home_song_recycler adapter = homeAdapter + + popupProvider = ::updatePopup + fastScrollCallback = homeModel::setFastScrolling } collectImmediately(homeModel.songs, homeAdapter::replaceList) - collectImmediately(selectionModel.selected, homeAdapter::updateSelection) + collectImmediately(selectionModel.selected, homeAdapter::setSelected) collectImmediately( - playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) } - override fun getPopup(pos: Int): String? { + override fun onDestroyBinding(binding: FragmentHomeListBinding) { + super.onDestroyBinding(binding) + binding.homeRecycler.adapter = null + } + + private fun updatePopup(pos: Int): String? { val song = homeModel.songs.value[pos] // Change how we display the popup depending on the mode. @@ -106,7 +125,7 @@ class SongListFragment : HomeListFragment() { } } - override fun onItemClick(music: Music) { + override fun onClick(music: Music) { check(music is Song) { "Unexpected datatype: ${music::class.java}" } when (settings.libPlaybackMode) { MusicMode.SONGS -> playbackModel.playFromAll(music) @@ -116,12 +135,12 @@ class SongListFragment : HomeListFragment() { } } - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenMenu(item: Item, anchor: View) { check(item is Song) { "Unexpected datatype: ${item::class.java}" } - musicMenu(anchor, R.menu.menu_song_actions, item) + openMusicMenu(anchor, R.menu.menu_song_actions, item) } - private fun handlePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { if (parent == null) { homeAdapter.updateIndicator(song, isPlaying) } else { @@ -130,7 +149,7 @@ class SongListFragment : HomeListFragment() { } } - private class SongAdapter(private val listener: MenuItemListener) : + private class SongAdapter(private val callback: ItemSelectCallback) : SelectionIndicatorAdapter() { private val differ = SyncListDiffer(this, SongViewHolder.DIFFER) @@ -146,7 +165,7 @@ class SongListFragment : HomeListFragment() { super.onBindViewHolder(holder, position, payloads) if (payloads.isEmpty()) { - holder.bind(differ.currentList[position], listener) + holder.bind(differ.currentList[position], callback) } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt index 1a775186c..ba5728a34 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt @@ -24,11 +24,11 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemTabBinding +import org.oxycblt.auxio.list.recycler.DialogViewHolder import org.oxycblt.auxio.music.MusicMode -import org.oxycblt.auxio.ui.recycler.DialogViewHolder import org.oxycblt.auxio.util.inflater -class TabAdapter(private val listener: Listener) : RecyclerView.Adapter() { +class TabAdapter(private val callback: Callback) : RecyclerView.Adapter() { var tabs = arrayOf() private set @@ -37,7 +37,7 @@ class TabAdapter(private val listener: Listener) : RecyclerView.Adapter Unit, + val pickUpTab: (RecyclerView.ViewHolder) -> Unit + ) companion object { val PAYLOAD_TAB_CHANGED = Any() @@ -72,8 +72,8 @@ class TabAdapter(private val listener: Listener) : RecyclerView.Adapter binding.tabDragHandle.performClick() if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) { - listener.onPickUpTab(this) + callback.pickUpTab(this) true } else false } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index b02b2cc6a..37885464d 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -27,7 +27,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogTabsBinding import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.logD @@ -35,8 +35,8 @@ import org.oxycblt.auxio.util.logD * The dialog for customizing library tabs. * @author OxygenCobalt */ -class TabCustomizeDialog : ViewBindingDialogFragment(), TabAdapter.Listener { - private val tabAdapter = TabAdapter(this) +class TabCustomizeDialog : ViewBindingDialogFragment() { + private val tabAdapter = TabAdapter(TabAdapter.Callback(::toggleVisibility, ::pickUpTab)) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } private val touchHelper: ItemTouchHelper by lifecycleObject { ItemTouchHelper(TabDragCallback(tabAdapter)) @@ -79,7 +79,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment(), TabAd binding.tabRecycler.adapter = null } - override fun onVisibilityToggled(mode: MusicMode) { + private fun toggleVisibility(mode: MusicMode) { val index = tabAdapter.tabs.indexOfFirst { it.mode == mode } if (index > -1) { val tab = tabAdapter.tabs[index] @@ -95,7 +95,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment(), TabAd tabAdapter.tabs.filterIsInstance().isNotEmpty() } - override fun onPickUpTab(viewHolder: RecyclerView.ViewHolder) { + private fun pickUpTab(viewHolder: RecyclerView.ViewHolder) { touchHelper.startDrag(viewHolder) } diff --git a/app/src/main/java/org/oxycblt/auxio/list/List.kt b/app/src/main/java/org/oxycblt/auxio/list/List.kt new file mode 100644 index 000000000..520823fd0 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/List.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.list + +import android.view.View +import androidx.annotation.StringRes + +/** A marker for something that is a RecyclerView item. Has no functionality on it's own. */ +interface Item + +/** A data object used solely for the "Header" UI element. */ +data class Header( + /** The string resource used for the header. */ + @StringRes val string: Int +) : Item + +open class ItemClickCallback(val onClick: (Item) -> Unit) + +open class ItemMenuCallback(onClick: (Item) -> Unit, val onOpenMenu: (Item, View) -> Unit) : + ItemClickCallback(onClick) + +open class ItemSelectCallback( + onClick: (Item) -> Unit, + onOpenMenu: (Item, View) -> Unit, + val onSelect: (Item) -> Unit +) : ItemMenuCallback(onClick, onOpenMenu) + +/** An interface for detecting if an item has been clicked once. */ +interface ItemClickListener { + /** Called when an item is clicked once. */ + fun onItemClick(item: Item) +} + +/** An interface for detecting if an item has had it's menu opened. */ +interface MenuItemListener : ItemClickListener { + /** Called when an item is long-clicked. */ + fun onSelect(item: Item) {} + + /** Called when an item desires to open a menu relating to it. */ + fun onOpenMenu(item: Item, anchor: View) +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fragment/MenuFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/MenuFragment.kt similarity index 83% rename from app/src/main/java/org/oxycblt/auxio/ui/fragment/MenuFragment.kt rename to app/src/main/java/org/oxycblt/auxio/list/MenuFragment.kt index f953a5348..7706262a4 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fragment/MenuFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/MenuFragment.kt @@ -15,8 +15,9 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.fragment +package org.oxycblt.auxio.list +import android.view.MenuItem import android.view.View import androidx.annotation.MenuRes import androidx.appcompat.widget.PopupMenu @@ -29,8 +30,9 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel +import org.oxycblt.auxio.shared.MainNavigationAction +import org.oxycblt.auxio.shared.NavigationViewModel +import org.oxycblt.auxio.shared.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.showToast @@ -50,11 +52,11 @@ abstract class MenuFragment : ViewBindingFragment() { * Opens the given menu in context of [song]. Assumes that the menu is only composed of common * [Song] options. */ - protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, song: Song) { + protected fun openMusicMenu(anchor: View, @MenuRes menuRes: Int, song: Song) { logD("Launching new song menu: ${song.rawName}") - musicMenuImpl(anchor, menuRes) { id -> - when (id) { + openMusicMenuImpl(anchor, menuRes) { + when (it.itemId) { R.id.action_play_next -> { playbackModel.playNext(song) requireContext().showToast(R.string.lng_queue_added) @@ -78,8 +80,6 @@ abstract class MenuFragment : ViewBindingFragment() { error("Unexpected menu item selected") } } - - true } } @@ -87,11 +87,11 @@ abstract class MenuFragment : ViewBindingFragment() { * Opens the given menu in context of [album]. Assumes that the menu is only composed of common * [Album] options. */ - protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, album: Album) { + protected fun openMusicMenu(anchor: View, @MenuRes menuRes: Int, album: Album) { logD("Launching new album menu: ${album.rawName}") - musicMenuImpl(anchor, menuRes) { id -> - when (id) { + openMusicMenuImpl(anchor, menuRes) { + when (it.itemId) { R.id.action_play -> { playbackModel.play(album) } @@ -113,8 +113,6 @@ abstract class MenuFragment : ViewBindingFragment() { error("Unexpected menu item selected") } } - - true } } @@ -122,11 +120,11 @@ abstract class MenuFragment : ViewBindingFragment() { * Opens the given menu in context of [artist]. Assumes that the menu is only composed of common * [Artist] options. */ - protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, artist: Artist) { + protected fun openMusicMenu(anchor: View, @MenuRes menuRes: Int, artist: Artist) { logD("Launching new artist menu: ${artist.rawName}") - musicMenuImpl(anchor, menuRes) { id -> - when (id) { + openMusicMenuImpl(anchor, menuRes) { + when (it.itemId) { R.id.action_play -> { playbackModel.play(artist) } @@ -145,8 +143,6 @@ abstract class MenuFragment : ViewBindingFragment() { error("Unexpected menu item selected") } } - - true } } @@ -154,11 +150,11 @@ abstract class MenuFragment : ViewBindingFragment() { * Opens the given menu in context of [genre]. Assumes that the menu is only composed of common * [Genre] options. */ - protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, genre: Genre) { + protected fun openMusicMenu(anchor: View, @MenuRes menuRes: Int, genre: Genre) { logD("Launching new genre menu: ${genre.rawName}") - musicMenuImpl(anchor, menuRes) { id -> - when (id) { + openMusicMenuImpl(anchor, menuRes) { + when (it.itemId) { R.id.action_play -> { playbackModel.play(genre) } @@ -177,20 +173,27 @@ abstract class MenuFragment : ViewBindingFragment() { error("Unexpected menu item selected") } } - - true } } - private fun musicMenuImpl(anchor: View, @MenuRes menuRes: Int, onSelect: (Int) -> Boolean) { - menu(anchor, menuRes) { setOnMenuItemClickListener { item -> onSelect(item.itemId) } } + private fun openMusicMenuImpl( + anchor: View, + @MenuRes menuRes: Int, + onClick: (MenuItem) -> Unit + ) { + openMenu(anchor, menuRes) { + setOnMenuItemClickListener { item -> + onClick(item) + true + } + } } /** * Open a generic menu with configuration in [block]. If a menu is already opened, then this * function is a no-op. */ - protected fun menu(anchor: View, @MenuRes menuRes: Int, block: PopupMenu.() -> Unit) { + protected fun openMenu(anchor: View, @MenuRes menuRes: Int, block: PopupMenu.() -> Unit) { if (currentMenu != null) { logD("Menu already present, not launching") return diff --git a/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt new file mode 100644 index 000000000..50cb68683 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/SelectionFragment.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.list + +import android.view.MenuItem +import androidx.fragment.app.activityViewModels +import androidx.viewbinding.ViewBinding +import org.oxycblt.auxio.R +import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.util.showToast + +abstract class SelectionFragment : MenuFragment() { + protected val selectionModel: SelectionViewModel by activityViewModels() + + open fun onClick(music: Music) { + throw NotImplementedError() + } + + protected fun setupOverlay(overlay: SelectionToolbarOverlay) { + overlay.apply { + setOnSelectionCancelListener { selectionModel.consume() } + setOnMenuItemClickListener { + handleSelectionMenuItem(it) + true + } + } + } + + private fun handleSelectionMenuItem(item: MenuItem) { + when (item.itemId) { + R.id.action_play_next -> { + playbackModel.playNext(selectionModel.consume()) + requireContext().showToast(R.string.lng_queue_added) + } + R.id.action_queue_add -> { + playbackModel.addToQueue(selectionModel.consume()) + requireContext().showToast(R.string.lng_queue_added) + } + } + } + + protected fun handleClick(item: Item) { + check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" } + if (selectionModel.selected.value.isNotEmpty()) { + selectionModel.select(item) + } else { + onClick(item) + } + } + + protected fun handleSelect(item: Item) { + check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" } + selectionModel.select(item) + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/selection/SelectionToolbarOverlay.kt b/app/src/main/java/org/oxycblt/auxio/list/SelectionToolbarOverlay.kt similarity index 83% rename from app/src/main/java/org/oxycblt/auxio/ui/selection/SelectionToolbarOverlay.kt rename to app/src/main/java/org/oxycblt/auxio/list/SelectionToolbarOverlay.kt index eaa7c2232..b3dcc33a4 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/selection/SelectionToolbarOverlay.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/SelectionToolbarOverlay.kt @@ -15,15 +15,14 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.selection +package org.oxycblt.auxio.list import android.animation.ValueAnimator import android.content.Context import android.util.AttributeSet -import android.view.MenuItem import android.widget.FrameLayout import androidx.annotation.AttrRes -import androidx.appcompat.widget.Toolbar +import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener import androidx.core.view.isInvisible import com.google.android.material.appbar.MaterialToolbar import org.oxycblt.auxio.R @@ -38,29 +37,12 @@ class SelectionToolbarOverlay @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) { - var callback: Callback? = null private lateinit var innerToolbar: MaterialToolbar private val selectionToolbar = MaterialToolbar(context).apply { setNavigationIcon(R.drawable.ic_close_24) - setNavigationOnClickListener { - callback?.onClearSelection() - } - inflateMenu(R.menu.menu_selection_actions) - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_play_next -> { - callback?.onPlaySelectionNext() - } - R.id.action_queue_add -> { - callback?.onAddSelectionToQueue() - } - } - - true - } } private var fadeThroughAnimator: ValueAnimator? = null @@ -75,9 +57,12 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr addView(selectionToolbar) } - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - callback = null + fun setOnSelectionCancelListener(listener: OnClickListener) { + selectionToolbar.setNavigationOnClickListener(listener) + } + + fun setOnMenuItemClickListener(listener: OnMenuItemClickListener) { + selectionToolbar.setOnMenuItemClickListener(listener) } /** @@ -151,10 +136,4 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr isInvisible = innerAlpha == 1f } } - - interface Callback { - fun onClearSelection() - fun onPlaySelectionNext() - fun onAddSelectionToQueue() - } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/selection/SelectionViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/SelectionViewModel.kt similarity index 80% rename from app/src/main/java/org/oxycblt/auxio/ui/selection/SelectionViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/list/SelectionViewModel.kt index 5717d191d..e1565e305 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/selection/SelectionViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/SelectionViewModel.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.selection +package org.oxycblt.auxio.list import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -33,15 +33,15 @@ class SelectionViewModel : ViewModel() { get() = _selected /** Select a music item. */ - fun select(item: Music) { - val items = _selected.value.toMutableList() - if (items.remove(item)) { - logD("Unselecting item $item") - _selected.value = items + fun select(music: Music) { + val selected = _selected.value.toMutableList() + if (selected.remove(music)) { + logD("Unselecting item $music") + _selected.value = selected } else { - logD("Selecting item $item") - items.add(item) - _selected.value = items + logD("Selecting item $music") + selected.add(music) + _selected.value = selected } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/AuxioRecyclerView.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt rename to app/src/main/java/org/oxycblt/auxio/list/recycler/AuxioRecyclerView.kt index 3edd521d5..9a64da26a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/AuxioRecyclerView.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.recycler +package org.oxycblt.auxio.list.recycler import android.content.Context import android.graphics.Rect diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/DialogRecyclerView.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt rename to app/src/main/java/org/oxycblt/auxio/list/recycler/DialogRecyclerView.kt index 03b1cf5ec..c7e21ad83 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/DialogRecyclerView.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.recycler +package org.oxycblt.auxio.list.recycler import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/PlayingIndicatorAdapter.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/PlayingIndicatorAdapter.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/PlayingIndicatorAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/list/recycler/PlayingIndicatorAdapter.kt index de7f665c8..98650e760 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/PlayingIndicatorAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/PlayingIndicatorAdapter.kt @@ -15,10 +15,11 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.recycler +package org.oxycblt.auxio.list.recycler import android.view.View import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.util.logW /** diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/SelectionIndicatorAdapter.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/list/recycler/SelectionIndicatorAdapter.kt index be27f3ce8..86d3f1aa0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/SelectionIndicatorAdapter.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.recycler +package org.oxycblt.auxio.list.recycler import android.view.View import androidx.recyclerview.widget.RecyclerView @@ -37,7 +37,7 @@ abstract class SelectionIndicatorAdapter : } } - fun updateSelection(items: List) { + fun setSelected(items: List) { val oldSelectedItems = selectedItems val newSelectedItems = items.toSet() if (newSelectedItems == oldSelectedItems) { diff --git a/app/src/main/java/org/oxycblt/auxio/list/recycler/SimpleItemCallback.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/SimpleItemCallback.kt new file mode 100644 index 000000000..bf18f838c --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/SimpleItemCallback.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.list.recycler + +import androidx.recyclerview.widget.DiffUtil +import org.oxycblt.auxio.list.Item + +/** + * A base [DiffUtil.ItemCallback] that automatically provides an implementation of + * [areContentsTheSame] any object that is derived from [Item]. + */ +abstract class SimpleItemCallback : DiffUtil.ItemCallback() { + final override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem == newItem +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/SyncListDiffer.kt similarity index 80% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt rename to app/src/main/java/org/oxycblt/auxio/list/recycler/SyncListDiffer.kt index 436d0ed5c..993a2881a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/SyncListDiffer.kt @@ -15,38 +15,12 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.recycler +package org.oxycblt.auxio.list.recycler -import android.view.View -import androidx.annotation.StringRes import androidx.recyclerview.widget.AdapterListUpdateCallback import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView -/** A marker for something that is a RecyclerView item. Has no functionality on it's own. */ -interface Item - -/** A data object used solely for the "Header" UI element. */ -data class Header( - /** The string resource used for the header. */ - @StringRes val string: Int -) : Item - -/** An interface for detecting if an item has been clicked once. */ -interface ItemClickListener { - /** Called when an item is clicked once. */ - fun onItemClick(item: Item) -} - -/** An interface for detecting if an item has had it's menu opened. */ -interface MenuItemListener : ItemClickListener { - /** Called when an item is long-clicked. */ - fun onSelect(item: Item) {} - - /** Called when an item desires to open a menu relating to it. */ - fun onOpenMenu(item: Item, anchor: View) -} - /** * Like AsyncListDiffer, but synchronous. This may seem like it would be inefficient, but in * practice Auxio's lists tend to be small enough to the point where this does not matter, and @@ -153,11 +127,3 @@ class SyncListDiffer( currentList = newList } } - -/** - * A base [DiffUtil.ItemCallback] that automatically provides an implementation of - * [areContentsTheSame] any object that is derived from [Item]. - */ -abstract class SimpleItemCallback : DiffUtil.ItemCallback() { - final override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem == newItem -} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt similarity index 74% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt rename to app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt index 9808f6713..96cac83d2 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.recycler +package org.oxycblt.auxio.list.recycler import android.view.View import androidx.recyclerview.widget.RecyclerView @@ -24,6 +24,9 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemHeaderBinding import org.oxycblt.auxio.databinding.ItemParentBinding import org.oxycblt.auxio.databinding.ItemSongBinding +import org.oxycblt.auxio.list.Header +import org.oxycblt.auxio.list.ItemSelectCallback +import org.oxycblt.auxio.list.MenuItemListener import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -53,6 +56,21 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) : } } + fun bind(item: Song, callback: ItemSelectCallback) { + binding.songAlbumCover.bind(item) + binding.songName.text = item.resolveName(binding.context) + binding.songInfo.text = item.resolveArtistContents(binding.context) + + binding.songMenu.setOnClickListener { callback.onOpenMenu(item, it) } + binding.root.apply { + setOnClickListener { callback.onClick(item) } + setOnLongClickListener { + callback.onSelect(item) + true + } + } + } + override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { binding.root.isSelected = isActive binding.songAlbumCover.isPlaying = isPlaying @@ -97,6 +115,21 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding } } + fun bind(item: Album, callback: ItemSelectCallback) { + binding.parentImage.bind(item) + binding.parentName.text = item.resolveName(binding.context) + binding.parentInfo.text = item.resolveArtistContents(binding.context) + + binding.parentMenu.setOnClickListener { callback.onOpenMenu(item, it) } + binding.root.apply { + setOnClickListener { callback.onClick(item) } + setOnLongClickListener { + callback.onSelect(item) + true + } + } + } + override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { binding.root.isSelected = isActive binding.parentImage.isPlaying = isPlaying @@ -153,6 +186,31 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin } } + fun bind(item: Artist, callback: ItemSelectCallback) { + binding.parentImage.bind(item) + binding.parentName.text = item.resolveName(binding.context) + + binding.parentInfo.text = + if (item.songs.isNotEmpty()) { + binding.context.getString( + R.string.fmt_two, + binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size), + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) + } else { + // Artist has no songs, only display an album count. + binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size) + } + + binding.parentMenu.setOnClickListener { callback.onOpenMenu(item, it) } + binding.root.apply { + setOnClickListener { callback.onClick(item) } + setOnLongClickListener { + callback.onSelect(item) + true + } + } + } + override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { binding.root.isSelected = isActive binding.parentImage.isPlaying = isPlaying @@ -203,6 +261,25 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding } } + fun bind(item: Genre, callback: ItemSelectCallback) { + binding.parentImage.bind(item) + binding.parentName.text = item.resolveName(binding.context) + binding.parentInfo.text = + binding.context.getString( + R.string.fmt_two, + binding.context.getPlural(R.plurals.fmt_artist_count, item.artists.size), + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) + + binding.parentMenu.setOnClickListener { callback.onOpenMenu(item, it) } + binding.root.apply { + setOnClickListener { callback.onClick(item) } + setOnLongClickListener { + callback.onSelect(item) + true + } + } + } + override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { binding.root.isSelected = isActive binding.parentImage.isPlaying = isPlaying diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index 965106f8c..be019df32 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -29,6 +29,7 @@ import kotlin.math.max import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.oxycblt.auxio.R +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.extractor.parseId3GenreNames import org.oxycblt.auxio.music.extractor.parseMultiValue import org.oxycblt.auxio.music.extractor.toUuidOrNull @@ -38,7 +39,6 @@ import org.oxycblt.auxio.music.storage.Path import org.oxycblt.auxio.music.storage.albumCoverUri import org.oxycblt.auxio.music.storage.audioUri import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.unlikelyToBeNull diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index e4a0b320a..2312c5276 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -124,12 +124,9 @@ class MusicStore private constructor() { // We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a // song. Do what we can to hopefully find the song the user wanted to open. - val displayName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) - val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)) - songs.find { it.path.name == displayName && it.size == size } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt index 04cc15185..1f0aee05b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt @@ -25,7 +25,7 @@ import com.google.android.material.checkbox.MaterialCheckBox import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSeparatorsBinding import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.context class SeparatorsDialog : ViewBindingDialogFragment() { diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt index fdb1f611a..e57832c9b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt @@ -21,14 +21,14 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding +import org.oxycblt.auxio.list.ItemClickCallback +import org.oxycblt.auxio.list.recycler.DialogViewHolder import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.ui.recycler.DialogViewHolder -import org.oxycblt.auxio.ui.recycler.ItemClickListener import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.inflater /** The adapter that displays a list of artist choices in the picker UI. */ -class ArtistChoiceAdapter(private val listener: ItemClickListener) : +class ArtistChoiceAdapter(private val callback: ItemClickCallback) : RecyclerView.Adapter() { private var artists = listOf() @@ -38,7 +38,7 @@ class ArtistChoiceAdapter(private val listener: ItemClickListener) : ArtistChoiceViewHolder.new(parent) override fun onBindViewHolder(holder: ArtistChoiceViewHolder, position: Int) = - holder.bind(artists[position], listener) + holder.bind(artists[position], callback) fun submitList(newArtists: List) { if (newArtists != artists) { @@ -55,10 +55,10 @@ class ArtistChoiceAdapter(private val listener: ItemClickListener) : */ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : DialogViewHolder(binding.root) { - fun bind(artist: Artist, listener: ItemClickListener) { + fun bind(artist: Artist, callback: ItemClickCallback) { binding.pickerImage.bind(artist) binding.pickerName.text = artist.resolveName(binding.context) - binding.root.setOnClickListener { listener.onItemClick(artist) } + binding.root.setOnClickListener { callback.onClick(artist) } } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt index 912e566ea..734dce3af 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt @@ -21,9 +21,9 @@ import android.os.Bundle import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.databinding.DialogMusicPickerBinding -import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.ui.NavigationViewModel -import org.oxycblt.auxio.ui.recycler.Item +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.shared.NavigationViewModel /** * The [ArtistPickerDialog] for ambiguous artist navigation operations. @@ -31,6 +31,7 @@ import org.oxycblt.auxio.ui.recycler.Item */ class ArtistNavigationPickerDialog : ArtistPickerDialog() { private val navModel: NavigationViewModel by activityViewModels() + private val args: ArtistNavigationPickerDialogArgs by navArgs() override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { @@ -38,9 +39,9 @@ class ArtistNavigationPickerDialog : ArtistPickerDialog() { super.onBindingCreated(binding, savedInstanceState) } - override fun onItemClick(item: Item) { - super.onItemClick(item) - check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" } + override fun onChoiceConfirmed(item: Item) { + super.onChoiceConfirmed(item) + check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" } navModel.exploreNavigateTo(item) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt index f1a2eae24..83917a8de 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt @@ -24,15 +24,15 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicPickerBinding -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.ItemClickListener +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.ItemClickCallback +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.collectImmediately -abstract class ArtistPickerDialog : - ViewBindingDialogFragment(), ItemClickListener { +abstract class ArtistPickerDialog : ViewBindingDialogFragment() { protected val pickerModel: MusicPickerViewModel by viewModels() - private val artistAdapter = ArtistChoiceAdapter(this) + private val artistAdapter = ArtistChoiceAdapter(ItemClickCallback(::onChoiceConfirmed)) override fun onCreateBinding(inflater: LayoutInflater) = DialogMusicPickerBinding.inflate(inflater) @@ -43,6 +43,7 @@ abstract class ArtistPickerDialog : override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { binding.pickerRecycler.adapter = artistAdapter + collectImmediately(pickerModel.currentArtists) { artists -> if (!artists.isNullOrEmpty()) { artistAdapter.submitList(artists) @@ -56,7 +57,8 @@ abstract class ArtistPickerDialog : binding.pickerRecycler.adapter = null } - override fun onItemClick(item: Item) { + open fun onChoiceConfirmed(item: Item) { + check(item is Artist) { "Unexpected datatype: ${item::class.java}" } findNavController().navigateUp() } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt index 36b9644db..fc40f2fa6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt @@ -20,9 +20,9 @@ package org.oxycblt.auxio.music.picker import android.os.Bundle import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.databinding.DialogMusicPickerBinding +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.androidActivityViewModels /** @@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.androidActivityViewModels */ class ArtistPlaybackPickerDialog : ArtistPickerDialog() { private val playbackModel: PlaybackViewModel by androidActivityViewModels() + private val args: ArtistPlaybackPickerDialogArgs by navArgs() override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { @@ -38,8 +39,8 @@ class ArtistPlaybackPickerDialog : ArtistPickerDialog() { super.onBindingCreated(binding, savedInstanceState) } - override fun onItemClick(item: Item) { - super.onItemClick(item) + override fun onChoiceConfirmed(item: Item) { + super.onChoiceConfirmed(item) check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" } pickerModel.currentSong.value?.let { song -> playbackModel.playFromArtist(song, item) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirAdapter.kt index 705659072..c4f0c3346 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirAdapter.kt @@ -21,7 +21,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.ItemMusicDirBinding -import org.oxycblt.auxio.ui.recycler.DialogViewHolder +import org.oxycblt.auxio.list.recycler.DialogViewHolder import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.inflater diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt index 93064193e..93d2f067c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt @@ -29,7 +29,7 @@ import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicDirsBinding import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD @@ -59,7 +59,8 @@ class MusicDirsDialog : .setPositiveButton(R.string.lbl_save) { _, _ -> val dirs = settings.getMusicDirs(storageManager) val newDirs = - MusicDirs(dirs = dirAdapter.dirs, shouldInclude = isInclude(requireBinding())) + MusicDirs( + dirs = dirAdapter.dirs, shouldInclude = isUiModeInclude(requireBinding())) if (dirs != newDirs) { logD("Committing changes") settings.setMusicDirs(newDirs) @@ -122,7 +123,7 @@ class MusicDirsDialog : super.onSaveInstanceState(outState) outState.putStringArrayList( KEY_PENDING_DIRS, ArrayList(dirAdapter.dirs.map { it.toString() })) - outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding())) + outState.putBoolean(KEY_PENDING_MODE, isUiModeInclude(requireBinding())) } override fun onDestroyBinding(binding: DialogMusicDirsBinding) { @@ -166,14 +167,14 @@ class MusicDirsDialog : private fun updateMode() { val binding = requireBinding() - if (isInclude(binding)) { + if (isUiModeInclude(binding)) { binding.dirsModeDesc.setText(R.string.set_dirs_mode_include_desc) } else { binding.dirsModeDesc.setText(R.string.set_dirs_mode_exclude_desc) } } - private fun isInclude(binding: DialogMusicDirsBinding) = + private fun isUiModeInclude(binding: DialogMusicDirsBinding) = binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt index e38ec29a8..bb3d11312 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt @@ -23,7 +23,7 @@ import androidx.core.app.NotificationCompat import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.system.ServiceNotification +import org.oxycblt.auxio.shared.ServiceNotification import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.newMainPendingIntent diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index bfd214404..2fa9dacaa 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -35,7 +35,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.system.ForegroundManager +import org.oxycblt.auxio.shared.ForegroundManager import org.oxycblt.auxio.util.contentResolverSafe import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD 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 4d74f8952..bd1278866 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -25,9 +25,9 @@ import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.MainNavigationAction -import org.oxycblt.auxio.ui.NavigationViewModel -import org.oxycblt.auxio.ui.fragment.ViewBindingFragment +import org.oxycblt.auxio.shared.MainNavigationAction +import org.oxycblt.auxio.shared.NavigationViewModel +import org.oxycblt.auxio.shared.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.getAttrColorCompat @@ -63,16 +63,29 @@ class PlaybackBarFragment : ViewBindingFragment() { binding.playbackSong.isSelected = true binding.playbackInfo.isSelected = true + binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() } + setupSecondaryActions(binding, Settings(context)) + // Load the track color in manually as it's unclear whether the track actually supports // using a ColorStateList in the resources binding.playbackProgressBar.trackColor = context.getColorCompat(R.color.sel_track).defaultColor - binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() } + // -- VIEWMODEL SETUP --- - // Update the secondary action to match the setting. + collectImmediately(playbackModel.song, ::updateSong) + collectImmediately(playbackModel.isPlaying, ::updatePlaying) + collectImmediately(playbackModel.positionDs, ::updatePosition) + } - when (Settings(context).actionMode) { + override fun onDestroyBinding(binding: FragmentPlaybackBarBinding) { + super.onDestroyBinding(binding) + binding.playbackSong.isSelected = false + binding.playbackInfo.isSelected = false + } + + private fun setupSecondaryActions(binding: FragmentPlaybackBarBinding, settings: Settings) { + when (settings.actionMode) { ActionMode.NEXT -> { binding.playbackSecondaryAction.apply { setIconResource(R.drawable.ic_skip_next_24) @@ -99,18 +112,6 @@ class PlaybackBarFragment : ViewBindingFragment() { } } } - - // -- VIEWMODEL SETUP --- - - collectImmediately(playbackModel.song, ::updateSong) - collectImmediately(playbackModel.isPlaying, ::updateIsPlaying) - collectImmediately(playbackModel.positionDs, ::updatePosition) - } - - override fun onDestroyBinding(binding: FragmentPlaybackBarBinding) { - super.onDestroyBinding(binding) - binding.playbackSong.isSelected = false - binding.playbackInfo.isSelected = false } private fun updateSong(song: Song?) { @@ -124,7 +125,7 @@ class PlaybackBarFragment : ViewBindingFragment() { } } - private fun updateIsPlaying(isPlaying: Boolean) { + private fun updatePlaying(isPlaying: Boolean) { requireBinding().playbackPlayPause.isActivated = isPlaying } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBottomSheetBehavior.kt similarity index 91% rename from app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt rename to app/src/main/java/org/oxycblt/auxio/playback/PlaybackBottomSheetBehavior.kt index d6e6a1b44..1169a69b1 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBottomSheetBehavior.kt @@ -25,7 +25,7 @@ import android.view.View import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.AuxioSheetBehavior +import org.oxycblt.auxio.shared.AuxioBottomSheetBehavior import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getDimen @@ -34,8 +34,8 @@ import org.oxycblt.auxio.util.getDimen * to make bottom sheets like this work. * @author OxygenCobalt */ -class PlaybackSheetBehavior(context: Context, attributeSet: AttributeSet?) : - AuxioSheetBehavior(context, attributeSet) { +class PlaybackBottomSheetBehavior(context: Context, attributeSet: AttributeSet?) : + AuxioBottomSheetBehavior(context, attributeSet) { val sheetBackgroundDrawable = MaterialShapeDrawable.createWithElevationOverlay(context).apply { fillColor = context.getAttrColorCompat(R.attr.colorSurface) 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 807b72a66..d0a756087 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -24,18 +24,19 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.widget.Toolbar import androidx.core.view.updatePadding import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song 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.fragment.MenuFragment +import org.oxycblt.auxio.shared.MainNavigationAction +import org.oxycblt.auxio.shared.NavigationViewModel +import org.oxycblt.auxio.shared.ViewBindingFragment +import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -47,10 +48,10 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * * TODO: Make seek thumb grow when selected */ -class PlaybackPanelFragment : - MenuFragment(), - StyledSeekBar.Callback, - Toolbar.OnMenuItemClickListener { +class PlaybackPanelFragment : ViewBindingFragment() { + private val playbackModel: PlaybackViewModel by androidActivityViewModels() + private val navModel: NavigationViewModel by activityViewModels() + // AudioEffect expects you to use startActivityForResult with the panel intent. Use // the contract analogue for this since there is no built-in contract for AudioEffect. private val activityLauncher by lifecycleObject { @@ -76,7 +77,10 @@ class PlaybackPanelFragment : binding.playbackToolbar.apply { setNavigationOnClickListener { navModel.mainNavigateTo(MainNavigationAction.Collapse) } - setOnMenuItemClickListener(this@PlaybackPanelFragment) + setOnMenuItemClickListener { + handleMenuItem(it) + true + } } // Make sure we enable marquee on the song info @@ -96,7 +100,7 @@ class PlaybackPanelFragment : setOnClickListener { playbackModel.song.value?.let { showCurrentAlbum() } } } - binding.playbackSeekBar.callback = this + binding.playbackSeekBar.onSeekConfirmed = playbackModel::seekTo binding.playbackRepeat.setOnClickListener { playbackModel.incrementRepeatMode() } binding.playbackSkipPrev.setOnClickListener { playbackModel.prev() } @@ -115,18 +119,14 @@ class PlaybackPanelFragment : } override fun onDestroyBinding(binding: FragmentPlaybackPanelBinding) { - binding.playbackToolbar.setOnMenuItemClickListener(null) - // Leaving marquee on will cause a leak binding.playbackSong.isSelected = false binding.playbackArtist.isSelected = false binding.playbackAlbum.isSelected = false - - binding.playbackSeekBar.callback = null } - override fun onMenuItemClick(item: MenuItem): Boolean { - return when (item.itemId) { + private fun handleMenuItem(item: MenuItem) { + when (item.itemId) { R.id.action_open_equalizer -> { val equalizerIntent = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL) @@ -139,16 +139,12 @@ class PlaybackPanelFragment : } catch (e: ActivityNotFoundException) { requireContext().showToast(R.string.err_no_app) } - - true } R.id.action_go_artist -> { showCurrentArtist() - true } R.id.action_go_album -> { showCurrentAlbum() - true } R.id.action_song_detail -> { playbackModel.song.value?.let { song -> @@ -156,17 +152,10 @@ class PlaybackPanelFragment : MainNavigationAction.Directions( MainFragmentDirections.actionShowDetails(song.uid))) } - - true } - else -> false } } - override fun seekTo(positionDs: Long) { - playbackModel.seekTo(positionDs) - } - private fun updateSong(song: Song?) { if (song == null) return val binding = requireBinding() @@ -208,6 +197,7 @@ class PlaybackPanelFragment : val song = playbackModel.song.value ?: return navModel.exploreNavigateTo(song.artists) } + private fun showCurrentAlbum() { val song = playbackModel.song.value ?: return navModel.exploreNavigateTo(song.album) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index 1cfdc413c..02ed6f96e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -27,10 +27,10 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemQueueSongBinding +import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter +import org.oxycblt.auxio.list.recycler.SongViewHolder +import org.oxycblt.auxio.list.recycler.SyncListDiffer import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.ui.recycler.PlayingIndicatorAdapter -import org.oxycblt.auxio.ui.recycler.SongViewHolder -import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getDimen diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueBottomSheetBehavior.kt similarity index 92% rename from app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt rename to app/src/main/java/org/oxycblt/auxio/playback/queue/QueueBottomSheetBehavior.kt index 52fd5a980..cc615ff0c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueBottomSheetBehavior.kt @@ -24,7 +24,7 @@ import android.view.WindowInsets import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.AuxioSheetBehavior +import org.oxycblt.auxio.shared.AuxioBottomSheetBehavior import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getDimen import org.oxycblt.auxio.util.getDimenSize @@ -35,8 +35,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * The bottom sheet behavior designed for the queue in particular. * @author OxygenCobalt */ -class QueueSheetBehavior(context: Context, attributeSet: AttributeSet?) : - AuxioSheetBehavior(context, attributeSet) { +class QueueBottomSheetBehavior(context: Context, attributeSet: AttributeSet?) : + AuxioBottomSheetBehavior(context, attributeSet) { private var barHeight = 0 private var barSpacing = context.getDimenSize(R.dimen.spacing_small) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index b79b1852c..26c0b2146 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.queue import android.os.Bundle import android.view.LayoutInflater -import androidx.core.view.isInvisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.ItemTouchHelper @@ -28,7 +27,7 @@ import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.fragment.ViewBindingFragment +import org.oxycblt.auxio.shared.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD @@ -52,16 +51,6 @@ class QueueFragment : ViewBindingFragment(), QueueItemList binding.queueRecycler.apply { adapter = queueAdapter touchHelper.attachToRecyclerView(this) - - // Sometimes the scroll can change without the listener being updated, so we also - // check for relayout events. - addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> invalidateDivider() } - addOnScrollListener( - object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - invalidateDivider() - } - }) } // --- VIEWMODEL SETUP ---- @@ -95,10 +84,6 @@ class QueueFragment : ViewBindingFragment(), QueueItemList queueAdapter.submitList(queue) } - binding.queueDivider.isInvisible = - (binding.queueRecycler.layoutManager as LinearLayoutManager) - .findFirstCompletelyVisibleItemPosition() < 1 - queueModel.finishReplace() val scrollTo = queueModel.scrollTo @@ -117,11 +102,4 @@ class QueueFragment : ViewBindingFragment(), QueueItemList queueAdapter.updateIndicator(index, isPlaying) } - - private fun invalidateDivider() { - val binding = requireBinding() - binding.queueDivider.isInvisible = - (binding.queueRecycler.layoutManager as LinearLayoutManager) - .findFirstCompletelyVisibleItemPosition() < 1 - } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt index 15d135227..f5499bf96 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt @@ -25,7 +25,7 @@ import kotlin.math.abs import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogPreAmpBinding import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.context /** diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt index 21cd5a6c9..12e624822 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt @@ -29,7 +29,7 @@ import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.state.RepeatMode -import org.oxycblt.auxio.ui.system.ServiceNotification +import org.oxycblt.auxio.shared.ServiceNotification import org.oxycblt.auxio.util.newBroadcastPendingIntent import org.oxycblt.auxio.util.newMainPendingIntent diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 1af044431..8c66d679a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -55,7 +55,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateDatabase import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.system.ForegroundManager +import org.oxycblt.auxio.shared.ForegroundManager import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.widgets.WidgetComponent import org.oxycblt.auxio.widgets.WidgetProvider diff --git a/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt index eaeb75552..895fa38c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt @@ -45,7 +45,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 binding.seekBarSlider.addOnChangeListener(this) } - var callback: Callback? = null + var onSeekConfirmed: ((Long) -> Unit)? = null /** * The current position, in seconds. This is the current value of the SeekBar and is indicated @@ -104,17 +104,10 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 logD("Confirming seek") // End of seek event, send off new value to callback. isActivated = false - callback?.seekTo(slider.value.toLong()) + onSeekConfirmed?.invoke(slider.value.toLong()) } override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { binding.seekBarPosition.text = value.toLong().formatDurationDs(true) } - - interface Callback { - /** - * Called when a seek event was completed and the new position must be seeked to by the app. - */ - fun seekTo(positionDs: Long) - } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt index b4b6d693d..e5de315d9 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt @@ -20,13 +20,14 @@ package org.oxycblt.auxio.search import android.view.ViewGroup import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.list.* +import org.oxycblt.auxio.list.recycler.* import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.ui.recycler.* -class SearchAdapter(private val listener: MenuItemListener) : +class SearchAdapter(private val callback: ItemSelectCallback) : SelectionIndicatorAdapter(), AuxioRecyclerView.SpanSizeLookup { private val differ = AsyncListDiffer(this, DIFFER) @@ -61,10 +62,10 @@ class SearchAdapter(private val listener: MenuItemListener) : if (payloads.isEmpty()) { when (val item = differ.currentList[position]) { - is Song -> (holder as SongViewHolder).bind(item, listener) - is Album -> (holder as AlbumViewHolder).bind(item, listener) - is Artist -> (holder as ArtistViewHolder).bind(item, listener) - is Genre -> (holder as GenreViewHolder).bind(item, listener) + is Song -> (holder as SongViewHolder).bind(item, callback) + is Album -> (holder as AlbumViewHolder).bind(item, callback) + is Artist -> (holder as ArtistViewHolder).bind(item, callback) + is Genre -> (holder as GenreViewHolder).bind(item, callback) is Header -> (holder as HeaderViewHolder).bind(item) } } 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 17387bbed..2ca0d557b 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -22,16 +22,17 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.inputmethod.InputMethodManager -import androidx.appcompat.widget.Toolbar import androidx.core.view.isInvisible import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.google.android.material.transition.MaterialSharedAxis import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentSearchBinding +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.ItemSelectCallback +import org.oxycblt.auxio.list.SelectionFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -40,26 +41,20 @@ import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.MenuFragment -import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.ui.recycler.MenuItemListener -import org.oxycblt.auxio.ui.selection.SelectionToolbarOverlay -import org.oxycblt.auxio.ui.selection.SelectionViewModel import org.oxycblt.auxio.util.* /** - * A [Fragment] that allows for the searching of the entire music library. - * FIXME: Keyboard logic is really wonky + * A [Fragment] that allows for the searching of the entire music library. TODO: Minor rework with + * better keyboard logic, recycler updating, and chips * @author OxygenCobalt */ -class SearchFragment : - MenuFragment(), MenuItemListener, Toolbar.OnMenuItemClickListener, SelectionToolbarOverlay.Callback { +class SearchFragment : SelectionFragment() { // SearchViewModel is only scoped to this Fragment private val searchModel: SearchViewModel by androidViewModels() - private val selectionModel: SelectionViewModel by activityViewModels() - private val searchAdapter = SearchAdapter(this) + private val searchAdapter = + SearchAdapter(ItemSelectCallback(::handleClick, ::handleOpenMenu, ::handleSelect)) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } private val imm: InputMethodManager by lifecycleObject { binding -> binding.context.getSystemServiceCompat(InputMethodManager::class) @@ -78,7 +73,7 @@ class SearchFragment : override fun onCreateBinding(inflater: LayoutInflater) = FragmentSearchBinding.inflate(inflater) override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) { - binding.searchToolbarOverlay.callback = this + setupOverlay(binding.searchToolbarOverlay) binding.searchToolbar.apply { val itemIdToSelect = @@ -92,17 +87,11 @@ class SearchFragment : menu.findItem(itemIdToSelect).isChecked = true - setNavigationOnClickListener { - // Reset selection (navigating to another selectable screen) - selectionModel.consume() - - // Drop keyboard as it's no longer needed - imm.hide() - - findNavController().navigateUp() + setNavigationOnClickListener { handleSearchNavigateUp() } + setOnMenuItemClickListener { + handleSearchMenuItem(it) + true } - - setOnMenuItemClickListener(this@SearchFragment) } binding.searchEditText.apply { @@ -113,9 +102,7 @@ class SearchFragment : if (!launchedKeyboard) { // Auto-open the keyboard when this view is shown - requestFocus() - postDelayed(200) { imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } - + imm.show(this) launchedKeyboard = true } } @@ -124,76 +111,58 @@ class SearchFragment : // --- VIEWMODEL SETUP --- - collectImmediately(searchModel.searchResults, ::handleResults) + collectImmediately(searchModel.searchResults, ::updateResults) + collectImmediately( - playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) + collect(navModel.exploreNavigationItem, ::handleNavigation) - collectImmediately(selectionModel.selected, ::handleSelection) + collectImmediately(selectionModel.selected, ::updateSelection) } override fun onDestroyBinding(binding: FragmentSearchBinding) { - binding.searchToolbarOverlay.callback = null - binding.searchToolbar.setOnMenuItemClickListener(null) binding.searchRecycler.adapter = null } - override fun onMenuItemClick(item: MenuItem): Boolean { + override fun onClick(music: Music) { + when (music) { + is Song -> + when (settings.libPlaybackMode) { + MusicMode.SONGS -> playbackModel.playFromAll(music) + MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) + MusicMode.ARTISTS -> playbackModel.playFromArtist(music) + else -> error("Unexpected playback mode: ${settings.libPlaybackMode}") + } + is MusicParent -> navModel.exploreNavigateTo(music) + } + } + + private fun handleSearchNavigateUp() { + // Reset selection (navigating to another selectable screen) + selectionModel.consume() + // Drop keyboard as it's no longer needed + imm.hide() + findNavController().navigateUp() + } + + private fun handleSearchMenuItem(item: MenuItem) { + // Ignore junk sub-menu click events if (item.itemId != R.id.submenu_filtering) { searchModel.updateFilterModeWithId(item.itemId) - item.isChecked = true - } - - return true - } - - override fun onClearSelection() { - selectionModel.consume() - } - - override fun onPlaySelectionNext() { - playbackModel.playNext(selectionModel.consume()) - requireContext().showToast(R.string.lng_queue_added) - } - - override fun onAddSelectionToQueue() { - playbackModel.addToQueue(selectionModel.consume()) - requireContext().showToast(R.string.lng_queue_added) - } - - override fun onItemClick(item: Item) { - check(item is Music) { "Unexpected datatype ${item::class.simpleName}"} - if (selectionModel.selected.value.isEmpty()) { - when (item) { - is Song -> - when (settings.libPlaybackMode) { - MusicMode.SONGS -> playbackModel.playFromAll(item) - MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) - MusicMode.ARTISTS -> playbackModel.playFromArtist(item) - else -> error("Unexpected playback mode: ${settings.libPlaybackMode}") - } - is MusicParent -> navModel.exploreNavigateTo(item) - } - } else { - selectionModel.select(item) } } - override fun onSelect(item: Item) { - check(item is Music) { "Unexpected datatype ${item::class.simpleName}"} - selectionModel.select(item) - } - - override fun onOpenMenu(item: Item, anchor: View) { + private fun handleOpenMenu(item: Item, anchor: View) { when (item) { - is Song -> musicMenu(anchor, R.menu.menu_song_actions, item) - is Album -> musicMenu(anchor, R.menu.menu_album_actions, item) - is Artist -> musicMenu(anchor, R.menu.menu_artist_actions, item) - is Genre -> musicMenu(anchor, R.menu.menu_artist_actions, item) + is Song -> openMusicMenu(anchor, R.menu.menu_song_actions, item) + is Album -> openMusicMenu(anchor, R.menu.menu_album_actions, item) + is Artist -> openMusicMenu(anchor, R.menu.menu_artist_actions, item) + is Genre -> openMusicMenu(anchor, R.menu.menu_artist_actions, item) else -> logW("Unexpected datatype when opening menu: ${item::class.java}") } } - private fun handleResults(results: List) { + private fun updateResults(results: List) { val binding = requireBinding() searchAdapter.submitList(results.toMutableList()) { @@ -206,20 +175,21 @@ class SearchFragment : binding.searchRecycler.isInvisible = results.isEmpty() } - private fun handlePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { searchAdapter.updateIndicator(parent ?: song, isPlaying) } private fun handleNavigation(item: Music?) { - findNavController() - .navigate( - 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) - else -> return - }) + 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) + else -> return + } + + findNavController().navigate(action) // Reset selection (navigating to another selectable screen) selectionModel.consume() @@ -228,13 +198,21 @@ class SearchFragment : imm.hide() } - private fun handleSelection(selected: List) { - searchAdapter.updateSelection(selected) - if (requireBinding().searchToolbarOverlay.updateSelectionAmount(selected.size) && selected.isNotEmpty()) { + private fun updateSelection(selected: List) { + searchAdapter.setSelected(selected) + if (requireBinding().searchToolbarOverlay.updateSelectionAmount(selected.size) && + selected.isNotEmpty()) { imm.hide() } } + private fun InputMethodManager.show(view: View) { + view.apply { + requestFocus() + postDelayed(200) { showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } + } + } + private fun InputMethodManager.hide() { hideSoftInputFromWindow(requireView().windowToken, InputMethodManager.HIDE_NOT_ALWAYS) } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index c7ada5d8d..44ea6c3b4 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.yield import org.oxycblt.auxio.R +import org.oxycblt.auxio.list.Header +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -39,8 +41,6 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.recycler.Header -import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.logD diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index 8de188012..835fee212 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentAboutBinding import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.ui.fragment.ViewBindingFragment +import org.oxycblt.auxio.shared.ViewBindingFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.showToast diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index e3fef5ae4..8c3ba43c0 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -35,7 +35,7 @@ import org.oxycblt.auxio.music.storage.MusicDirs import org.oxycblt.auxio.playback.ActionMode import org.oxycblt.auxio.playback.replaygain.ReplayGainMode import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp -import org.oxycblt.auxio.ui.accent.Accent +import org.oxycblt.auxio.settings.accent.Accent import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt index f69d58244..5e26640c2 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt @@ -23,7 +23,7 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.google.android.material.transition.MaterialFadeThrough import org.oxycblt.auxio.databinding.FragmentSettingsBinding -import org.oxycblt.auxio.ui.fragment.ViewBindingFragment +import org.oxycblt.auxio.shared.ViewBindingFragment /** * A container [Fragment] for the settings menu. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt b/app/src/main/java/org/oxycblt/auxio/settings/accent/Accent.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt rename to app/src/main/java/org/oxycblt/auxio/settings/accent/Accent.kt index 6213579e9..14c43fdb8 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/accent/Accent.kt @@ -15,10 +15,11 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.accent +package org.oxycblt.auxio.settings.accent import android.os.Build import org.oxycblt.auxio.R +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.util.logW private val ACCENT_NAMES = @@ -114,7 +115,7 @@ private val ACCENT_PRIMARY_COLORS = * @property primary The primary color resource for this accent * @author OxygenCobalt */ -class Accent private constructor(val index: Int) { +class Accent private constructor(val index: Int) : Item { val name: Int get() = ACCENT_NAMES[index] val theme: Int diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentAdapter.kt similarity index 88% rename from app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/settings/accent/AccentAdapter.kt index f71e721ba..d699afa92 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentAdapter.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.accent +package org.oxycblt.auxio.settings.accent import android.view.View import android.view.ViewGroup @@ -23,6 +23,7 @@ import androidx.appcompat.widget.TooltipCompat import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemAccentBinding +import org.oxycblt.auxio.list.ItemClickCallback import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getColorCompat import org.oxycblt.auxio.util.inflater @@ -31,7 +32,8 @@ import org.oxycblt.auxio.util.inflater * An adapter that displays the accent palette. * @author OxygenCobalt */ -class AccentAdapter(private val listener: Listener) : RecyclerView.Adapter() { +class AccentAdapter(private val callback: ItemClickCallback) : + RecyclerView.Adapter() { var selectedAccent: Accent? = null private set @@ -50,7 +52,7 @@ class AccentAdapter(private val listener: Listener) : RecyclerView.Adapter. */ -package org.oxycblt.auxio.ui.accent +package org.oxycblt.auxio.settings.accent import android.os.Bundle import android.view.LayoutInflater @@ -23,8 +23,10 @@ import androidx.appcompat.app.AlertDialog import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogAccentBinding +import org.oxycblt.auxio.list.Item +import org.oxycblt.auxio.list.ItemClickCallback import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment +import org.oxycblt.auxio.shared.ViewBindingDialogFragment import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull @@ -33,9 +35,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * Dialog responsible for showing the list of accents to select. * @author OxygenCobalt */ -class AccentCustomizeDialog : - ViewBindingDialogFragment(), AccentAdapter.Listener { - private var accentAdapter = AccentAdapter(this) +class AccentCustomizeDialog : ViewBindingDialogFragment() { + private var accentAdapter = AccentAdapter(ItemClickCallback(::handleClick)) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater) @@ -74,8 +75,9 @@ class AccentCustomizeDialog : binding.accentRecycler.adapter = null } - override fun onAccentSelected(accent: Accent) { - accentAdapter.setSelectedAccent(accent) + private fun handleClick(item: Item) { + check(item is Accent) { "Unexpected datatype: ${item::class.java}" } + accentAdapter.setSelectedAccent(item) } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentGridLayoutManager.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt rename to app/src/main/java/org/oxycblt/auxio/settings/accent/AccentGridLayoutManager.kt index a8b16070c..e9666b1b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentGridLayoutManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.accent +package org.oxycblt.auxio.settings.accent import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt index b6dd6e3e8..addfec454 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt @@ -35,7 +35,7 @@ import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.SettingsFragmentDirections -import org.oxycblt.auxio.ui.NavigationViewModel +import org.oxycblt.auxio.shared.NavigationViewModel import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.logD diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/shared/AuxioAppBarLayout.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt rename to app/src/main/java/org/oxycblt/auxio/shared/AuxioAppBarLayout.kt index 1ee840c05..9c3eb3cb1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/AuxioAppBarLayout.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio.shared import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/shared/AuxioBottomSheetBehavior.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt rename to app/src/main/java/org/oxycblt/auxio/shared/AuxioBottomSheetBehavior.kt index f2a3bdcd7..04af49bde 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/AuxioBottomSheetBehavior.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio.shared import android.content.Context import android.graphics.drawable.Drawable @@ -34,7 +34,7 @@ import org.oxycblt.auxio.util.systemGestureInsetsCompat * the vendored code because of course I have to) for normal use without absurd bugs. * @author OxygenCobalt */ -abstract class AuxioSheetBehavior(context: Context, attributeSet: AttributeSet?) : +abstract class AuxioBottomSheetBehavior(context: Context, attributeSet: AttributeSet?) : NeoBottomSheetBehavior(context, attributeSet) { private var setup = false diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt b/app/src/main/java/org/oxycblt/auxio/shared/BottomSheetContentBehavior.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt rename to app/src/main/java/org/oxycblt/auxio/shared/BottomSheetContentBehavior.kt index 21c1b55d8..a0b1e06f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/BottomSheetContentBehavior.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio.shared import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/oxycblt/auxio/ui/system/ForegroundManager.kt b/app/src/main/java/org/oxycblt/auxio/shared/ForegroundManager.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/system/ForegroundManager.kt rename to app/src/main/java/org/oxycblt/auxio/shared/ForegroundManager.kt index e6e9153b1..6ccc3cdd4 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/system/ForegroundManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/ForegroundManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.system +package org.oxycblt.auxio.shared import android.app.Service import androidx.core.app.ServiceCompat diff --git a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt b/app/src/main/java/org/oxycblt/auxio/shared/NavigationViewModel.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/shared/NavigationViewModel.kt index 00ad683dc..0c8200cad 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/NavigationViewModel.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio.shared import androidx.lifecycle.ViewModel import androidx.navigation.NavDirections @@ -108,7 +108,7 @@ class NavigationViewModel : ViewModel() { /** * Represents the navigation options for the Main Fragment, which tends to be multiple layers above * normal fragments. This can be passed to [NavigationViewModel.mainNavigateTo] in order to - * facilitate navigation without stupid fragment hacks. + * facilitate navigation without workarounds.. */ sealed class MainNavigationAction { /** Expand the playback panel. */ diff --git a/app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt b/app/src/main/java/org/oxycblt/auxio/shared/ServiceNotification.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt rename to app/src/main/java/org/oxycblt/auxio/shared/ServiceNotification.kt index 37cefce00..ae04beb5a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/ServiceNotification.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.system +package org.oxycblt.auxio.shared import android.content.Context import androidx.annotation.StringRes diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/shared/ViewBindingDialogFragment.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt rename to app/src/main/java/org/oxycblt/auxio/shared/ViewBindingDialogFragment.kt index 424296c20..45c281135 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/ViewBindingDialogFragment.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.fragment +package org.oxycblt.auxio.shared import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt b/app/src/main/java/org/oxycblt/auxio/shared/ViewBindingFragment.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt rename to app/src/main/java/org/oxycblt/auxio/shared/ViewBindingFragment.kt index 5f7a009a6..552a75521 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/shared/ViewBindingFragment.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui.fragment +package org.oxycblt.auxio.shared import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/res/layout-w600dp-land/fragment_main.xml b/app/src/main/res/layout-w600dp-land/fragment_main.xml index a4a0fd849..0c31f7110 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_main.xml +++ b/app/src/main/res/layout-w600dp-land/fragment_main.xml @@ -12,7 +12,7 @@ android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="org.oxycblt.auxio.ui.BottomSheetContentBehavior" + app:layout_behavior="org.oxycblt.auxio.shared.BottomSheetContentBehavior" app:navGraph="@navigation/nav_explore" tools:layout="@layout/fragment_home" /> @@ -20,7 +20,7 @@ android:id="@+id/playback_sheet" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="org.oxycblt.auxio.playback.PlaybackSheetBehavior"> + app:layout_behavior="org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior"> - diff --git a/app/src/main/res/layout/dialog_music_picker.xml b/app/src/main/res/layout/dialog_music_picker.xml index b358ea46b..f7f81f9af 100644 --- a/app/src/main/res/layout/dialog_music_picker.xml +++ b/app/src/main/res/layout/dialog_music_picker.xml @@ -1,5 +1,5 @@ - - - @@ -21,7 +21,7 @@ app:navigationIcon="@drawable/ic_back_24" app:title="@string/lbl_about" /> - + - - - @@ -26,7 +26,7 @@ app:menu="@menu/menu_home" app:title="@string/info_app_name" /> - + - + - @@ -22,7 +22,7 @@ style="@style/Widget.Auxio.DisableDropShadows" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="org.oxycblt.auxio.playback.PlaybackSheetBehavior"> + app:layout_behavior="org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior"> + app:layout_behavior="org.oxycblt.auxio.shared.BottomSheetContentBehavior" /> + app:layout_behavior="org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior"> - - - @@ -49,11 +49,11 @@ - + - + - - - +