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 c4bdf04d3..1c76e0094 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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 --- 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 366b634a5..4003746e2 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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 --- 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 4651fab49..2e0bee862 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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 --- 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 3aa921695..f051ec044 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -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) } 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 24d2f8872..99eb52765 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -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 -- 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 25d76276f..6b2505ba0 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -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 --- diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt index 35aa17139..c5d81ac05 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -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() } /** diff --git a/app/src/main/res/layout-land/fragment_compact_playback.xml b/app/src/main/res/layout-land/fragment_compact_playback.xml index 573ce6b57..21219a3d1 100644 --- a/app/src/main/res/layout-land/fragment_compact_playback.xml +++ b/app/src/main/res/layout-land/fragment_compact_playback.xml @@ -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" diff --git a/app/src/main/res/layout-land/fragment_playback.xml b/app/src/main/res/layout-land/fragment_playback.xml index fd01c68a6..6d8040780 100644 --- a/app/src/main/res/layout-land/fragment_playback.xml +++ b/app/src/main/res/layout-land/fragment_playback.xml @@ -190,7 +190,7 @@ von Z bis A sortieren Titel %d - Abspielen - Pausieren + Abspielen/Pausieren Zu letzter Lied springen Zu nächster Lied springen Zufällig anschalten diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index a44f208f4..48efb1bdc 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -59,8 +59,7 @@ Κομμάτι %d - Αναπαραγωγή - Παύση + Αναπαραγωγή/Παύση Εκκαθάριση ουράς αναπαραγωγής diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 779834101..30faaf01f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -68,8 +68,7 @@ Pista %d - Pausar - Reproducir + Reproducir/Pausar Vaciar cola de reproducción diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 58c6e952e..b4cff63b0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -68,8 +68,7 @@ Morceau %d - Lecture - Pause + Lecture/Pause Effacer la file d\'attente diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 1555df67c..98cea6b26 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -57,8 +57,7 @@ कोई संगीत नहीं मिला - चलाएं - रोकें + चलाएं/रोकें त्रुटी diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index fa6726b6b..2687cb963 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -68,8 +68,7 @@ Traccia %d - Play - Pausa + Play/Pausa Svuota coda diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 338b15fdf..ebcb212b7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -68,8 +68,7 @@ Nummer %d - Afspelen - Pauzeren + Afspelen/Pauzeren Leeg wachtrij diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 11bad46bc..48230da91 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -67,8 +67,7 @@ Música %d - Reproduzir - Pausar + Reproduzir/Pausar Limpar fila diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index a095b528a..5b45255c9 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -67,8 +67,7 @@ Música %d - Reproduzir - Pausar + Reproduzir/Pausar Limpar fila diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 5720c767a..e495b93d0 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -72,8 +72,7 @@ Melodie %d - Redă - Pauză + Redă/Pauză Golește lista de redare diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 75955be6c..6904e5700 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -66,8 +66,7 @@ Трек %d - Воспроизвести - Пауза + Воспроизвести/Пауза Очистить очередь diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index c0351a9af..68bcdbd56 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,28 +1,28 @@ - + @string/setting_theme_auto @string/setting_theme_day @string/setting_theme_night - + - + AUTO LIGHT DARK - + - + @string/label_genres @string/label_artists @string/label_albums - + - + SHOW_GENRES SHOW_ARTISTS SHOW_ALBUMS - + @string/label_play_all_songs @@ -31,22 +31,22 @@ @string/label_play_genre - + ALL_SONGS IN_ARTIST IN_ALBUM IN_GENRE - + - + @string/setting_behavior_end_loop_pause @string/setting_behavior_end_loop @string/setting_behavior_end_stop - + - + LOOP_PAUSE LOOP STOP - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 081cb210c..c8252dbbd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,8 +104,7 @@ Sort from Z to A Track %d - Play - Pause + Play/Pause Skip to next song Skip to last song Turn shuffle on diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 54451c8c7..0c4610b79 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -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.