Add actionmenu extension function

Simplify menu creation by using a Fragment extension to create a new ActionMenu.
This commit is contained in:
OxygenCobalt 2021-02-07 18:54:04 -07:00
parent eb5292d083
commit 26dd1036ec
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
27 changed files with 67 additions and 80 deletions

View file

@ -18,7 +18,7 @@ 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.requireCompatActivity
import org.oxycblt.auxio.ui.newMenu
/**
* The [DetailFragment] for an album.
@ -47,9 +47,7 @@ class AlbumDetailFragment : DetailFragment() {
val detailAdapter = AlbumDetailAdapter(
detailModel, playbackModel, viewLifecycleOwner,
doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
doOnLongClick = { view, data ->
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_IN_ALBUM)
}
doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ALBUM) }
)
// --- UI SETUP ---

View file

@ -15,7 +15,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.requireCompatActivity
import org.oxycblt.auxio.ui.newMenu
/**
* The [DetailFragment] for an artist.
@ -53,9 +53,7 @@ class ArtistDetailFragment : DetailFragment() {
)
}
},
doOnLongClick = { view, data ->
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_IN_ARTIST)
}
doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ARTIST) }
)
// --- UI SETUP ---

View file

@ -15,7 +15,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.requireCompatActivity
import org.oxycblt.auxio.ui.newMenu
/**
* The [DetailFragment] for a genre.
@ -46,9 +46,7 @@ class GenreDetailFragment : DetailFragment() {
doOnClick = {
playbackModel.playSong(it, PlaybackMode.IN_GENRE)
},
doOnLongClick = { view, data ->
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_IN_GENRE)
}
doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_GENRE) }
)
// --- UI SETUP ---

View file

@ -17,10 +17,9 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.ui.requireCompatActivity
import org.oxycblt.auxio.ui.newMenu
/**
* A [Fragment] that shows a custom list of [Genre], [Artist], or [Album] data. Also allows for
@ -37,9 +36,7 @@ class LibraryFragment : Fragment() {
): View {
val binding = FragmentLibraryBinding.inflate(inflater)
val libraryAdapter = LibraryAdapter(::onItemSelection) { view, data ->
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE)
}
val libraryAdapter = LibraryAdapter(::onItemSelection) { view, data -> newMenu(view, data) }
// --- UI SETUP ---
@ -63,8 +60,8 @@ class LibraryFragment : Fragment() {
binding.libraryRecycler.apply {
adapter = libraryAdapter
setHasFixedSize(true)
val spans = getSpans()
val spans = getSpans()
if (spans != 1) {
layoutManager = GridLayoutManager(requireContext(), spans)
}

View file

@ -23,10 +23,9 @@ import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.ui.requireCompatActivity
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.toColor
import org.oxycblt.auxio.ui.toStateList
@ -51,10 +50,7 @@ class SearchFragment : Fragment() {
// Apply the accents manually. Not going through the mess of converting my app's
// styling to Material given all the second-and-third-order effects it has.
val accent = Accent.get().color.toColor(requireContext())
val searchAdapter = SearchAdapter(::onItemSelection) { view, data ->
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE)
}
val searchAdapter = SearchAdapter(::onItemSelection) { view, data -> newMenu(view, data) }
// --- UI SETUP --

View file

@ -18,9 +18,8 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.ui.requireCompatActivity
import org.oxycblt.auxio.ui.newMenu
import kotlin.math.ceil
/**
@ -48,7 +47,7 @@ class SongsFragment : Fragment() {
val musicStore = MusicStore.getInstance()
val songAdapter = SongsAdapter(musicStore.songs, playbackModel::playSong) { view, data ->
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE)
newMenu(view, data)
}
// --- UI SETUP ---

View file

@ -5,6 +5,7 @@ import androidx.annotation.IdRes
import androidx.annotation.MenuRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.DetailViewModel
@ -16,20 +17,29 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
/**
* Extension method for creating and showing a new [ActionMenu].
* @param anchor [View] This should be centered around
* @param data [BaseModel] this menu corresponds to
* @param flag (Optional, defaults to [ActionMenu.FLAG_NONE]) Any extra flags to accompany the data. See [ActionMenu] for more details
*/
fun Fragment.newMenu(anchor: View, data: BaseModel, flag: Int = ActionMenu.FLAG_NONE) {
ActionMenu(requireCompatActivity(), anchor, data, flag).show()
}
/**
* A wrapper around [PopupMenu] that automates the menu creation for nearly every datatype 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 (Optional, defaults to [FLAG_NONE]) Any extra flags to accompany the data.
* See [FLAG_NONE], [FLAG_IN_ALBUM], [FLAG_IN_ARTIST] and [FLAG_IN_GENRE] for more details.
* @param flag Any extra flags to accompany the data. See [FLAG_NONE], [FLAG_IN_ALBUM], [FLAG_IN_ARTIST], [FLAG_IN_GENRE] for more detials.
* @throws IllegalArgumentException When there is no menu for this specific datatype/flag
*/
class ActionMenu(
activity: AppCompatActivity,
anchor: View,
private val data: BaseModel,
private val flag: Int = FLAG_NONE
private val flag: Int
) : PopupMenu(activity, anchor) {
private val context = activity.applicationContext
@ -54,7 +64,6 @@ class ActionMenu(
onMenuClick(it.itemId)
true
}
show()
}
/**

View file

@ -82,7 +82,7 @@
android:layout_height="@dimen/size_play_pause_compact"
android:layout_marginEnd="@dimen/margin_mid_small"
android:background="@drawable/ui_unbounded_ripple"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"
android:contentDescription="@string/description_play_pause"
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
android:tint="@color/control_color"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -190,7 +190,7 @@
<ImageButton
android:id="@+id/playback_play_pause"
style="@style/PlayPause"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"
android:contentDescription="@string/description_play_pause"
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/playback_song_container_duration"

View file

@ -188,7 +188,7 @@
<ImageButton
android:id="@+id/playback_play_pause"
style="@style/PlayPause"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"
android:contentDescription="@string/description_play_pause"
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/playback_song_container_duration"

View file

@ -175,7 +175,7 @@
android:id="@+id/playback_play_pause"
style="@style/PlayPause"
android:layout_marginBottom="@dimen/margin_medium"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"
android:contentDescription="@string/description_play_pause"
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/playback_song_duration"

View file

@ -91,7 +91,7 @@
android:layout_height="@dimen/size_play_pause_compact"
android:layout_marginEnd="@dimen/margin_mid_small"
android:background="@drawable/ui_unbounded_ripple"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"
android:contentDescription="@string/description_play_pause"
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
android:tint="@color/control_color"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -175,7 +175,7 @@
android:id="@+id/playback_play_pause"
style="@style/PlayPause"
android:layout_marginBottom="@dimen/margin_medium"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"
android:contentDescription="@string/description_play_pause"
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/playback_song_duration"

View file

@ -104,8 +104,7 @@
<string name="description_sort_alpha_up">von Z bis A sortieren</string>
<string name="description_track_number">Titel %d</string>
<string name="description_play">Abspielen</string>
<string name="description_pause">Pausieren</string>
<string name="description_play_pause">Abspielen/Pausieren</string>
<string name="description_skip_prev">Zu letzter Lied springen</string>
<string name="description_skip_next">Zu nächster Lied springen</string>
<string name="description_shuffle_on">Zufällig anschalten</string>

View file

@ -59,8 +59,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Κομμάτι %d</string>
<string name="description_play">Αναπαραγωγή</string>
<string name="description_pause">Παύση</string>
<string name="description_play_pause">Αναπαραγωγή/Παύση</string>
<string name="description_clear_user_queue">Εκκαθάριση ουράς αναπαραγωγής</string>

View file

@ -68,8 +68,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Pista %d</string>
<string name="description_pause">Pausar</string>
<string name="description_play">Reproducir</string>
<string name="description_play_pause">Reproducir/Pausar</string>
<string name="description_clear_user_queue">Vaciar cola de reproducción</string>

View file

@ -68,8 +68,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Morceau %d</string>
<string name="description_play">Lecture</string>
<string name="description_pause">Pause</string>
<string name="description_play_pause">Lecture/Pause</string>
<string name="description_clear_user_queue">Effacer la file d\'attente</string>

View file

@ -57,8 +57,7 @@
<string name="error_no_music">कोई संगीत नहीं मिला</string>
<!-- Description Namespace | Accessibility Strings -->
<string name="description_play">चलाएं</string>
<string name="description_pause">रोकें</string>
<string name="description_play_pause">चलाएं/रोकें</string>
<string name="description_error">त्रुटी</string>

View file

@ -68,8 +68,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Traccia %d</string>
<string name="description_play">Play</string>
<string name="description_pause">Pausa</string>
<string name="description_play_pause">Play/Pausa</string>
<string name="description_clear_user_queue">Svuota coda</string>

View file

@ -68,8 +68,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Nummer %d</string>
<string name="description_play">Afspelen</string>
<string name="description_pause">Pauzeren</string>
<string name="description_play_pause">Afspelen/Pauzeren</string>
<string name="description_clear_user_queue">Leeg wachtrij</string>

View file

@ -67,8 +67,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Música %d</string>
<string name="description_play">Reproduzir</string>
<string name="description_pause">Pausar</string>
<string name="description_play_pause">Reproduzir/Pausar</string>
<string name="description_clear_user_queue">Limpar fila</string>

View file

@ -67,8 +67,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Música %d</string>
<string name="description_play">Reproduzir</string>
<string name="description_pause">Pausar</string>
<string name="description_play_pause">Reproduzir/Pausar</string>
<string name="description_clear_user_queue">Limpar fila</string>

View file

@ -72,8 +72,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Melodie %d</string>
<string name="description_play">Redă</string>
<string name="description_pause">Pauză</string>
<string name="description_play_pause">Redă/Pauză</string>
<string name="description_clear_user_queue">Golește lista de redare</string>

View file

@ -66,8 +66,7 @@
<!-- Description Namespace | Accessibility Strings -->
<string name="description_track_number">Трек %d</string>
<string name="description_play">Воспроизвести</string>
<string name="description_pause">Пауза</string>
<string name="description_play_pause">Воспроизвести/Пауза</string>
<string name="description_clear_user_queue">Очистить очередь</string>

View file

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="entires_theme">
<string-array name="entires_theme">
<item>@string/setting_theme_auto</item>
<item>@string/setting_theme_day</item>
<item>@string/setting_theme_night</item>
</array>
</string-array>
<array name="values_theme">
<string-array name="values_theme" translatable="false">
<item>AUTO</item>
<item>LIGHT</item>
<item>DARK</item>
</array>
</string-array>
<array name="entries_lib_display">
<string-array name="entries_lib_display">
<item>@string/label_genres</item>
<item>@string/label_artists</item>
<item>@string/label_albums</item>
</array>
</string-array>
<array name="values_lib_display">
<string-array name="values_lib_display" translatable="false">
<item>SHOW_GENRES</item>
<item>SHOW_ARTISTS</item>
<item>SHOW_ALBUMS</item>
</array>
</string-array>
<array name="entries_song_playback_mode">
<item>@string/label_play_all_songs</item>
@ -31,22 +31,22 @@
<item>@string/label_play_genre</item>
</array>
<array name="values_song_playback_mode">
<string-array name="values_song_playback_mode" translatable="false">
<item>ALL_SONGS</item>
<item>IN_ARTIST</item>
<item>IN_ALBUM</item>
<item>IN_GENRE</item>
</array>
</string-array>
<array name="entries_at_end">
<string-array name="entries_at_end">
<item>@string/setting_behavior_end_loop_pause</item>
<item>@string/setting_behavior_end_loop</item>
<item>@string/setting_behavior_end_stop</item>
</array>
</string-array>
<array name="values_at_end">
<string-array name="values_at_end" translatable="false">
<item>LOOP_PAUSE</item>
<item>LOOP</item>
<item>STOP</item>
</array>
</string-array>
</resources>

View file

@ -104,8 +104,7 @@
<string name="description_sort_alpha_up">Sort from Z to A</string>
<string name="description_track_number">Track %d</string>
<string name="description_play">Play</string>
<string name="description_pause">Pause</string>
<string name="description_play_pause">Play/Pause</string>
<string name="description_skip_next">Skip to next song</string>
<string name="description_skip_prev">Skip to last song</string>
<string name="description_shuffle_on">Turn shuffle on</string>

View file

@ -4,6 +4,8 @@ This document is designed to provide a simple overview of Auxio's architecture a
#### Package structure overview
Auxio's package structure is mostly based around the features, and then any sub-features or components involved with that. There are some shared packages however. A diagram of the package structure is shown below:
```
org.oxycblt.auxio # Main UI's and logging utilities
├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
@ -41,7 +43,7 @@ Ideally, UIs should only be talking to ViewModels, ViewModels should only be tal
Auxio only has one activity, that being `MainActivity`. When adding a new UI, it should be added as a `Fragment` or a `RecyclerView` item depending on the situation.
Databinding should *always* be used instead of `findViewById`. `by memberBinding` is used if the binding needs to be a member variable in order to avoid memory leaks.
Databinding should *always* be used instead of `findViewById`. Use `by memberBinding` if the binding needs to be a member variable in order to avoid memory leaks.
Usually, fragment creation is done in `onCreateView`, and organized into three parts:
@ -49,7 +51,7 @@ Usually, fragment creation is done in `onCreateView`, and organized into three p
- Set up the UI
- Set up LiveData observers
When creating a ViewHolder for a `RecyclerView`, one should use `BaseHolder` to standardize the binding process and automate some code shared across all ViewHolders.
When creating a ViewHolder for a `RecyclerView`, one should use `BaseViewHolder` to standardize the binding process and automate some code shared across all ViewHolders.
#### Binding Adapters
@ -69,9 +71,7 @@ Auxio's playback system is somewhat unorthodox, as it avoids a lot of the built-
PlaybackStateManager───────────────────┘
```
`PlaybackStateManager` is the shared object that contains the master copy of the playback state, doing all operations on it. If you want to add something to the playback system, this is likely where you should add it.
This object should ***NEVER*** be used in a UI, as it does not sanitize input and can cause major problems if a Volatile UI interacts with it. It's callback system is also prone to memory leaks if not cleared when done. `PlaybackViewModel` can be used instead, as it exposes stable data and abstracted functions that UI's can use to interact with the playback state.
`PlaybackStateManager` is the shared object that contains the master copy of the playback state, doing all operations on it. This object should ***NEVER*** be used in a UI, as it does not sanitize input and can cause major problems if a Volatile UI interacts with it. It's callback system is also prone to memory leaks if not cleared when done. `PlaybackViewModel` can be used instead, as it exposes stable data and abstracted functions that UI's can use to interact with the playback state.
`PlaybackService`'s job is to use the playback state to manage the ExoPlayer instance and also modify the state depending on system external events, such as when a button is pressed on a headset. It should **never** be bound to, mostly because there is no need given that `PlaybackViewModel` exposes the same data in a much safer fashion.
@ -86,6 +86,10 @@ All music objects inherit `BaseModel`, which guarantees that all music has both
`BaseModel` can be used as an argument type to specify that any music type, while `Parent` can be used as an argument type to only specify music objects that have child items, such as albums or artists.
### Using menus
Instead of directly instantiating a menu for an item yourself, you should instead use `ActionMenu` as it will automate the menu creation and click listeners immediately.
#### Using Settings
Access to settings should preferably be done with `SettingsManager` as it can be accessed everywhere without a context.