Refactor genre UI
Fully refactor the genre UI so that it makes more sense.
This commit is contained in:
parent
6e5bff3bd3
commit
51a5e9fd63
20 changed files with 116 additions and 101 deletions
|
@ -84,7 +84,8 @@ class ArtistDetailFragment : DetailFragment() {
|
|||
|
||||
true
|
||||
}
|
||||
R.id.action_play -> {
|
||||
|
||||
R.id.action_play_albums -> {
|
||||
playbackModel.playArtist(
|
||||
detailModel.currentArtist.value!!, false
|
||||
)
|
||||
|
|
|
@ -16,7 +16,7 @@ import org.oxycblt.auxio.music.MusicStore
|
|||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.ui.disable
|
||||
import org.oxycblt.auxio.ui.setupSongActions
|
||||
import org.oxycblt.auxio.ui.setupGenreSongActions
|
||||
|
||||
/**
|
||||
* The [DetailFragment] for a genre.
|
||||
|
@ -51,7 +51,7 @@ class GenreDetailFragment : DetailFragment() {
|
|||
playbackModel.playSong(it, PlaybackMode.IN_GENRE)
|
||||
},
|
||||
doOnLongClick = { data, view ->
|
||||
PopupMenu(requireContext(), view).setupSongActions(
|
||||
PopupMenu(requireContext(), view).setupGenreSongActions(
|
||||
requireContext(), data, playbackModel
|
||||
)
|
||||
}
|
||||
|
@ -78,13 +78,6 @@ class GenreDetailFragment : DetailFragment() {
|
|||
|
||||
true
|
||||
}
|
||||
R.id.action_play -> {
|
||||
playbackModel.playGenre(
|
||||
detailModel.currentGenre.value!!, false
|
||||
)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
|
|
@ -161,12 +161,12 @@ data class Genre(
|
|||
private val mSongs = mutableListOf<Song>()
|
||||
val songs: List<Song> get() = mSongs
|
||||
|
||||
val albumCount: Int by lazy {
|
||||
songs.groupBy { it.album }.size
|
||||
val totalDuration: String by lazy {
|
||||
var seconds: Long = 0
|
||||
songs.forEach {
|
||||
seconds += it.seconds
|
||||
}
|
||||
|
||||
val artistCount: Int by lazy {
|
||||
songs.groupBy { it.album.artist }.size
|
||||
seconds.toDuration()
|
||||
}
|
||||
|
||||
fun addSong(song: Song) {
|
||||
|
|
|
@ -105,37 +105,6 @@ fun Int.toYear(context: Context): String {
|
|||
|
||||
// --- BINDING ADAPTERS ---
|
||||
|
||||
/**
|
||||
* Bind the artist + album counts for a genre
|
||||
*/
|
||||
@BindingAdapter("genreCounts")
|
||||
fun TextView.bindGenreCounts(genre: Genre) {
|
||||
val artists = context.resources.getQuantityString(
|
||||
R.plurals.format_artist_count, genre.artistCount, genre.artistCount
|
||||
)
|
||||
val albums = context.resources.getQuantityString(
|
||||
R.plurals.format_album_count, genre.albumCount, genre.albumCount
|
||||
)
|
||||
|
||||
text = context.getString(R.string.format_double_counts, artists, albums)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the album + song counts for a genre
|
||||
*/
|
||||
@BindingAdapter("altGenreCounts")
|
||||
fun TextView.bindAltGenreCounts(genre: Genre) {
|
||||
val albums = context.resources.getQuantityString(
|
||||
R.plurals.format_album_count, genre.albumCount, genre.albumCount
|
||||
)
|
||||
|
||||
val songs = context.resources.getQuantityString(
|
||||
R.plurals.format_song_count, genre.songs.size, genre.songs.size
|
||||
)
|
||||
|
||||
text = context.getString(R.string.format_double_counts, albums, songs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the most prominent artist genre
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.oxycblt.auxio.music.processing
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Albums
|
||||
import android.provider.MediaStore.Audio.Genres
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
|
@ -16,7 +17,8 @@ import org.oxycblt.auxio.music.toAlbumArtURI
|
|||
import org.oxycblt.auxio.music.toNamedGenre
|
||||
|
||||
/**
|
||||
* Object that loads music from the filesystem.
|
||||
* Class that loads/constructs [Genre]s, [Album]s, and [Song] objects from the filesystem
|
||||
* Artists are constructed in [MusicSorter], as they are only really containers for [Album]s
|
||||
*/
|
||||
class MusicLoader(private val app: Application) {
|
||||
var genres = mutableListOf<Genre>()
|
||||
|
@ -117,10 +119,14 @@ class MusicLoader(private val app: Application) {
|
|||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val name = cursor.getString(nameIndex) ?: albumPlaceholder
|
||||
val artistName = cursor.getString(artistIdIndex) ?: artistPlaceholder
|
||||
var artistName = cursor.getString(artistIdIndex) ?: artistPlaceholder
|
||||
val year = cursor.getInt(yearIndex)
|
||||
val coverUri = id.toAlbumArtURI()
|
||||
|
||||
if (artistName == MediaStore.UNKNOWN_STRING) {
|
||||
artistName = artistPlaceholder
|
||||
}
|
||||
|
||||
albums.add(
|
||||
Album(
|
||||
id = id, name = name, artistName = artistName,
|
||||
|
@ -192,9 +198,7 @@ class MusicLoader(private val app: Application) {
|
|||
for (genre in genres) {
|
||||
val songGenreCursor = resolver.query(
|
||||
Genres.Members.getContentUri("external", genre.id),
|
||||
arrayOf(
|
||||
Genres.Members._ID
|
||||
),
|
||||
arrayOf(Genres.Members._ID),
|
||||
null, null, null
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package org.oxycblt.auxio.music.processing
|
||||
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* Object responsible for creating [Artist]s from [Album]s and generally sorting everything.
|
||||
*/
|
||||
class MusicSorter(
|
||||
val songs: MutableList<Song>,
|
||||
val albums: MutableList<Album>
|
||||
|
@ -27,7 +29,6 @@ class MusicSorter(
|
|||
val groupedAlbums = albums.groupBy { it.artistName }
|
||||
|
||||
groupedAlbums.forEach {
|
||||
logD(it.key)
|
||||
artists.add(
|
||||
Artist(id = artists.size.toLong(), name = it.key, albums = it.value)
|
||||
)
|
||||
|
|
|
@ -507,15 +507,13 @@ class PlaybackStateManager private constructor() {
|
|||
* @param useLastSong (Optional, defaults to false) Whether to use the previous song for the index calculations caused by the above parameter.
|
||||
*/
|
||||
private fun genShuffle(keepSong: Boolean, useLastSong: Boolean = false) {
|
||||
val newSeed = Random.Default.nextLong()
|
||||
mShuffleSeed = Random.Default.nextLong()
|
||||
|
||||
logD("Shuffling queue with seed $newSeed")
|
||||
logD("Shuffling queue with seed $mShuffleSeed")
|
||||
|
||||
val lastSong = if (useLastSong) mQueue[mIndex] else mSong
|
||||
|
||||
mShuffleSeed = newSeed
|
||||
|
||||
mQueue.shuffle(Random(newSeed))
|
||||
mQueue.shuffle(Random(mShuffleSeed))
|
||||
mIndex = 0
|
||||
|
||||
// If specified, make the current song the first member of the queue.
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.oxycblt.auxio.ui.ACCENTS
|
|||
|
||||
/**
|
||||
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
||||
* TODO: Add option to play song from genre, now that its possible
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class SettingsManager private constructor(context: Context) :
|
||||
|
|
|
@ -197,7 +197,7 @@ fun PopupMenu.setupAlbumActions(
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
inflateAndShow(R.menu.menu_album_actions)
|
||||
inflateAndShow(R.menu.menu_album_detail)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,7 +221,7 @@ fun PopupMenu.setupArtistActions(artist: Artist, playbackModel: PlaybackViewMode
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
inflateAndShow(R.menu.menu_detail)
|
||||
inflateAndShow(R.menu.menu_artist_detail)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,11 +232,6 @@ fun PopupMenu.setupArtistActions(artist: Artist, playbackModel: PlaybackViewMode
|
|||
fun PopupMenu.setupGenreActions(genre: Genre, playbackModel: PlaybackViewModel) {
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_play -> {
|
||||
playbackModel.playGenre(genre, false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_shuffle -> {
|
||||
playbackModel.playGenre(genre, true)
|
||||
true
|
||||
|
@ -245,7 +240,43 @@ fun PopupMenu.setupGenreActions(genre: Genre, playbackModel: PlaybackViewModel)
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
inflateAndShow(R.menu.menu_detail)
|
||||
inflateAndShow(R.menu.menu_artist_detail)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show actions for a song in a genre.
|
||||
* @param context [Context] required
|
||||
* @param song [Song] the menu should correspond to
|
||||
* @param playbackModel [PlaybackViewModel] to dispatch actions to
|
||||
*/
|
||||
fun PopupMenu.setupGenreSongActions(context: Context, song: Song, 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_play_artist -> {
|
||||
playbackModel.playSong(song, PlaybackMode.IN_ARTIST)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_play_album -> {
|
||||
playbackModel.playSong(song, PlaybackMode.IN_ALBUM)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_play_all_songs -> {
|
||||
playbackModel.playSong(song, PlaybackMode.ALL_SONGS)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
inflateAndShow(R.menu.menu_genre_song_actions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
style="@style/Toolbar.Style.Icon"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_album_actions"
|
||||
app:menu="@menu/menu_album_detail"
|
||||
app:title="@string/label_library" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
style="@style/Toolbar.Style.Icon"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_detail"
|
||||
app:menu="@menu/menu_artist_detail"
|
||||
app:title="@string/label_library" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
style="@style/Toolbar.Style.Icon"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_detail"
|
||||
app:menu="@menu/menu_songs"
|
||||
app:title="@string/label_library" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
|
@ -61,7 +61,7 @@
|
|||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:layout_marginEnd="@dimen/margin_medium"
|
||||
android:text="@{genre.name}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/genre_counts"
|
||||
app:layout_constraintBottom_toTopOf="@+id/genre_song_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/genre_image"
|
||||
|
@ -70,30 +70,30 @@
|
|||
tools:text="Genre Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_counts"
|
||||
android:id="@+id/genre_song_count"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:genreCounts="@{genre}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/genre_song_count"
|
||||
android:text="@{@plurals/format_song_count(genre.songs.size(), genre.songs.size())}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/genre_duration"
|
||||
app:layout_constraintStart_toEndOf="@+id/genre_image"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_name"
|
||||
tools:text="2 Artists, 4 Albums" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_song_count"
|
||||
android:id="@+id/genre_duration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:text="@{@plurals/format_song_count(genre.songs.size, genre.songs.size)}"
|
||||
android:text="@{genre.totalDuration}"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/genre_song_header"
|
||||
app:layout_constraintStart_toEndOf="@+id/genre_image"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_counts"
|
||||
tools:text="80 Songs" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_song_count"
|
||||
tools:text="16:16" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_song_header"
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
style="@style/Toolbar.Style.Icon"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_album_actions"
|
||||
app:menu="@menu/menu_album_detail"
|
||||
app:title="@string/label_library" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
style="@style/Toolbar.Style.Icon"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_detail"
|
||||
app:menu="@menu/menu_artist_detail"
|
||||
app:title="@string/label_library" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
style="@style/Toolbar.Style.Icon"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
app:menu="@menu/menu_detail"
|
||||
app:menu="@menu/menu_songs"
|
||||
app:title="@string/label_library" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
|
@ -70,29 +70,29 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/genre_image"
|
||||
tools:text="Genre Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_counts"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:genreCounts="@{genre}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_name"
|
||||
tools:text="2 Artists, 4 Albums" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_song_count"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:text="@{@plurals/format_song_count(genre.songs.size, genre.songs.size)}"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@{@plurals/format_song_count(genre.songs.size(), genre.songs.size())}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_name"
|
||||
tools:text="80 Songs" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_duration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:text="@{genre.totalDuration}"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_counts"
|
||||
tools:text="80 Songs" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_song_count"
|
||||
tools:text="16:16" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_song_header"
|
||||
|
@ -100,7 +100,7 @@
|
|||
android:layout_marginTop="@dimen/padding_medium"
|
||||
android:text="@string/label_songs"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_song_count" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_duration" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/genre_sort_button"
|
||||
|
|
|
@ -38,12 +38,12 @@
|
|||
<TextView
|
||||
android:id="@+id/genre_count"
|
||||
style="@style/ItemText.Secondary"
|
||||
app:altGenreCounts="@{genre}"
|
||||
android:text="@{@plurals/format_song_count(genre.songs.size(), genre.songs.size())}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/genre_image"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_name"
|
||||
tools:text="4 Albums, 40 Songs" />
|
||||
tools:text="40 Songs" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
|
@ -7,8 +7,8 @@
|
|||
android:title="@string/label_shuffle"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_play"
|
||||
android:id="@+id/action_play_albums"
|
||||
android:icon="@drawable/ic_play"
|
||||
android:title="@string/label_play"
|
||||
android:title="@string/label_play_albums"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
15
app/src/main/res/menu/menu_genre_song_actions.xml
Normal file
15
app/src/main/res/menu/menu_genre_song_actions.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_queue_add"
|
||||
android:icon="@drawable/ic_queue_add"
|
||||
android:title="@string/label_queue_add" />
|
||||
<item
|
||||
android:id="@+id/action_play_artist"
|
||||
android:icon="@drawable/ic_artist"
|
||||
android:title="@string/label_play_artist" />
|
||||
<item
|
||||
android:id="@+id/action_play_album"
|
||||
android:icon="@drawable/ic_album"
|
||||
android:title="@string/label_play_album" />
|
||||
</menu>
|
|
@ -28,6 +28,8 @@
|
|||
<string name="label_play_album">Play from album</string>
|
||||
<string name="label_play_artist">Play from artist</string>
|
||||
<string name="label_go_artist">Go to artist</string>
|
||||
<string name="label_play_albums">Play albums</string>
|
||||
<string name="label_shuffle_albums">Shuffle albums</string>
|
||||
|
||||
<string name="label_queue">Queue</string>
|
||||
<string name="label_queue_add">Add to queue</string>
|
||||
|
|
Loading…
Reference in a new issue