Create dedicated ActionMenu object

Create a dedicated object for every ActionMenu to reduce on code clutter caused by duplicated PopupMenu code.
This commit is contained in:
OxygenCobalt 2021-01-04 11:19:46 -07:00
parent f109130fb8
commit 119078fc77
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 205 additions and 281 deletions

View file

@ -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)
}
)

View file

@ -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)
}
)

View file

@ -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)
}
)

View file

@ -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)
}
/**

View file

@ -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)
}
)

View file

@ -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
}
}

View file

@ -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()
}