From 119078fc770e30bbaf3c7ee5276548b3a2128cfb Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 4 Jan 2021 11:19:46 -0700 Subject: [PATCH] Create dedicated ActionMenu object Create a dedicated object for every ActionMenu to reduce on code clutter caused by duplicated PopupMenu code. --- .../auxio/detail/AlbumDetailFragment.kt | 8 +- .../auxio/detail/ArtistDetailFragment.kt | 8 +- .../auxio/detail/GenreDetailFragment.kt | 8 +- .../oxycblt/auxio/library/LibraryFragment.kt | 21 +- .../org/oxycblt/auxio/songs/SongsFragment.kt | 8 +- .../java/org/oxycblt/auxio/ui/ActionMenu.kt | 180 +++++++++++++ .../org/oxycblt/auxio/ui/InterfaceUtils.kt | 253 +----------------- 7 files changed, 205 insertions(+), 281 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt 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 147fbf8eb..ee99fc2ad 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.PopupMenu import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.R @@ -16,8 +15,9 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.recycler.CenterSmoothScroller +import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.createToast -import org.oxycblt.auxio.ui.setupAlbumSongActions +import org.oxycblt.auxio.ui.requireCompatActivity /** * The [DetailFragment] for an album. @@ -47,9 +47,7 @@ class AlbumDetailFragment : DetailFragment() { detailModel, playbackModel, viewLifecycleOwner, doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) }, doOnLongClick = { data, view -> - PopupMenu(requireContext(), view).setupAlbumSongActions( - requireContext(), data, detailModel, playbackModel - ) + ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_IN_ALBUM) } ) 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 39821f268..913e4e305 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.PopupMenu import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.detail.adapters.ArtistDetailAdapter @@ -14,7 +13,8 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.ui.setupArtistAlbumActions +import org.oxycblt.auxio.ui.ActionMenu +import org.oxycblt.auxio.ui.requireCompatActivity /** * The [DetailFragment] for an artist. @@ -53,9 +53,7 @@ class ArtistDetailFragment : DetailFragment() { } }, doOnLongClick = { data, view -> - PopupMenu(requireContext(), view).setupArtistAlbumActions( - requireContext(), data, playbackModel - ) + ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_IN_ARTIST) } ) 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 29244946a..30c49242f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.PopupMenu import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.detail.adapters.GenreDetailAdapter @@ -14,7 +13,8 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.state.PlaybackMode -import org.oxycblt.auxio.ui.setupGenreSongActions +import org.oxycblt.auxio.ui.ActionMenu +import org.oxycblt.auxio.ui.requireCompatActivity /** * The [DetailFragment] for a genre. @@ -46,9 +46,7 @@ class GenreDetailFragment : DetailFragment() { playbackModel.playSong(it, PlaybackMode.IN_GENRE) }, doOnLongClick = { data, view -> - PopupMenu(requireContext(), view).setupGenreSongActions( - requireContext(), data, playbackModel, detailModel - ) + ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_IN_GENRE) } ) diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt index 34dc73746..c830b2cf2 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.core.view.forEach import androidx.fragment.app.Fragment @@ -28,15 +27,13 @@ import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.settings.SettingsManager +import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.accent import org.oxycblt.auxio.ui.applyColor import org.oxycblt.auxio.ui.getLandscapeSpans import org.oxycblt.auxio.ui.isLandscape +import org.oxycblt.auxio.ui.requireCompatActivity import org.oxycblt.auxio.ui.resolveAttr -import org.oxycblt.auxio.ui.setupAlbumActions -import org.oxycblt.auxio.ui.setupArtistActions -import org.oxycblt.auxio.ui.setupGenreActions -import org.oxycblt.auxio.ui.setupSongActions import org.oxycblt.auxio.ui.toColor /** @@ -197,22 +194,12 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { } /** - * Show the [PopupMenu] actions for an item. + * Show the [ActionMenu] actions for an item. * @param data The model that the actions should correspond to * @param view The anchor view the menu should be bound to. */ private fun showActionsForItem(data: BaseModel, view: View) { - val menu = PopupMenu(requireContext(), view) - - when (data) { - is Song -> menu.setupSongActions(requireContext(), data, playbackModel, detailModel) - is Album -> menu.setupAlbumActions(requireContext(), data, playbackModel, detailModel) - is Artist -> menu.setupArtistActions(data, playbackModel) - is Genre -> menu.setupGenreActions(data, playbackModel) - - else -> { - } - } + ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index fb935b071..d7475d60c 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -7,7 +7,6 @@ import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.PopupMenu import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.GridLayoutManager @@ -21,10 +20,11 @@ import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.settings.SettingsManager +import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.accent import org.oxycblt.auxio.ui.getLandscapeSpans import org.oxycblt.auxio.ui.isLandscape -import org.oxycblt.auxio.ui.setupSongActions +import org.oxycblt.auxio.ui.requireCompatActivity import org.oxycblt.auxio.ui.toColor import kotlin.math.ceil @@ -61,9 +61,7 @@ class SongsFragment : Fragment() { playbackModel.playSong(it, settingsManager.songPlaybackMode) }, doOnLongClick = { data, view -> - PopupMenu(requireContext(), view).setupSongActions( - requireContext(), data, playbackModel, detailModel - ) + ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE) } ) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt new file mode 100644 index 000000000..8e2505935 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -0,0 +1,180 @@ +package org.oxycblt.auxio.ui + +import android.view.View +import androidx.annotation.IdRes +import androidx.annotation.MenuRes +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.PopupMenu +import androidx.lifecycle.ViewModelProvider +import org.oxycblt.auxio.R +import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.playback.state.PlaybackMode + +/** + * A wrapper around [PopupMenu] that automates a ton of things across all the menus in Auxio + * @param activity [AppCompatActivity] required as both a context and ViewModelStore owner. + * @param anchor [View] This should be centered around + * @param data [BaseModel] this menu corresponds to + * @param flag Any extra flags to accompany the data. See [Companion] for more details. + */ +class ActionMenu( + activity: AppCompatActivity, + anchor: View, + private val data: BaseModel, + private val flag: Int = FLAG_NONE +) : PopupMenu(activity, anchor) { + private val context = activity.applicationContext + + private val detailModel: DetailViewModel by lazy { + ViewModelProvider(activity).get(DetailViewModel::class.java) + } + + private val playbackModel: PlaybackViewModel by lazy { + ViewModelProvider(activity).get(PlaybackViewModel::class.java) + } + + init { + val menuRes = determineMenu() + + check(menuRes != -1) { "There is no menu associated with this configuration." } + + inflate(menuRes) + setOnMenuItemClickListener { + onMenuClick(it.itemId) + true + } + show() + } + + /** + * Figure out what menu to use here, based on the data & flags + */ + @MenuRes + private fun determineMenu(): Int { + return when (data) { + is Song -> { + when (flag) { + FLAG_NONE, FLAG_IN_GENRE -> R.menu.menu_song_actions + FLAG_IN_ALBUM -> R.menu.menu_album_song_actions + + else -> -1 + } + } + + is Album -> { + when (flag) { + FLAG_NONE -> R.menu.menu_album_actions + FLAG_IN_ARTIST -> R.menu.menu_artist_album_actions + + else -> -1 + } + } + + is Artist -> R.menu.menu_artist_actions + + is Genre -> R.menu.menu_genre_actions + + else -> -1 + } + } + + private fun onMenuClick(@IdRes id: Int) { + when (id) { + R.id.action_play -> { + when (data) { + is Song -> playbackModel.playSong(data, getPlaybackModeFromFlag()) + is Album -> playbackModel.playAlbum(data, false) + is Artist -> playbackModel.playArtist(data, false) + is Genre -> playbackModel.playGenre(data, false) + + else -> {} + } + } + + R.id.action_shuffle -> { + when (data) { + is Album -> playbackModel.playAlbum(data, true) + is Artist -> playbackModel.playArtist(data, true) + is Genre -> playbackModel.playGenre(data, true) + + else -> {} + } + } + + R.id.action_play_artist -> { + if (flag == FLAG_IN_ALBUM && data is Song) { + playbackModel.playSong(data, PlaybackMode.IN_ARTIST) + } + } + + R.id.action_queue_add -> { + val success = when (data) { + is Song -> { + playbackModel.addToUserQueue(data) + true + } + is Album -> { + playbackModel.addToUserQueue(data) + true + } + + else -> false + } + + if (success) { + context.getString(R.string.label_queue_added).createToast(context) + } + } + + R.id.action_go_album -> { + if (data is Song) { + determineWhereToNavWithSong(data.album) + } + } + + R.id.action_go_artist -> { + if (data is Song) { + determineWhereToNavWithSong(data.album.artist) + } else if (data is Album) { + detailModel.navToItem(data.artist) + } + } + } + } + + private fun determineWhereToNavWithSong(parent: BaseModel) { + when (flag) { + FLAG_NONE -> detailModel.navToItem(parent) + FLAG_IN_ALBUM -> detailModel.navToParent() + FLAG_IN_GENRE -> detailModel.navToChild(parent) + } + } + + private fun getPlaybackModeFromFlag(): PlaybackMode { + return when (flag) { + FLAG_NONE -> PlaybackMode.ALL_SONGS + FLAG_IN_ALBUM -> PlaybackMode.IN_ALBUM + FLAG_IN_ARTIST -> PlaybackMode.IN_ARTIST + FLAG_IN_GENRE -> PlaybackMode.IN_GENRE + + else -> PlaybackMode.ALL_SONGS + } + } + + companion object { + /** No Flags **/ + const val FLAG_NONE = -1 + /** Flag for when an item is accessed from an artist **/ + const val FLAG_IN_ARTIST = 0 + /** Flag for when an item is accessed from an album **/ + const val FLAG_IN_ALBUM = 1 + /** Flag or when an item is accessed from a genre **/ + const val FLAG_IN_GENRE = 2 + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index d2357ec1a..2d4d43972 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -14,8 +14,10 @@ import android.widget.Toast import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.MenuRes +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.text.HtmlCompat +import androidx.fragment.app.Fragment import com.google.android.material.button.MaterialButton import org.oxycblt.auxio.R import org.oxycblt.auxio.detail.DetailViewModel @@ -24,7 +26,6 @@ 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.playback.state.PlaybackMode /** * Apply a text color to a [MenuItem] @@ -107,250 +108,14 @@ fun MaterialButton.applyAccents(highlighted: Boolean) { } /** - * Show actions for a song item, such as the ones found in [org.oxycblt.auxio.songs.SongsFragment] - * @param context [Context] required - * @param song [Song] The menu should correspond to - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - * @param detailModel The [DetailViewModel] the menu should dispatch actions to. + * Require an [AppCompatActivity] */ -fun PopupMenu.setupSongActions( - context: Context, - song: Song, - playbackModel: PlaybackViewModel, - detailModel: DetailViewModel -) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_queue_add -> { - playbackModel.addToUserQueue(song) - context.getString(R.string.label_queue_added).createToast(context) - true - } +fun Fragment.requireCompatActivity(): AppCompatActivity { + val activity = requireActivity() - R.id.action_go_artist -> { - detailModel.navToItem(song.album.artist) - true - } - - R.id.action_go_album -> { - detailModel.navToItem(song.album) - true - } - - else -> false - } + if (activity is AppCompatActivity) { + return activity + } else { + error("Required activity to be AppCompatActivity, however it wasn't.") } - - inflateAndShow(R.menu.menu_song_actions) -} - -/** - * Show actions for a album song item, such as the ones found in - * [org.oxycblt.auxio.detail.AlbumDetailFragment] - * @param context [Context] required - * @param song [Song] The menu should correspond to - * @param detailModel The [DetailViewModel] the menu should dispatch some actions to. - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - */ -fun PopupMenu.setupAlbumSongActions( - context: Context, - song: Song, - detailModel: DetailViewModel, - playbackModel: PlaybackViewModel -) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_queue_add -> { - playbackModel.addToUserQueue(song) - context.getString(R.string.label_queue_added).createToast(context) - - true - } - - R.id.action_go_artist -> { - detailModel.navToParent() - true - } - - R.id.action_play_artist -> { - playbackModel.playSong(song, PlaybackMode.IN_ARTIST) - true - } - - else -> false - } - } - inflateAndShow(R.menu.menu_album_song_actions) -} - -/** - * Show actions for an [Album]. - * @param context [Context] required - * @param album [Album] The menu should correspond to - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - * @param detailModel The [DetailViewModel] the menu should dispatch actions to. - */ -fun PopupMenu.setupAlbumActions( - context: Context, - album: Album, - playbackModel: PlaybackViewModel, - detailModel: DetailViewModel -) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_play -> { - playbackModel.playAlbum(album, false) - true - } - - R.id.action_shuffle -> { - playbackModel.playAlbum(album, true) - true - } - - R.id.action_queue_add -> { - playbackModel.addToUserQueue(album) - context.getString(R.string.label_queue_added).createToast(context) - - true - } - - R.id.action_go_artist -> { - detailModel.navToItem(album.artist) - - true - } - - else -> false - } - } - inflateAndShow(R.menu.menu_album_actions) -} - -/** - * Show actions for an [Album] in the artist detail fragment - * @param context [Context] required - * @param album [Album] The menu should correspond to - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - */ -fun PopupMenu.setupArtistAlbumActions(context: Context, album: Album, playbackModel: PlaybackViewModel) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_play -> { - playbackModel.playAlbum(album, false) - true - } - - R.id.action_shuffle -> { - playbackModel.playAlbum(album, true) - true - } - - R.id.action_queue_add -> { - playbackModel.addToUserQueue(album) - context.getString(R.string.label_queue_added).createToast(context) - - true - } - - else -> false - } - } - inflateAndShow(R.menu.menu_artist_album_actions) -} - -/** - * Show actions for an [Artist]. - * @param artist The [Artist] The menu should correspond to - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - */ -fun PopupMenu.setupArtistActions(artist: Artist, playbackModel: PlaybackViewModel) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_play -> { - playbackModel.playArtist(artist, false) - true - } - - R.id.action_shuffle -> { - playbackModel.playArtist(artist, true) - true - } - - else -> false - } - } - inflateAndShow(R.menu.menu_artist_actions) -} - -/** - * Show actions for a [Genre]. - * @param genre The [Genre] The menu should correspond to - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - */ -fun PopupMenu.setupGenreActions(genre: Genre, playbackModel: PlaybackViewModel) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_play -> { - playbackModel.playGenre(genre, true) - true - } - - R.id.action_shuffle -> { - playbackModel.playGenre(genre, true) - true - } - - else -> false - } - } - inflateAndShow(R.menu.menu_genre_actions) -} - -/** - * Show actions for a [Genre] song. Mostly identical to [setupSongActions] aside from a different - * flag being used for navigation. - * @param context [Context] required - * @param song [Song] The menu should correspond to - * @param playbackModel The [PlaybackViewModel] the menu should dispatch actions to. - * @param detailModel The [DetailViewModel] the menu should dispatch actions to. - */ -fun PopupMenu.setupGenreSongActions( - context: Context, - song: Song, - playbackModel: PlaybackViewModel, - detailModel: DetailViewModel -) { - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_queue_add -> { - playbackModel.addToUserQueue(song) - context.getString(R.string.label_queue_added).createToast(context) - true - } - - R.id.action_go_artist -> { - detailModel.navToChild(song.album.artist) - true - } - - R.id.action_go_album -> { - detailModel.navToChild(song.album) - true - } - - else -> false - } - } - - inflateAndShow(R.menu.menu_song_actions) -} - -/** - * Shortcut method that inflates a menu and shows the action menu. - * @param menuRes the menu that should be shown. - */ -private fun PopupMenu.inflateAndShow(@MenuRes menuRes: Int) { - inflate(menuRes) - show() }