From 7495a59ab18a60e82d6834f385dafe6200660e80 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Thu, 6 Jul 2023 20:04:17 -0600 Subject: [PATCH 01/28] playback: add scaffold for new playback mode Add the scaffold for PlaySong, a new version of playback modes that - Supports playback of a song by itself, requested by #424. - Will make direct playback from the song menu feasible (given additional reworks) - Prevents the invalid state of playing a song by it's playlist, as the sealed interface implementation of PlaySong requires a Playlist to be provided to it's respective variant. --- .../java/org/oxycblt/auxio/IntegerTable.kt | 14 +- .../auxio/detail/AlbumDetailFragment.kt | 8 +- .../auxio/detail/ArtistDetailFragment.kt | 2 +- .../auxio/detail/GenreDetailFragment.kt | 2 +- .../auxio/home/list/SongListFragment.kt | 2 +- .../auxio/playback/PlaybackViewModel.kt | 232 ++++++++++-------- .../oxycblt/auxio/search/SearchFragment.kt | 2 +- 7 files changed, 157 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index d0bff5315..98f1b5739 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -101,8 +101,6 @@ object IntegerTable { const val SORT_BY_TRACK = 0xA117 /** Sort.Mode.ByDateAdded */ const val SORT_BY_DATE_ADDED = 0xA118 - /** Sort.Mode.None */ - const val SORT_BY_NONE = 0xA11F /** ReplayGainMode.Off (No longer used but still reserved) */ // const val REPLAY_GAIN_MODE_OFF = 0xA110 /** ReplayGainMode.Track */ @@ -123,4 +121,16 @@ object IntegerTable { const val COVER_MODE_MEDIA_STORE = 0xA11D /** CoverMode.Quality */ const val COVER_MODE_QUALITY = 0xA11E + /** PlaySong.Itself */ + const val PLAY_SONG_ITSELF = 0xA11F + /** PlaySong.FromAll */ + const val PLAY_SONG_FROM_ALL = 0xA120 + /** PlaySong.FromAlbum */ + const val PLAY_SONG_FROM_ALBUM = 0xA121 + /** PlaySong.FromArtist */ + const val PLAY_SONG_FROM_ARTIST = 0xA122 + /** PlaySong.FromGenre */ + const val PLAY_SONG_FROM_GENRE = 0xA123 + /** PlaySong.FromPlaylist */ + const val PLAY_SONG_FROM_PLAYLIST = 0xA124 } 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 b654902da..f2f3f0543 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -180,8 +180,12 @@ class AlbumDetailFragment : } override fun onRealClick(item: Song) { - // There can only be one album, so a null mode and an ALBUMS mode will function the same. - playbackModel.playFrom(item, detailModel.playbackMode ?: MusicMode.ALBUMS) + val mode = detailModel.playbackMode + if (mode != null) { + playbackModel.play(item, detailModel.playbackMode ?: MusicMode.ALBUMS) + } else { + playbackModel.playFromAlbum(item) + } } override fun onOpenMenu(item: Song, anchor: View) { 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 6eebd76dd..973a524ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -182,7 +182,7 @@ class ArtistDetailFragment : is Song -> { val playbackMode = detailModel.playbackMode if (playbackMode != null) { - playbackModel.playFrom(item, playbackMode) + playbackModel.play(item, playbackMode) } else { // When configured to play from the selected item, we already have an Artist // to play from. 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 8d26371e7..f71fcb48d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -180,7 +180,7 @@ class GenreDetailFragment : is Song -> { val playbackMode = detailModel.playbackMode if (playbackMode != null) { - playbackModel.playFrom(item, playbackMode) + playbackModel.play(item, playbackMode) } else { // When configured to play from the selected item, we already have an Genre // to play from. diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index aba66c5e0..9f2539c3f 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -137,7 +137,7 @@ class SongListFragment : } override fun onRealClick(item: Song) { - playbackModel.playFrom(item, homeModel.playbackMode) + playbackModel.play(item, homeModel.playbackMode) } override fun onOpenMenu(item: Song, anchor: View) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index f329e2a70..de2d08f2c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -180,32 +180,31 @@ constructor( // --- PLAYING FUNCTIONS --- + fun play(song: Song, playbackMode: MusicMode) { + play(song, PlaySong.fromPlaybackModeTemporary(playbackMode)) + } + + fun play(song: Song, with: PlaySong) { + logD("Playing $song with $with") + playWithImpl(song, with, isImplicitlyShuffled()) + } + + // fun playExplicit(song: Song, with: PlaySong) { + // playWithImpl(song, with, false) + // } + // + // fun shuffleExplicit(song: Song, with: PlaySong) { + // playWithImpl(song, with, true) + // } + /** Shuffle all songs in the music library. */ fun shuffleAll() { logD("Shuffling all songs") - playImpl(null, null, true) + playFromAllImpl(null, true) } - /** - * Play a [Song] from the [MusicParent] outlined by the given [MusicMode]. - * - If [MusicMode.SONGS], the [Song] is played from all songs. - * - If [MusicMode.ALBUMS], the [Song] is played from it's [Album]. - * - If [MusicMode.ARTISTS], the [Song] is played from one of it's [Artist]s. - * - If [MusicMode.GENRES], the [Song] is played from one of it's [Genre]s. - * [MusicMode.PLAYLISTS] is disallowed here. - * - * @param song The [Song] to play. - * @param playbackMode The [MusicMode] to play from. - */ - fun playFrom(song: Song, playbackMode: MusicMode) { - logD("Playing $song from $playbackMode") - when (playbackMode) { - MusicMode.SONGS -> playImpl(song, null) - MusicMode.ALBUMS -> playImpl(song, song.album) - MusicMode.ARTISTS -> playFromArtist(song) - MusicMode.GENRES -> playFromGenre(song) - MusicMode.PLAYLISTS -> error("Playing from a playlist is not supported.") - } + fun playFromAlbum(song: Song) { + playFromAlbumImpl(song, isImplicitlyShuffled()) } /** @@ -216,16 +215,7 @@ constructor( * be prompted on what artist to play. Defaults to null. */ fun playFromArtist(song: Song, artist: Artist? = null) { - if (artist != null) { - logD("Playing $song from $artist") - playImpl(song, artist) - } else if (song.artists.size == 1) { - logD("$song has one artist, playing from it") - playImpl(song, song.artists[0]) - } else { - logD("$song has multiple artists, showing choice dialog") - startPlaybackDecisionImpl(PlaybackDecision.PlayFromArtist(song)) - } + playFromArtistImpl(song, artist, isImplicitlyShuffled()) } /** @@ -236,19 +226,77 @@ constructor( * be prompted on what artist to play. Defaults to null. */ fun playFromGenre(song: Song, genre: Genre? = null) { - if (genre != null) { - logD("Playing $song from $genre") - playImpl(song, genre) - } else if (song.genres.size == 1) { - logD("$song has one genre, playing from it") - playImpl(song, song.genres[0]) - } else { - logD("$song has multiple genres, showing choice dialog") - startPlaybackDecisionImpl(PlaybackDecision.PlayFromGenre(song)) + playFromGenreImpl(song, genre, isImplicitlyShuffled()) + } + + /** + * Play a [Song] from one of it's [Playlist]s. + * + * @param song The [Song] to play. + * @param playlist The [Playlist] to play from. Must be linked to the [Song]. + */ + fun playFromPlaylist(song: Song, playlist: Playlist) { + playFromPlaylistImpl(song, playlist, isImplicitlyShuffled()) + } + + private fun isImplicitlyShuffled() = + playbackManager.queue.isShuffled && playbackSettings.keepShuffle + + private fun playWithImpl(song: Song, with: PlaySong, shuffled: Boolean) { + when (with) { + is PlaySong.ByItself -> playItselfImpl(song, shuffled) + is PlaySong.FromAll -> playFromAllImpl(song, shuffled) + is PlaySong.FromAlbum -> playFromAlbumImpl(song, shuffled) + is PlaySong.FromArtist -> playFromArtistImpl(song, with.which, shuffled) + is PlaySong.FromGenre -> playFromGenreImpl(song, with.which, shuffled) + is PlaySong.FromPlaylist -> playFromPlaylistImpl(song, with.which, shuffled) } } - private fun startPlaybackDecisionImpl(decision: PlaybackDecision) { + private fun playItselfImpl(song: Song, shuffled: Boolean) { + playImpl(song, listOf(song), shuffled) + } + + private fun playFromAllImpl(song: Song?, shuffled: Boolean) { + playImpl(song, null, shuffled) + } + + private fun playFromAlbumImpl(song: Song, shuffled: Boolean) { + playImpl(song, song.album, shuffled) + } + + private fun playFromArtistImpl(song: Song, artist: Artist?, shuffled: Boolean) { + if (artist != null) { + logD("Playing $song from $artist") + playImpl(song, artist, shuffled) + } else if (song.artists.size == 1) { + logD("$song has one artist, playing from it") + playImpl(song, song.artists[0], shuffled) + } else { + logD("$song has multiple artists, showing choice dialog") + startPlaybackDecision(PlaybackDecision.PlayFromArtist(song)) + } + } + + private fun playFromGenreImpl(song: Song, genre: Genre?, shuffled: Boolean) { + if (genre != null) { + logD("Playing $song from $genre") + playImpl(song, genre, shuffled) + } else if (song.genres.size == 1) { + logD("$song has one genre, playing from it") + playImpl(song, song.genres[0], shuffled) + } else { + logD("$song has multiple genres, showing choice dialog") + startPlaybackDecision(PlaybackDecision.PlayFromGenre(song)) + } + } + + private fun playFromPlaylistImpl(song: Song, playlist: Playlist, shuffled: Boolean) { + logD("Playing $song from $playlist") + playImpl(song, playlist, shuffled) + } + + private fun startPlaybackDecision(decision: PlaybackDecision) { val existing = _playbackDecision.flow.value if (existing != null) { logD("Already handling decision $existing, ignoring $decision") @@ -257,17 +305,6 @@ constructor( _playbackDecision.put(decision) } - /** - * PLay a [Song] from one of it's [Playlist]s. - * - * @param song The [Song] to play. - * @param playlist The [Playlist] to play from. Must be linked to the [Song]. - */ - fun playFromPlaylist(song: Song, playlist: Playlist) { - logD("Playing $song from $playlist") - playImpl(song, playlist) - } - /** * Play an [Album]. * @@ -278,46 +315,6 @@ constructor( playImpl(null, album, false) } - /** - * Play an [Artist]. - * - * @param artist The [Artist] to play. - */ - fun play(artist: Artist) { - logD("Playing $artist") - playImpl(null, artist, false) - } - - /** - * Play a [Genre]. - * - * @param genre The [Genre] to play. - */ - fun play(genre: Genre) { - logD("Playing $genre") - playImpl(null, genre, false) - } - - /** - * Play a [Playlist]. - * - * @param playlist The [Playlist] to play. - */ - fun play(playlist: Playlist) { - logD("Playing $playlist") - playImpl(null, playlist, false) - } - - /** - * Play a list of [Song]s. - * - * @param songs The [Song]s to play. - */ - fun play(songs: List) { - logD("Playing ${songs.size} songs") - playbackManager.play(null, null, songs, false) - } - /** * Shuffle an [Album]. * @@ -328,6 +325,16 @@ constructor( playImpl(null, album, true) } + /** + * Play an [Artist]. + * + * @param artist The [Artist] to play. + */ + fun play(artist: Artist) { + logD("Playing $artist") + playImpl(null, artist, false) + } + /** * Shuffle an [Artist]. * @@ -338,6 +345,16 @@ constructor( playImpl(null, artist, true) } + /** + * Play a [Genre]. + * + * @param genre The [Genre] to play. + */ + fun play(genre: Genre) { + logD("Playing $genre") + playImpl(null, genre, false) + } + /** * Shuffle a [Genre]. * @@ -348,6 +365,16 @@ constructor( playImpl(null, genre, true) } + /** + * Play a [Playlist]. + * + * @param playlist The [Playlist] to play. + */ + fun play(playlist: Playlist) { + logD("Playing $playlist") + playImpl(null, playlist, false) + } + /** * Shuffle a [Playlist]. * @@ -358,6 +385,16 @@ constructor( playImpl(null, playlist, true) } + /** + * Play a list of [Song]s. + * + * @param songs The [Song]s to play. + */ + fun play(songs: List) { + logD("Playing ${songs.size} songs") + playbackManager.play(null, null, songs, false) + } + /** * Shuffle a list of [Song]s. * @@ -368,11 +405,12 @@ constructor( playbackManager.play(null, null, songs, true) } - private fun playImpl( - song: Song?, - parent: MusicParent?, - shuffled: Boolean = playbackManager.queue.isShuffled && playbackSettings.keepShuffle - ) { + private fun playImpl(song: Song?, queue: List, shuffled: Boolean) { + check(song == null || queue.contains(song)) { "Song to play not in queue" } + playbackManager.play(song, null, queue, shuffled) + } + + private fun playImpl(song: Song?, parent: MusicParent?, shuffled: Boolean) { check(song == null || parent == null || parent.songs.contains(song)) { "Song to play not in parent" } 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 9417f8a03..9156488cc 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -174,7 +174,7 @@ class SearchFragment : ListFragment() { override fun onRealClick(item: Music) { when (item) { - is Song -> playbackModel.playFrom(item, searchModel.playbackMode) + is Song -> playbackModel.play(item, searchModel.playbackMode) is Album -> detailModel.showAlbum(item) is Artist -> detailModel.showArtist(item) is Genre -> detailModel.showGenre(item) From 598b3f41733cab5529c5a510a2f2eb52717370de Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 10 Jul 2023 15:21:47 -0600 Subject: [PATCH 02/28] playback: include playsong datatype This was apparently not included in the prior commit, whoops. --- .../org/oxycblt/auxio/playback/PlaySong.kt | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt new file mode 100644 index 000000000..2e14aa928 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 Auxio Project + * PlaySong.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.playback + +import org.oxycblt.auxio.IntegerTable +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.Playlist + +sealed interface PlaySong { + val intCode: Int + + object ByItself : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_ITSELF + } + + object FromAll : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_ITSELF + } + + object FromAlbum : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_FROM_ALBUM + } + + data class FromArtist(val which: Artist?) : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_FROM_ARTIST + } + + data class FromGenre(val which: Genre?) : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_FROM_GENRE + } + + data class FromPlaylist(val which: Playlist) : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_FROM_PLAYLIST + } + + companion object { + fun fromPlaybackModeTemporary(playbackMode: MusicMode) = + when (playbackMode) { + MusicMode.SONGS -> FromAll + MusicMode.ALBUMS -> FromAlbum + MusicMode.ARTISTS -> FromArtist(null) + MusicMode.GENRES -> FromGenre(null) + MusicMode.PLAYLISTS -> throw IllegalStateException() + } + + fun fromIntCode(intCode: Int, inner: Music?): PlaySong? = + when (intCode) { + IntegerTable.PLAY_SONG_ITSELF -> ByItself + IntegerTable.PLAY_SONG_FROM_ALBUM -> FromAlbum + IntegerTable.PLAY_SONG_FROM_ARTIST -> + if (inner is Artist?) { + FromArtist(inner) + } else { + null + } + IntegerTable.PLAY_SONG_FROM_GENRE -> + if (inner is Genre?) { + FromGenre(inner) + } else { + null + } + IntegerTable.PLAY_SONG_FROM_PLAYLIST -> + if (inner is Playlist) { + FromPlaylist(inner) + } else { + null + } + else -> null + } + } +} From 97816e349a656e04958cd70c2749e5f15e36acbe Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 11 Jul 2023 10:27:48 -0600 Subject: [PATCH 03/28] build: update deps Media3 -> 1.1.0. Update project info accordingly to use Media3 ExoPlayer instead of standalone ExoPlayer. Nav -> 2.6.0 once again, probably without the absurd bugs now Kotlin -> 1.9.0 Kotlin Coroutines -> 1.7.2 LeakCanary -> 2.12 --- .github/CONTRIBUTING.md | 6 +++--- README.md | 9 ++++----- app/build.gradle | 4 ++-- .../java/org/oxycblt/auxio/list/menu/MenuViewModel.kt | 1 + build.gradle | 4 ++-- fastlane/metadata/android/en-US/full_description.txt | 4 ++-- media | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 76abe25e7..de42bfe09 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -39,8 +39,8 @@ If you have knowledge of Android/Kotlin, feel free to to contribute to the proje - If you want to help out with an existing bug report, comment on the issue that you want to fix saying that you are going to try your hand at it. - If you want to add something, its recommended to open up an issue for what you want to change before you start working on it. That way I can determine if the addition will be merged in the first place, and generally gives a heads-up overall. - Do not bring non-free software into the project, such as Binary Blobs. -- Stick to [F-Droid Including Guidelines](https://f-droid.org/wiki/page/Inclusion_Policy) -- Make sure you stick to Auxio's styling with [ktlint](https://github.com/pinterest/ktlint). `ktlintformat` should run on every build. +- Stick to [F-Droid Inclusion Guidelines](https://f-droid.org/wiki/page/Inclusion_Policy) +- Make sure you stick to Auxio's styling, which should be auto-formatted on every build. - Please ***FULLY TEST*** your changes before creating a PR. Untested code will not be merged. -- Java code will **NOT** be accepted. Kotlin only. +- Only **Kotlin** will be accepted, except for the case that a UI component must be vendored in the project. - Keep your code up the date with the upstream and continue to maintain it after you create the PR. This makes it less of a hassle to merge. diff --git a/README.md b/README.md index 012eae415..a324ffd3c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ## About -Auxio is a local music player with a fast, reliable UI/UX without the many useless features present in other music players. Built off of [ExoPlayer](https://exoplayer.dev/), Auxio has superior library support and listening quality compared to other apps that use outdated android functionality. In short, **It plays music.** +Auxio is a local music player with a fast, reliable UI/UX without the many useless features present in other music players. Built off of modern media playback libraries, Auxio has superior library support and listening quality compared to other apps that use outdated android functionality. In short, **It plays music.** I primarily built Auxio for myself, but you can use it too, I guess. @@ -42,7 +42,7 @@ I primarily built Auxio for myself, but you can use it too, I guess. ## Features -- [ExoPlayer](https://exoplayer.dev/)-based playback +- Playback based on [Media3 ExoPlayer](https://developer.android.com/guide/topics/media/exoplayer) - Snappy UI derived from the latest Material Design guidelines - Opinionated UX that prioritizes ease of use over edge cases - Customizable behavior @@ -69,12 +69,11 @@ precise/original dates, sort tags, and more ## Building -Auxio relies on a custom version of ExoPlayer that enables some extra features. This adds some caveats to -the build process: +Auxio relies on a custom version of Media3 that enables some extra features. This adds some caveats to the build process: 1. `cmake` and `ninja-build` must be installed before building the project. 2. The project uses submodules, so when cloning initially, use `git clone --recurse-submodules` to properly download the external code. -3. You are **unable** to build this project on windows, as the custom ExoPlayer build runs shell scripts that +3. You are **unable** to build this project on windows, as the custom Media3 build runs shell scripts that will only work on unix-based systems. ## Contributing diff --git a/app/build.gradle b/app/build.gradle index 0ec8807b2..e85547c49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - def coroutines_version = '1.7.1' + def coroutines_version = '1.7.2' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines_version" @@ -142,7 +142,7 @@ dependencies { kapt "com.google.dagger:hilt-android-compiler:$hilt_version" // Testing - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12' testImplementation "junit:junit:4.13.2" androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt index 44af342bd..3e34c1a52 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt @@ -29,6 +29,7 @@ import org.oxycblt.auxio.util.logW /** * Manages the state information for [MenuDialogFragment] implementations. + * * @author Alexander Capehart (OxygenCobalt) */ @HiltViewModel diff --git a/build.gradle b/build.gradle index efe210373..bcc63d310 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { - kotlin_version = '1.8.22' - navigation_version = "2.5.0" + kotlin_version = '1.9.0' + navigation_version = "2.6.0" hilt_version = '2.46.1' } diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index ec9c5977d..b43251ee6 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,8 +1,8 @@ -Auxio is a local music player with a fast, reliable UI/UX without the many useless features present in other music players. Built off of ExoPlayer, Auxio has superior library support and listening quality compared to other apps that use outdated android functionality. In short, It plays music. +Auxio is a local music player with a fast, reliable UI/UX without the many useless features present in other music players. Built off of modern media playback libraries, Auxio has superior library support and listening quality compared to other apps that use outdated android functionality. In short, It plays music. Features -- ExoPlayer-based playback +- Playback based on Media3 ExoPlayer - Snappy UI derived from the latest Material Design guidelines - Opinionated UX that prioritizes ease of use over edge cases - Customizable behavior diff --git a/media b/media index 8712967a7..316763308 160000 --- a/media +++ b/media @@ -1 +1 @@ -Subproject commit 8712967a789192d60d2207451cd5ed2b3191999e +Subproject commit 316763308d3143c75270103c85cf2d984bfa34a0 From 3908400418a40d8ebd4ac7432615a4fd96180e73 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 11 Jul 2023 11:44:11 -0600 Subject: [PATCH 04/28] playback: use playsong settings Use PlaySong modeling within settings too. This largely completes the PlaySong refactor and fully adds the ability to play songs by themselves as an option. Resolves #424. --- CHANGELOG.md | 4 + .../java/org/oxycblt/auxio/IntegerTable.kt | 20 ++-- .../auxio/detail/AlbumDetailFragment.kt | 8 +- .../auxio/detail/ArtistDetailFragment.kt | 12 +-- .../oxycblt/auxio/detail/DetailViewModel.kt | 26 +++-- .../auxio/detail/GenreDetailFragment.kt | 12 +-- .../auxio/detail/PlaylistDetailFragment.kt | 2 +- .../org/oxycblt/auxio/home/HomeViewModel.kt | 7 +- .../auxio/home/list/SongListFragment.kt | 2 +- .../org/oxycblt/auxio/playback/PlaySong.kt | 22 ++--- .../auxio/playback/PlaybackSettings.kt | 94 +++++++------------ .../auxio/playback/PlaybackViewModel.kt | 29 +----- .../oxycblt/auxio/search/SearchFragment.kt | 2 +- .../oxycblt/auxio/search/SearchViewModel.kt | 7 +- app/src/main/res/values-ar-rIQ/strings.xml | 8 +- app/src/main/res/values-be/strings.xml | 14 +-- app/src/main/res/values-cs/strings.xml | 14 +-- app/src/main/res/values-de/strings.xml | 14 +-- app/src/main/res/values-es/strings.xml | 14 +-- app/src/main/res/values-fi/strings.xml | 12 +-- app/src/main/res/values-fr/strings.xml | 14 +-- app/src/main/res/values-gl/strings.xml | 14 +-- app/src/main/res/values-hi/strings.xml | 14 +-- app/src/main/res/values-hr/strings.xml | 14 +-- app/src/main/res/values-hu/strings.xml | 14 +-- app/src/main/res/values-in/strings.xml | 14 +-- app/src/main/res/values-it/strings.xml | 14 +-- app/src/main/res/values-iw/strings.xml | 14 +-- app/src/main/res/values-ja/strings.xml | 14 +-- app/src/main/res/values-ko/strings.xml | 14 +-- app/src/main/res/values-lt/strings.xml | 14 +-- app/src/main/res/values-nl/strings.xml | 14 +-- app/src/main/res/values-pa/strings.xml | 14 +-- app/src/main/res/values-pl/strings.xml | 14 +-- app/src/main/res/values-pt-rBR/strings.xml | 14 +-- app/src/main/res/values-pt-rPT/strings.xml | 14 +-- app/src/main/res/values-ro/strings.xml | 14 +-- app/src/main/res/values-ru/strings.xml | 14 +-- app/src/main/res/values-sv/strings.xml | 14 +-- app/src/main/res/values-tr/strings.xml | 14 +-- app/src/main/res/values-uk/strings.xml | 14 +-- app/src/main/res/values-zh-rCN/strings.xml | 14 +-- app/src/main/res/values/settings.xml | 63 +++++++------ app/src/main/res/values/strings.xml | 15 +-- .../main/res/xml/preferences_personalize.xml | 20 ++-- 45 files changed, 337 insertions(+), 392 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2830c2b..57d03abe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,16 @@ #### What's New - Menus have been refreshed with a cleaner look +- Added option to play song by itself in library/item details #### What's Improved - Made "Add to Playlist" action more prominent in selection toolbar - Fixed notification album covers not updating after changing the cover aspect ratio setting +#### What's Fixed +- Playlist detail view now respects playback settings + #### Dev/Meta - Unified navigation graph diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index 98f1b5739..68240c781 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -65,14 +65,14 @@ object IntegerTable { const val REPEAT_MODE_ALL = 0xA101 /** RepeatMode.TRACK */ const val REPEAT_MODE_TRACK = 0xA102 - /** PlaybackMode.IN_GENRE */ - const val PLAYBACK_MODE_IN_GENRE = 0xA103 - /** PlaybackMode.IN_ARTIST */ - const val PLAYBACK_MODE_IN_ARTIST = 0xA104 - /** PlaybackMode.IN_ALBUM */ - const val PLAYBACK_MODE_IN_ALBUM = 0xA105 - /** PlaybackMode.ALL_SONGS */ - const val PLAYBACK_MODE_ALL_SONGS = 0xA106 + // /** PlaybackMode.IN_GENRE (No longer used but still reserved) */ + // const val PLAYBACK_MODE_IN_GENRE = 0xA103 + // /** PlaybackMode.IN_ARTIST (No longer used but still reserved) */ + // const val PLAYBACK_MODE_IN_ARTIST = 0xA104 + // /** PlaybackMode.IN_ALBUM (No longer used but still reserved) */ + // const val PLAYBACK_MODE_IN_ALBUM = 0xA105 + // /** PlaybackMode.ALL_SONGS (No longer used but still reserved) */ + // const val PLAYBACK_MODE_ALL_SONGS = 0xA106 /** MusicMode.SONGS */ const val MUSIC_MODE_SONGS = 0xA10B /** MusicMode.ALBUMS */ @@ -121,8 +121,8 @@ object IntegerTable { const val COVER_MODE_MEDIA_STORE = 0xA11D /** CoverMode.Quality */ const val COVER_MODE_QUALITY = 0xA11E - /** PlaySong.Itself */ - const val PLAY_SONG_ITSELF = 0xA11F + /** PlaySong.ByItself */ + const val PLAY_SONG_BY_ITSELF = 0xA11F /** PlaySong.FromAll */ const val PLAY_SONG_FROM_ALL = 0xA120 /** PlaySong.FromAlbum */ 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 f2f3f0543..61ac27fef 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -44,7 +44,6 @@ import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.PlaylistDecision @@ -180,12 +179,7 @@ class AlbumDetailFragment : } override fun onRealClick(item: Song) { - val mode = detailModel.playbackMode - if (mode != null) { - playbackModel.play(item, detailModel.playbackMode ?: MusicMode.ALBUMS) - } else { - playbackModel.playFromAlbum(item) - } + playbackModel.play(item, detailModel.playInAlbumWith) } override fun onOpenMenu(item: Song, anchor: View) { 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 973a524ce..1673da9de 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -179,17 +179,7 @@ class ArtistDetailFragment : override fun onRealClick(item: Music) { when (item) { is Album -> detailModel.showAlbum(item) - is Song -> { - val playbackMode = detailModel.playbackMode - if (playbackMode != null) { - playbackModel.play(item, playbackMode) - } else { - // When configured to play from the selected item, we already have an Artist - // to play from. - playbackModel.playFromArtist( - item, unlikelyToBeNull(detailModel.currentArtist.value)) - } - } + is Song -> playbackModel.play(item, detailModel.playInArtistWith) else -> error("Unexpected datatype: ${item::class.simpleName}") } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index ee1fe4df0..422a02764 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -42,18 +42,19 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.info.ReleaseType import org.oxycblt.auxio.music.metadata.AudioProperties +import org.oxycblt.auxio.playback.PlaySong import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.util.Event import org.oxycblt.auxio.util.MutableEvent import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW +import org.oxycblt.auxio.util.unlikelyToBeNull /** * [ViewModel] that manages the Song, Album, Artist, and Genre detail views. Keeps track of the @@ -115,6 +116,10 @@ constructor( currentAlbum.value?.let { refreshAlbumList(it, true) } } + /** The [PlaySong] instructions to use when playing a [Song] from [Album] details. */ + val playInAlbumWith + get() = playbackSettings.inParentPlaybackMode ?: PlaySong.FromAlbum + // --- ARTIST --- private val _currentArtist = MutableStateFlow(null) @@ -139,6 +144,10 @@ constructor( currentArtist.value?.let { refreshArtistList(it, true) } } + /** The [PlaySong] instructions to use when playing a [Song] from [Artist] details. */ + val playInArtistWith + get() = playbackSettings.inParentPlaybackMode ?: PlaySong.FromArtist(currentArtist.value) + // --- GENRE --- private val _currentGenre = MutableStateFlow(null) @@ -163,6 +172,10 @@ constructor( currentGenre.value?.let { refreshGenreList(it, true) } } + /** The [PlaySong] instructions to use when playing a [Song] from [Genre] details. */ + val playInGenreWith + get() = playbackSettings.inParentPlaybackMode ?: PlaySong.FromGenre(currentGenre.value) + // --- PLAYLIST --- private val _currentPlaylist = MutableStateFlow(null) @@ -186,12 +199,11 @@ constructor( val editedPlaylist: StateFlow?> get() = _editedPlaylist - /** - * The [MusicMode] to use when playing a [Song] from the UI, or null to play from the currently - * shown item. - */ - val playbackMode: MusicMode? - get() = playbackSettings.inParentPlaybackMode + /** The [PlaySong] instructions to use when playing a [Song] from [Genre] details. */ + val playInPlaylistWith + get() = + playbackSettings.inParentPlaybackMode + ?: PlaySong.FromPlaylist(unlikelyToBeNull(currentPlaylist.value)) init { musicRepository.addUpdateListener(this) 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 f71fcb48d..4a62f16a8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -177,17 +177,7 @@ class GenreDetailFragment : override fun onRealClick(item: Music) { when (item) { is Artist -> detailModel.showArtist(item) - is Song -> { - val playbackMode = detailModel.playbackMode - if (playbackMode != null) { - playbackModel.play(item, playbackMode) - } else { - // When configured to play from the selected item, we already have an Genre - // to play from. - playbackModel.playFromGenre( - item, unlikelyToBeNull(detailModel.currentGenre.value)) - } - } + is Song -> playbackModel.play(item, detailModel.playInGenreWith) else -> error("Unexpected datatype: ${item::class.simpleName}") } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt index 632352075..5365e9fba 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -229,7 +229,7 @@ class PlaylistDetailFragment : } override fun onRealClick(item: Song) { - playbackModel.playFromPlaylist(item, unlikelyToBeNull(detailModel.currentPlaylist.value)) + playbackModel.play(item, detailModel.playInPlaylistWith) } override fun onPickUp(viewHolder: RecyclerView.ViewHolder) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 4e471758a..538dcb541 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -34,6 +34,7 @@ import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.PlaySong import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.util.Event import org.oxycblt.auxio.util.MutableEvent @@ -102,9 +103,9 @@ constructor( val playlistsInstructions: Event get() = _playlistsInstructions - /** The [MusicMode] to use when playing a [Song] from the UI. */ - val playbackMode: MusicMode - get() = playbackSettings.inListPlaybackMode + /** The [PlaySong] instructions to use when playing a [Song]. */ + val playWith + get() = playbackSettings.playInListWith /** * A list of [MusicMode] corresponding to the current [Tab] configuration, excluding invisible diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 9f2539c3f..74bd833ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -137,7 +137,7 @@ class SongListFragment : } override fun onRealClick(item: Song) { - playbackModel.play(item, homeModel.playbackMode) + playbackModel.play(item, homeModel.playWith) } override fun onOpenMenu(item: Song, anchor: View) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt index 2e14aa928..a4983f97b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt @@ -22,18 +22,13 @@ import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.Playlist sealed interface PlaySong { val intCode: Int - object ByItself : PlaySong { - override val intCode = IntegerTable.PLAY_SONG_ITSELF - } - object FromAll : PlaySong { - override val intCode = IntegerTable.PLAY_SONG_ITSELF + override val intCode = IntegerTable.PLAY_SONG_FROM_ALL } object FromAlbum : PlaySong { @@ -52,19 +47,14 @@ sealed interface PlaySong { override val intCode = IntegerTable.PLAY_SONG_FROM_PLAYLIST } - companion object { - fun fromPlaybackModeTemporary(playbackMode: MusicMode) = - when (playbackMode) { - MusicMode.SONGS -> FromAll - MusicMode.ALBUMS -> FromAlbum - MusicMode.ARTISTS -> FromArtist(null) - MusicMode.GENRES -> FromGenre(null) - MusicMode.PLAYLISTS -> throw IllegalStateException() - } + object ByItself : PlaySong { + override val intCode = IntegerTable.PLAY_SONG_BY_ITSELF + } + companion object { fun fromIntCode(intCode: Int, inner: Music?): PlaySong? = when (intCode) { - IntegerTable.PLAY_SONG_ITSELF -> ByItself + IntegerTable.PLAY_SONG_BY_ITSELF -> ByItself IntegerTable.PLAY_SONG_FROM_ALBUM -> FromAlbum IntegerTable.PLAY_SONG_FROM_ARTIST -> if (inner is Artist?) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt index bfd97dbd4..481db77c5 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt @@ -24,7 +24,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.playback.replaygain.ReplayGainMode import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp import org.oxycblt.auxio.settings.Settings @@ -46,16 +45,13 @@ interface PlaybackSettings : Settings { val replayGainMode: ReplayGainMode /** The current ReplayGain pre-amp configuration. */ var replayGainPreAmp: ReplayGainPreAmp + /** How to play a song from a general list of songs, specified by [PlaySong] */ + val playInListWith: PlaySong /** - * What type of MusicParent to play from when a Song is played from a list of other items. Null - * if to play from all Songs. + * How to play a song from a parent item, specified by [PlaySong]. Null if to delegate to the UI + * context. */ - val inListPlaybackMode: MusicMode - /** - * What type of MusicParent to play from when a Song is played from within an item (ex. like in - * the detail view). Null if to play from the item it was played in. - */ - val inParentPlaybackMode: MusicMode? + val inParentPlaybackMode: PlaySong? /** Whether to keep shuffle on when playing a new Song. */ val keepShuffle: Boolean /** Whether to rewind when the skip previous button is pressed before skipping back. */ @@ -75,18 +71,20 @@ interface PlaybackSettings : Settings { class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Context) : Settings.Impl(context), PlaybackSettings { - override val inListPlaybackMode: MusicMode + override val playInListWith: PlaySong get() = - MusicMode.fromIntCode( + PlaySong.fromIntCode( sharedPreferences.getInt( - getString(R.string.set_key_in_list_playback_mode), Int.MIN_VALUE)) - ?: MusicMode.SONGS + getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE), + null) + ?: PlaySong.FromAll - override val inParentPlaybackMode: MusicMode? + override val inParentPlaybackMode: PlaySong? get() = - MusicMode.fromIntCode( + PlaySong.fromIntCode( sharedPreferences.getInt( - getString(R.string.set_key_in_parent_playback_mode), Int.MIN_VALUE)) + getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE), + null) override val barAction: ActionMode get() = @@ -132,65 +130,44 @@ class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Cont get() = sharedPreferences.getBoolean(getString(R.string.set_key_repeat_pause), false) override fun migrate() { - // "Use alternate notification action" was converted to an ActionMode setting in 3.0.0. - if (sharedPreferences.contains(OLD_KEY_ALT_NOTIF_ACTION)) { - logD("Migrating $OLD_KEY_ALT_NOTIF_ACTION") - - val mode = - if (sharedPreferences.getBoolean(OLD_KEY_ALT_NOTIF_ACTION, false)) { - ActionMode.SHUFFLE - } else { - ActionMode.REPEAT - } - - sharedPreferences.edit { - putInt(getString(R.string.set_key_notif_action), mode.intCode) - remove(OLD_KEY_ALT_NOTIF_ACTION) - apply() - } - } - - // PlaybackMode was converted to MusicMode in 3.0.0 - - fun Int.migratePlaybackMode() = + // MusicMode was converted to PlaySong in 3.2.0 + fun Int.migrateMusicMode() = when (this) { - // Convert PlaybackMode into MusicMode - IntegerTable.PLAYBACK_MODE_ALL_SONGS -> MusicMode.SONGS - IntegerTable.PLAYBACK_MODE_IN_ARTIST -> MusicMode.ARTISTS - IntegerTable.PLAYBACK_MODE_IN_ALBUM -> MusicMode.ALBUMS - IntegerTable.PLAYBACK_MODE_IN_GENRE -> MusicMode.GENRES + IntegerTable.MUSIC_MODE_SONGS -> PlaySong.FromAll + IntegerTable.MUSIC_MODE_ALBUMS -> PlaySong.FromAlbum + IntegerTable.MUSIC_MODE_ARTISTS -> PlaySong.FromArtist(null) + IntegerTable.MUSIC_MODE_GENRES -> PlaySong.FromGenre(null) else -> null } - if (sharedPreferences.contains(OLD_KEY_LIB_PLAYBACK_MODE)) { - logD("Migrating $OLD_KEY_LIB_PLAYBACK_MODE") + if (sharedPreferences.contains(OLD_KEY_LIB_MUSIC_PLAYBACK_MODE)) { + logD("Migrating $OLD_KEY_LIB_MUSIC_PLAYBACK_MODE") val mode = sharedPreferences - .getInt(OLD_KEY_LIB_PLAYBACK_MODE, IntegerTable.PLAYBACK_MODE_ALL_SONGS) - .migratePlaybackMode() - ?: MusicMode.SONGS + .getInt(OLD_KEY_LIB_MUSIC_PLAYBACK_MODE, Int.MIN_VALUE) + .migrateMusicMode() sharedPreferences.edit { - putInt(getString(R.string.set_key_in_list_playback_mode), mode.intCode) - remove(OLD_KEY_LIB_PLAYBACK_MODE) + putInt( + getString(R.string.set_key_play_in_list_with), mode?.intCode ?: Int.MIN_VALUE) + remove(OLD_KEY_LIB_MUSIC_PLAYBACK_MODE) apply() } } - if (sharedPreferences.contains(OLD_KEY_DETAIL_PLAYBACK_MODE)) { - logD("Migrating $OLD_KEY_DETAIL_PLAYBACK_MODE") + if (sharedPreferences.contains(OLD_KEY_DETAIL_MUSIC_PLAYBACK_MODE)) { + logD("Migrating $OLD_KEY_DETAIL_MUSIC_PLAYBACK_MODE") val mode = sharedPreferences - .getInt(OLD_KEY_DETAIL_PLAYBACK_MODE, Int.MIN_VALUE) - .migratePlaybackMode() + .getInt(OLD_KEY_DETAIL_MUSIC_PLAYBACK_MODE, Int.MIN_VALUE) + .migrateMusicMode() sharedPreferences.edit { putInt( - getString(R.string.set_key_in_parent_playback_mode), - mode?.intCode ?: Int.MIN_VALUE) - remove(OLD_KEY_DETAIL_PLAYBACK_MODE) + getString(R.string.set_key_play_in_parent_with), mode?.intCode ?: Int.MIN_VALUE) + remove(OLD_KEY_DETAIL_MUSIC_PLAYBACK_MODE) apply() } } @@ -216,8 +193,7 @@ class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Cont } private companion object { - const val OLD_KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION" - const val OLD_KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2" - const val OLD_KEY_DETAIL_PLAYBACK_MODE = "auxio_detail_song_play_mode" + const val OLD_KEY_LIB_MUSIC_PLAYBACK_MODE = "auxio_library_playback_mode" + const val OLD_KEY_DETAIL_MUSIC_PLAYBACK_MODE = "auxio_detail_playback_mode" } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index de2d08f2c..d0472e556 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.launch import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicSettings @@ -180,10 +179,6 @@ constructor( // --- PLAYING FUNCTIONS --- - fun play(song: Song, playbackMode: MusicMode) { - play(song, PlaySong.fromPlaybackModeTemporary(playbackMode)) - } - fun play(song: Song, with: PlaySong) { logD("Playing $song with $with") playWithImpl(song, with, isImplicitlyShuffled()) @@ -203,10 +198,6 @@ constructor( playFromAllImpl(null, true) } - fun playFromAlbum(song: Song) { - playFromAlbumImpl(song, isImplicitlyShuffled()) - } - /** * Play a [Song] from one of it's [Artist]s. * @@ -229,34 +220,20 @@ constructor( playFromGenreImpl(song, genre, isImplicitlyShuffled()) } - /** - * Play a [Song] from one of it's [Playlist]s. - * - * @param song The [Song] to play. - * @param playlist The [Playlist] to play from. Must be linked to the [Song]. - */ - fun playFromPlaylist(song: Song, playlist: Playlist) { - playFromPlaylistImpl(song, playlist, isImplicitlyShuffled()) - } - private fun isImplicitlyShuffled() = playbackManager.queue.isShuffled && playbackSettings.keepShuffle private fun playWithImpl(song: Song, with: PlaySong, shuffled: Boolean) { when (with) { - is PlaySong.ByItself -> playItselfImpl(song, shuffled) is PlaySong.FromAll -> playFromAllImpl(song, shuffled) is PlaySong.FromAlbum -> playFromAlbumImpl(song, shuffled) is PlaySong.FromArtist -> playFromArtistImpl(song, with.which, shuffled) is PlaySong.FromGenre -> playFromGenreImpl(song, with.which, shuffled) is PlaySong.FromPlaylist -> playFromPlaylistImpl(song, with.which, shuffled) + is PlaySong.ByItself -> playItselfImpl(song, shuffled) } } - private fun playItselfImpl(song: Song, shuffled: Boolean) { - playImpl(song, listOf(song), shuffled) - } - private fun playFromAllImpl(song: Song?, shuffled: Boolean) { playImpl(song, null, shuffled) } @@ -296,6 +273,10 @@ constructor( playImpl(song, playlist, shuffled) } + private fun playItselfImpl(song: Song, shuffled: Boolean) { + playImpl(song, listOf(song), shuffled) + } + private fun startPlaybackDecision(decision: PlaybackDecision) { val existing = _playbackDecision.flow.value if (existing != null) { 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 9156488cc..3a7a71233 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -174,7 +174,7 @@ class SearchFragment : ListFragment() { override fun onRealClick(item: Music) { when (item) { - is Song -> playbackModel.play(item, searchModel.playbackMode) + is Song -> playbackModel.play(item, searchModel.playWith) is Album -> detailModel.showAlbum(item) is Artist -> detailModel.showArtist(item) is Genre -> detailModel.showGenre(item) diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 8a3aa5a1c..9bf784010 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -38,6 +38,7 @@ import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.device.DeviceLibrary import org.oxycblt.auxio.music.user.UserLibrary +import org.oxycblt.auxio.playback.PlaySong import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.util.logD @@ -63,9 +64,9 @@ constructor( val searchResults: StateFlow> get() = _searchResults - /** The [MusicMode] to use when playing a [Song] from the UI. */ - val playbackMode: MusicMode - get() = playbackSettings.inListPlaybackMode + /** The [PlaySong] instructions to use when playing a [Song]. */ + val playWith + get() = playbackSettings.playInListWith init { musicRepository.addUpdateListener(this) diff --git a/app/src/main/res/values-ar-rIQ/strings.xml b/app/src/main/res/values-ar-rIQ/strings.xml index fbb6ab074..801ac1b80 100644 --- a/app/src/main/res/values-ar-rIQ/strings.xml +++ b/app/src/main/res/values-ar-rIQ/strings.xml @@ -23,9 +23,9 @@ يتم التشغيل الان تشغيل عشوائي - تشغيل من جميع الاغاني - تشغيل من البوم - تشغيل من فنان + تشغيل من جميع الاغاني + تشغيل من البوم + تشغيل من فنان طابور شغل الاغنية التالية أضف إلى الطابور @@ -63,7 +63,7 @@ تفضيل الالبوم ديناميكي سلوك - عند اختيار اغنية + عند اختيار اغنية تذكر الخلط إبقاء وضع الخلط عند تشغيل اغنية جديدة تشجيع قبل التخطي للخلف diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 8ff87a97e..efb419463 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -12,7 +12,7 @@ Дададзены ў чаргу Распрацавана Аляксандрам Кейпхартам Карыстальніцкае дзеянне панэлі прайгравання - Прайграць з альбома + Прайграць з альбома Коска (,) Плюс (+) Амперсанд (&) @@ -234,15 +234,15 @@ Перайсці да наступнага Рэжым паўтору Паводзіны - Пры прайграванні з бібліятэкі - Прайграць усе песні + Пры прайграванні з бібліятэкі + Прайграць усе песні Кіруйце загрузкай музыкі і малюнкаў Карыстальніцкае дзеянне апавяшчэння - Пры прайграванні з дэталяў прадмета - Гуляць з паказанага прадмета + Пры прайграванні з дэталяў прадмета + Гуляць з паказанага прадмета Ігнаруйце аўдыяфайлы, якія не з\'яўляюцца музыкай, напрыклад, падкасты - Гуляць ад выканаўцы - Гуляць з жанру + Гуляць ад выканаўцы + Гуляць з жанру Запамінаць перамешванне Уключайце перамешванне падчас прайгравання новай песні Кантэнт diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 68c3f3e4f..bd051766d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -29,9 +29,9 @@ Právě hraje Přehrát Náhodně - Přehrát ze všech skladeb - Přehrát z alba - Přehrát od umělce + Přehrát ze všech skladeb + Přehrát z alba + Přehrát od umělce Fronta Přehrát další Přidat do fronty @@ -80,7 +80,7 @@ Přizpůsobení bez štítků Varování: Změna předzesilovače na vysokou kladnou hodnotu může u některých zvukových stop vést k příliš vysokým hlasitostem. Chování - Při přehrávání z knihovny + Při přehrávání z knihovny Zapamatovat si náhodné přehrávání Ponechat náhodné přehrávání při přehrávání nové skladby Přetočit před přeskočením zpět @@ -180,9 +180,9 @@ Free Lossless Audio Codec (FLAC) %d kbps %d Hz - Při přehrávání z podrobností o položce + Při přehrávání z podrobností o položce Spravovat, odkud by měla být načítána hudba - Přehrát ze zobrazené položky + Přehrát ze zobrazené položky Zobrazit vlastnosti Vlastnosti skladby Název souboru @@ -263,7 +263,7 @@ Přehrát vybrané Vybráno %d Náhodně přehrát vybrané - Přehrát z žánru + Přehrát z žánru Wiki %1$s, %2$s Obnovit diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0321ffeec..00b9ac5dc 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -15,8 +15,8 @@ Aufsteigend Abspielen Zufällig - Von allen Lieder abspielen - Von Album abspielen + Von allen Lieder abspielen + Von Album abspielen Aktuelle Wiedergabe Warteschlange Als Nächstes abspielen @@ -56,7 +56,7 @@ Titel bevorzugen Album bevorzugen Personalisieren - Wenn ein Lied aus der Bibliothek abgespielt wird + Wenn ein Lied aus der Bibliothek abgespielt wird Zufällig-Einstellung merken Zufällig anlassen, wenn ein neues Lied abgespielt wird Zurückspulen, bevor das Lied zurück geändert wird @@ -153,8 +153,8 @@ Gesamtdauer: %s Abbrechen Warnung: Das Erhöhen der Vorverstärkung zu einem hohen positiven Wert könnte zu einer Übersteuerung bei einigen Audiospuren führen. - Wenn ein Lied aus den Elementdetails abgespielt wird - Vom dargestellten Element abspielen + Wenn ein Lied aus den Elementdetails abgespielt wird + Vom dargestellten Element abspielen Musikordner Verwalten, von wo die Musik geladen werden soll Modus @@ -233,7 +233,7 @@ Komma (,) Schrägstrich (/) Plus (+) - Vom Künstler abspielen + Vom Künstler abspielen Achtung: Verwenden dieser Einstellung könnte dazu führen, dass einige Tags fälschlicherweise interpretiert werden, als hätten sie mehrere Werte. Das kann gelöst werden, in dem vor ungewollte Trenner ein Backslash (\\) eingefügt wird. Nicht-Musik ausschließen Audio-Dateien, die keine Musik sind (wie Podcasts), ignorieren @@ -254,7 +254,7 @@ Ausgewählte abspielen Ausgewählte zufällig abspielen %d ausgewählt - Vom Genre abspielen + Vom Genre abspielen Wiki %1$s, %2$s Zurücksetzen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d1a76b43b..f46f63bdf 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -23,9 +23,9 @@ En reproducción Reproducir Mezcla - Reproducir todo - Reproducir por álbum - Reproducir por artista + Reproducir todo + Reproducir por álbum + Reproducir por artista Cola Reproducir siguiente Agregar a la cola @@ -63,7 +63,7 @@ Por álbum Preferir el álbum si se está en reproducción Comportamiento - Cuando se está reproduciendo de la biblioteca + Cuando se está reproduciendo de la biblioteca Recordar mezcla Mantener mezcla cuando se reproduce una nueva canción Rebobinar atrás @@ -149,7 +149,7 @@ Estadísticas de la biblioteca Ajuste sin etiquetas Advertencia: Cambiar el pre-amp a un valor alto puede resultar en picos en algunas pistas de audio. - Reproducir desde el elemento que se muestra + Reproducir desde el elemento que se muestra Modo Excluir La músicano se cargará de las carpetas que añadas. @@ -171,7 +171,7 @@ Monitorizando la librería de música Monitorizando cambios en tu librería de música… Audio ogg - Cuando se reproduce desde los detalles + Cuando se reproduce desde los detalles Fecha de añadido Propiedades de la canción Frecuencia de muestreo @@ -258,7 +258,7 @@ Nodo aleatorio seleccionado %d seleccionado Reproducir los seleccionados - Reproducir desde el género + Reproducir desde el género Wiki %1$s, %2$s Restablecer diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 21f8693dd..d99365815 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -68,12 +68,12 @@ Muuta kirjastovälilehtien näkyvyyttä ja järjestystä Siirry seuraavaan Kertaustila - Kirjastosta toistettaessa - Kohteen tiedoista toistettaessa + Kirjastosta toistettaessa + Kohteen tiedoista toistettaessa Muista sekoitus - Toista kaikista kappaleista - Toista albumilta - Toista tyylilajista + Toista kaikista kappaleista + Toista albumilta + Toista tyylilajista Moniarvoerottimet Ohita äänitiedostot, jotka eivät ole musiikkia, kuten podcastit Ja-merkki (&) @@ -238,7 +238,7 @@ Tuntematon tyylilaji Vihreä Musiikkia ei toisteta - Toista esittäjältä + Toista esittäjältä Ohita muu kuin musiikki Palauta aiemmin tallennettu toiston tila (jos olemassa) Musiikkia ei ladata valitsemistasi kansioista. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4a6cb7884..599471f22 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -169,9 +169,9 @@ Recharger la bibliothèque musicale chaque fois qu\'elle change (nécessite une notification persistante) Esperluette (&) Playlist - Lors de la lecture à partir des détails de l\'élément + Lors de la lecture à partir des détails de l\'élément Gardez la lecture aléatoire lors de la lecture d\'une nouvelle chanson - Lire à partir de l\'élément affiché + Lire à partir de l\'élément affiché N\'oubliez pas de mélanger Contrôlez le chargement de la musique et des images Musique @@ -185,18 +185,18 @@ Couvertures originales (téléchargement rapide) Configurer le son et le comportement de lecture Listes de lecture - Lors de la lecture depuis la bibliothèque + Lors de la lecture depuis la bibliothèque Séparateurs multi-valeurs Rechargement automatique - Jouer à partir de toutes les chansons - Jouer de l\'artiste - Jouer à partir du genre + Jouer à partir de toutes les chansons + Jouer de l\'artiste + Jouer à partir du genre Virgule (,) Point-virgule (;) Ignorer les fichiers audio qui ne sont pas de la musique, tels que les podcasts Avertissement : L\'utilisation de ce paramètre peut entraîner l\'interprétation incorrecte de certaines balises comme ayant plusieurs valeurs. Vous pouvez résoudre ce problème en préfixant les caractères de séparation indésirables avec une barre oblique inverse (\\). Exclure non-musique - Lire depuis l\'album + Lire depuis l\'album Barre oblique (/) Plus (+) Vider l\'état de lecture précédemment enregistré (si il existe) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 1ba5cedaf..b00782948 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -8,7 +8,7 @@ Axustes Claro Escuro - Cando se está a reproducir dende a biblioteca + Cando se está a reproducir dende a biblioteca Artista Un reproductor de música simple e racional para android. Sinxelo @@ -82,17 +82,17 @@ Recarga a biblioteca de música cando cambia (require unha notificación persistente) Acción da notificación personalizada Saltar ao seguinte - Reproducir dende xénero + Reproducir dende xénero Manter a mestura ao reproducir unha canción nova Contido Modo de repetición Comportamento - Reproducir dende artista + Reproducir dende artista Lembrar a mestura Música - Reproducir dende o elemento que se mostra - Reproducir dende todas as cancións - Reproducir dende álbum + Reproducir dende o elemento que se mostra + Reproducir dende todas as cancións + Reproducir dende álbum Recarga automática Ignorar arquivos de audio que non sexan música, como os pódcasts Todas as cancións @@ -138,7 +138,7 @@ Pestanas de biblioteca Cambiar a visibilidade e a orde das pestanas da biblioteca Acción personalizada da barra de reprodución - Cando se reproduce dende os detalles + Cando se reproduce dende os detalles Controla como se carga a música e as imaxes Separadores de varios valores Configura caracteres que denotan múltiples valores da etiqueta diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index b4dc11d12..61b83f80b 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -63,7 +63,7 @@ अगला चलाएं फ़ाइल का नाम लायब्रेरी टैब्स - एल्बम से चलाएं + एल्बम से चलाएं सामग्री %d चयनित प्रारूप @@ -85,8 +85,8 @@ गतिशील लुक और फील अतिरिक्त UI तत्वों पर गोल कोनों को सक्षम करें (एल्बम कवर को गोल करने की आवश्यकता है) - दिखाए गए आइटम से चलाएँ - लाइब्रेरी से चलाते समय + दिखाए गए आइटम से चलाएँ + लाइब्रेरी से चलाते समय संगीत लाइब्रेरी को फिर से लोड करें जब भी यह बदलता है (स्थाई नोटीफिकेशन की आवश्यकता होती है) ऑडियो फ़ाइलों को अनदेखा करें जो संगीत नहीं हैं, जैसे कि पॉडकास्ट लाइव संकलन @@ -96,7 +96,7 @@ प्लेलिस्ट प्लेलिस्टें गोल मोड - सभी गीतों से चलाएं + सभी गीतों से चलाएं %s हटाएँ\? इसे पूर्ववत नहीं किया जा सकता। लोड किए गए गाने: %d अवरोही @@ -109,7 +109,7 @@ UI नियंत्रण और व्यवहार अनुकूलित करें कलाकार लोड किए गए: %d कस्टम प्लेबैक बार एक्शन - आइटम विवरण से चलाते समय + आइटम विवरण से चलाते समय लाइव एल्बम रीमिक्स एल्बम लाइव EP @@ -165,8 +165,8 @@ गीत के गुणधर्म डिस्पले कस्टम नोटीफिकेशन एक्शन - कलाकार से चलाएं - शैली से चलाएं + कलाकार से चलाएं + शैली से चलाएं फेरबदल याद रखें नया गाना बजाते समय फेरबदल करते रहें मल्टी-मूल्य विभाजक diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 6be80a5d0..8ab7b06c6 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -82,9 +82,9 @@ Prilagođavanje s oznakama Prilagođavanje bez oznaka Upozorenje: Postavljanje pretpojačala na visoke razine može uzrokovati vrhunce tonova u nekim zvučnim zapisima. - Kada se reproducira iz zbirke - Kada se reproducira iz detalja predmeta - Reproduciraj iz svih pjesama + Kada se reproducira iz zbirke + Kada se reproducira iz detalja predmeta + Reproduciraj iz svih pjesama Aktualiziraj glazbu Ponovo učitaj glazbenu biblioteku, koristeći predmemorirane oznake kada je to moguće Mape glazbe @@ -191,10 +191,10 @@ Premotaj prije preskakanja natrag Spremi trenutno stanje reprodukcije Preskoči na sljedeću pjesmu - Reproduciraj iz prikazanog predmeta + Reproduciraj iz prikazanog predmeta Zapamti miješanje glazbe Vrati prethodno spremljeno stanje reprodukcije (ako postoji) - Reproduciraj iz albuma + Reproduciraj iz albuma Pauziraj čim se pjesma ponovi Premotaj prije vraćanja na prethodnu pjesmu Reproduciraj ili pauziraj @@ -230,7 +230,7 @@ Sakrij suradnike Isključeno Isključi sve što nije glazba - Reproduciraj iz izvođača + Reproduciraj iz izvođača Visoka kvaliteta Brzo Zanemari sve audio datoteke koje nisu glazba, npr. podcast datoteke @@ -249,7 +249,7 @@ Odabrano: %d Promiješaj odabrane Reproduciraj odabrane - Reproduciraj iz žanra + Reproduciraj iz žanra Wiki %1$s, %2$s Resetiraj diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 2717d6868..f8997a950 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -145,8 +145,8 @@ Kiválasztottak keverése UI vezérlők és viselkedés testreszabása A könyvtárfülek láthatóságának és sorrendjének módosítása - A tétel részleteiből történő lejátszáskor - Lejátszás albumból + A tétel részleteiből történő lejátszáskor + Lejátszás albumból A zene és a képek betöltésének vezérlése Képek Időtartam @@ -175,8 +175,8 @@ Alaphelyzet Állapot törölve Fejlesztő Alexander Capehart - Lejátszás az összes dalból - Lejátszás műfajból + Lejátszás az összes dalból + Lejátszás műfajból Tartalom A zenei könyvtár újratöltése, ha változik (állandó értesítést igényel) Zene könyvtárak @@ -212,8 +212,8 @@ Mód Free Lossless Audio Codec (FLAC) Beállítás címkékkel - A könyvtárból történő lejátszáskor - Lejátszás a megjelenő elemről + A könyvtárból történő lejátszáskor + Lejátszás a megjelenő elemről %d kbps Betöltött műfaj: %d Zene betöltés @@ -250,7 +250,7 @@ Playlista átnevezve Playlista törölve Ugrás a következőre - Lejátszás előadótól + Lejátszás előadótól Zene A nem zenei fájlok, például podcastok figyelmen kívül hagyása Több címkeértéket jelölő karakterek konfigurálása diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index fdd5eb194..525ed9115 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -93,8 +93,8 @@ Pre-amp diterapkan ke penyesuaian yang ada selama pemutaran Penyesuaian dengan tag Peringatan: Mengubah pre-amp ke nilai positif yang tinggi dapat mengakibatkan puncak pada beberapa trek audio. - Putar dari item yang ditampilkan - Putar dari semua lagu + Putar dari item yang ditampilkan + Putar dari semua lagu Tetap mengacak saat memutar lagu baru Jeda pada pengulangan Putar balik sebelum melompat ke belakang @@ -133,17 +133,17 @@ Album yang dimuat: %d Artis yang dimuat: %d Utamakan album jika ada yang diputar - Saat diputar dari pustaka - Putar dari album + Saat diputar dari pustaka + Putar dari album Ubah mode pengulangan Gambar Artis untuk %s - Saat diputar dari keterangan item + Saat diputar dari keterangan item Musik tidak akan dimuat dari folder yang Anda tambahkan. Hapus lagu antrian ini Hapus kueri pencarian Penyesuaian tanpa tag Folder musik - Putar dari artis + Putar dari artis Mode Auxio memerlukan izin untuk membaca perpustakaan musik Anda Loncat ke lagu terakhir @@ -193,7 +193,7 @@ Kualitas tinggi Titik koma (;) Wiki - Putar dari aliran + Putar dari aliran Aliran Sampul album Nonaktif diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 84ee01c8f..9d8dee9cb 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -23,9 +23,9 @@ Ora in riproduzione Riproduci Mescola - Riproduci da tutte le canzoni - Riproduci dall\'album - Riproduci dall\'artista + Riproduci da tutte le canzoni + Riproduci dall\'album + Riproduci dall\'artista Coda Riproduci successivo Accoda @@ -66,7 +66,7 @@ Preferisci album Preferisci l\'album se in riproduzione Comportamento - Quando in riproduzione dalla libreria + Quando in riproduzione dalla libreria Mantieni mescolamento Mantiene il mescolamento anche se una nuova canzone è selezionata Riavvolgi prima di saltare indietro @@ -154,14 +154,14 @@ +%.1f dB %d Hz Caricamento libreria musicale… (%1$d/%2$d) - Quando in riproduzione dai dettagli dell\'elemento + Quando in riproduzione dai dettagli dell\'elemento Attenzione: impostare valore positivi alti può provocare distorsioni su alcune tracce. Regolazione senza tag Mescola Mescola tutto Regolazione con tag Il pre-amp è applicato alla regolazione esistente durante la riproduzione - Riproduci dall\'elemento mostrato + Riproduci dall\'elemento mostrato Gestisci le cartelle da dove caricare la musica Cartelle musica Escludi @@ -258,7 +258,7 @@ Mescola selezionati Riproduci selezionati %d selezionati - Riproduci dal genere + Riproduci dal genere Wiki %1$s, %2$s Ripristina diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index f3b64bdc3..e439eaf5a 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -87,13 +87,13 @@ דילוג לבא מצב חזרה התנהגות - כאשר מנוגן מהספרייה - כאשר מנוגן מפרטי הפריט - ניגון מהפריט המוצג - ניגון מכל השירים - ניגון מאלבום - ניגון מהאומן - ניגון מסוגה + כאשר מנוגן מהספרייה + כאשר מנוגן מפרטי הפריט + ניגון מהפריט המוצג + ניגון מכל השירים + ניגון מאלבום + ניגון מהאומן + ניגון מסוגה לזכור ערבוב המשך ערבוב בעת הפעלת שיר חדש תוכן diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index d1b62413b..b571ec42e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -54,7 +54,7 @@ カラースキーム 黒基調 再生状態を復元 - 表示されたアイテムから再生 + 表示されたアイテムから再生 再生停止 ファイル名 @@ -151,11 +151,11 @@ ヘッドセット接続時に常時再生開始 (動作しない機種あり) ヘッドセット自動再生 リプレイゲイン - すべての曲から再生 - アルバムから再生 - アーティストから再生 - ライブラリからの再生時 - アイテム詳細からの再生時 + すべての曲から再生 + アルバムから再生 + アーティストから再生 + ライブラリからの再生時 + アイテム詳細からの再生時 音楽以外を除外 ここに追加したフォルダからのみ音楽が読み込まれます。 前回保存された再生状態がある場合、再生状態を復元 @@ -227,7 +227,7 @@ 次ヘスキップ 繰り返しモード - ジャンルから再生 + ジャンルから再生 新しい曲の再生時にシャフルを保持 ダイナミック 再生状態を解除できません diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 7460a0012..2de3ff1bd 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -27,9 +27,9 @@ 지금 재생 중 재생 셔플 - 모든 곡에서 재생 - 앨범에서 재생 - 아티스트에서 재생 + 모든 곡에서 재생 + 앨범에서 재생 + 아티스트에서 재생 대기열 다음 곡 재생 대기열에 추가 @@ -78,7 +78,7 @@ 태그 없이 조정 주의: 프리앰프를 높게 설정하면 일부 소리 트랙이 왜곡될 수 있습니다. 동작 - 라이브러리에서 재생할 때 + 라이브러리에서 재생할 때 무작위 재생 기억 새로운 곡을 재생할 때 무작위 재생 유지 이전 곡으로 가기 전에 되감기 @@ -178,7 +178,7 @@ DJ믹스 이퀄라이저 셔플 - 표시된 항목에서 재생 + 표시된 항목에서 재생 음악 라이브러리를 불러오는 중… 재생 상태 지우기 재생 상태 복원 @@ -238,7 +238,7 @@ 음악 라이브러리를 불러오는 중… (%1$d/%2$d) 장르 경고: 이 설정을 사용하면 일부 태그가 여러 값을 갖는 것으로 잘못 해석될 수 있습니다. 구분자로 읽히지 않도록 하려면 해당 구분자 앞에 백슬래시 (\\)를 붙입니다. - 항목 세부 정보에서 재생할 때 + 항목 세부 정보에서 재생할 때 음악 라이브러리의 변경사항을 추적하는 중… 다음 곡으로 건너뛰기 팟캐스트와 같이 음악이 아닌 소리 파일 무시 @@ -256,7 +256,7 @@ %d 선택됨 재설정 위키 - 장르에서 재생 + 장르에서 재생 %1$s, %2$s 리플레이게인 사운드 및 재생 동작 구성 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 946097b67..673981837 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -155,8 +155,8 @@ Persukti prieš šokant atgal Persukti atgal prieš peršokant į ankstesnę dainą Pauzė ant kartojamo - Kai grojant iš bibliotekos - Kai grojant iš elemento detalių + Kai grojant iš bibliotekos + Kai grojant iš elemento detalių Pašalinti aplanką Žanras Ieškokite savo bibliotekoje… @@ -174,10 +174,10 @@ Muzika nebus įkeliama iš pridėtų aplankų jūs pridėsite. Įtraukti Pašalinti šią dainą - Groti iš visų dainų - Groti iš parodyto elemento - Groti iš albumo - Groti iš atlikėjo + Groti iš visų dainų + Groti iš parodyto elemento + Groti iš albumo + Groti iš atlikėjo Išvalyta būsena Neįtraukti Muzika bus įkeliama iš aplankų jūs pridėsite. @@ -252,7 +252,7 @@ %d Pasirinkta Groti pasirinktą Pasirinktas maišymas - Groti iš žanro + Groti iš žanro Viki %1$s, %2$s Nustatyti iš naujo diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e27184725..4e1cd3ec0 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -17,9 +17,9 @@ Oplopend Afspelen Shuffle - Speel van alle nummers - Speel af van album - Speel van artiest + Speel van alle nummers + Speel af van album + Speel van artiest Nu afspelen Wachtrij Afspelen als volgende @@ -48,7 +48,7 @@ Gebruikt een afternatief notification action Audio Gedrag - Bij het afspelen vanuit de bibliotheek + Bij het afspelen vanuit de bibliotheek Onthoud shuffle Houd shuffle aan bij het afspelen van een nieuw nummer Terugspoelen voordat je terugspoelt @@ -149,7 +149,7 @@ Aanpassing met tags Aanpassing zonder tags Er speelt geen muziek - Bij het afspelen van item details + Bij het afspelen van item details Ronde modus Afgeronde hoeken inschakelen voor extra UI-elementen (vereist dat albumhoezen zijn afgerond) Staat gerestaureerd @@ -158,7 +158,7 @@ Headset automatisch afspelen ReplayGain Waarschuwing: Als u de voorversterker op een hoge positieve waarde zet, kan dit bij sommige audiotracks tot pieken leiden. - Afspelen vanaf getoond item + Afspelen vanaf getoond item Afspeelstatus herstellen Herstel de eerder opgeslagen afspeelstatus (indien aanwezig) Kan status niet herstellen @@ -271,7 +271,7 @@ Verwijderen Scheiders met meerdere waarden Verberg bijdragers - Speel vanuit genre + Speel vanuit genre Datum toegevoegd %1$s, %2$s Afspeellijst %d diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index d8ad7d851..97817e254 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -112,13 +112,13 @@ ਅਗਲੇ \'ਤੇ ਜਾਓ ਦੁਹਰਾਓ ਮੋਡ ਵਿਵਹਾਰ - ਜਦੋਂ ਲਾਇਬ੍ਰੇਰੀ ਤੋਂ ਚਲਾਉਂਦੇ ਹਾਂ - ਜਦੋਂ ਆਈਟਮ ਦੇ ਵੇਰਵਿਆਂ ਤੋਂ ਚਲਾਉਂਦੇ ਹਾਂ - ਦਿਖਾਈ ਗਈ ਆਈਟਮ ਤੋਂ ਚਲਾਓ - ਸਾਰੇ ਗੀਤਾਂ ਤੋਂ ਚਲਾਓ - ਐਲਬਮ ਤੋਂ ਚਲਾਓ - ਕਲਾਕਾਰ ਤੋਂ ਖੇਡੋ - ਸ਼ੈਲੀ ਤੋਂ ਖੇਡੋ + ਜਦੋਂ ਲਾਇਬ੍ਰੇਰੀ ਤੋਂ ਚਲਾਉਂਦੇ ਹਾਂ + ਜਦੋਂ ਆਈਟਮ ਦੇ ਵੇਰਵਿਆਂ ਤੋਂ ਚਲਾਉਂਦੇ ਹਾਂ + ਦਿਖਾਈ ਗਈ ਆਈਟਮ ਤੋਂ ਚਲਾਓ + ਸਾਰੇ ਗੀਤਾਂ ਤੋਂ ਚਲਾਓ + ਐਲਬਮ ਤੋਂ ਚਲਾਓ + ਕਲਾਕਾਰ ਤੋਂ ਖੇਡੋ + ਸ਼ੈਲੀ ਤੋਂ ਖੇਡੋ ਸ਼ਫਲ ਯਾਦ ਰੱਖੋ ਗੀਤ ਦੁਹਰਾਉਣ ਤੇ ਰੋਕੋ ਰੀਪਲੇਅ-ਗੇਨ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index dbce6c061..fc0c20794 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -132,8 +132,8 @@ Korektor Rozmiar Brak folderów - Odtwórz wszystkie utwory - Odtwórz album + Odtwórz wszystkie utwory + Odtwórz album Automatycznie odtwórz muzykę po podłączeniu słuchawek (może nie działać na wszystkich urządzeniach) Odśwież muzykę Odśwież bibliotekę muzyczną używając tagów z pamięci cache, jeśli są dostępne @@ -157,7 +157,7 @@ Tryb powtarzania Ustawienie ReplayGain Preferuj album, jeśli jest odtwarzany - Odtwarzanie z widoku biblioteki + Odtwarzanie z widoku biblioteki Zapisz stan odtwarzania Przecinek (,) Średnik (;) @@ -215,8 +215,8 @@ Regulacja w oparciu o tagi Regulacja bez tagów Wzmocnienie dźwięku przez preamplifier jest nakładane na wcześniej ustawione wzmocnienie podczas odtwarzania - Odtwarzanie z widoku szczegółowego - Odtwórz tylko wybrane + Odtwarzanie z widoku szczegółowego + Odtwórz tylko wybrane Zatrzymaj odtwarzanie, kiedy utwór się powtórzy Muzyka będzie importowana tylko z wybranych folderów. Znaki oddzielające wartości @@ -237,7 +237,7 @@ Kontynuuj odtwarzanie losowe po wybraniu nowego utworu Zaimportowane utwory: %d Ignoruj pliki audio które nie są utworami muzycznymi (np. podcasty) - Odtwórz od wykonawcy + Odtwórz od wykonawcy Wyklucz inne pliki dźwiękowe Okładki albumów Wyłączone @@ -262,7 +262,7 @@ Resetuj Wiki Funkcje - Odtwórz z gatunku + Odtwórz z gatunku Wyczyść pamięć cache z tagami i zaimportuj ponownie bibliotekę (wolniej, ale dokładniej) Zaimportuj ponownie bibliotekę diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 845157184..af9052aa1 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -163,8 +163,8 @@ Exibição Ativa cantos arredondados em elementos adicionais da interface do usuário Modo de normalização de volume (ReplayGain) - Reproduzir a partir do item mostrado - Reproduzir de todas as músicas + Reproduzir a partir do item mostrado + Reproduzir de todas as músicas Preferir álbum Prefira o álbum se estiver tocando Recarregamento automático @@ -183,7 +183,7 @@ Monitorando alterações na sua biblioteca de músicas… Abas da biblioteca Gênero - Reproduzir do artista + Reproduzir do artista Restaura a lista de reprodução salva anteriormente (se houver) Ajuste em faixas com metadados Lista limpa @@ -193,7 +193,7 @@ Monitorando a biblioteca de músicas Cantos arredondados Pular para o próximo - Reproduzir do álbum + Reproduzir do álbum Salvar lista de reprodução Limpar lista de reprodução Restaurar lista de reprodução @@ -211,8 +211,8 @@ Single remix Conteúdo Faixa - Ao tocar da biblioteca - Ao tocar a partir dos detalhes do item + Ao tocar da biblioteca + Ao tocar a partir dos detalhes do item Mixtapes Mixtape Remixes @@ -259,7 +259,7 @@ Wiki Redefinir %1$s, %2$s - Tocar a partir do gênero + Tocar a partir do gênero Configure o comportamento de som e reprodução Imagens Mude o tema e as cores do aplicativo diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index ff1aa5b04..d56b5eee1 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -71,8 +71,8 @@ %d Álbuns %d Álbuns - Reproduzir a partir do item mostrado - Reproduzir do álbum + Reproduzir a partir do item mostrado + Reproduzir do álbum Imagem do artista para %s Gênero desconhecido Nenhuma faixa @@ -93,7 +93,7 @@ Qualidade alta Ação da barra de reprodução personalizada Modo de repetição - Reproduzir do artista + Reproduzir do artista Pausar na repetição O Auxio precisa de permissão para ler a sua biblioteca de músicas Sem pastas @@ -188,7 +188,7 @@ Estratégia do ganho de repetição Preferir álbum O pré-amplificador é aplicado ao ajuste existente durante a reprodução - Reproduzir de todas as músicas + Reproduzir de todas as músicas Pausa quando uma música se repete Limpe o estado de reprodução salvo anteriormente (se houver) Restaurar o estado de reprodução @@ -239,8 +239,8 @@ Mostrar apenas artistas que foram creditados diretamente no álbum (funciona melhor em músicas com metadados completos) Preferir faixa Pré-amplificação da normalização de volume - Ao tocar a partir dos detalhes do item - Tocar a partir do gênero + Ao tocar a partir dos detalhes do item + Tocar a partir do gênero Retrocede a música antes de voltar para a anterior Recarregar música %1$s, %2$s @@ -250,7 +250,7 @@ Nenhuma lista pode ser restaurada Ícone do Auxio Aleatorizar tudo - Ao tocar da biblioteca + Ao tocar da biblioteca Singles Single Recarrega a biblioteca de músicas usando metadados salvos em cache quando possível diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 35c85858b..ed696e932 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -140,23 +140,23 @@ Descrescător Selecție aleatorie aleasă Treceți la următoarea - Redă de la artist - Redă din genul + Redă de la artist + Redă din genul Resetează Wiki Vizualizați și controlați redarea muzicii Schimbă vizibilitatea și ordinea taburilor din bibliotecă Taburi din bibliotecă Nu uita de shuffle - Redă din toate melodiile - În timpul redării din bibliotecă - Redă de la articolul afișat + Redă din toate melodiile + În timpul redării din bibliotecă + Redă de la articolul afișat Conținut Acțiune de notificare personalizată Menține funcția shuffle activată la redarea unei melodii noi Personalizarea acțiunii bării de redare Modul de repetare - Redă din album - În timpul redării de la detaliile articolului + Redă din album + În timpul redării de la detaliile articolului Comportament \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2dfc2830c..543b0fde7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -23,9 +23,9 @@ Сейчас играет Играть Перемешать - Играть все композиции - Играть альбом - Играть исполнителя + Играть все композиции + Играть альбом + Играть исполнителя Очередь Играть далее Добавить в очередь @@ -65,7 +65,7 @@ По альбому Предпочитать по альбому, если он воспроизводится Поведение - При воспроизведении из библиотеки + При воспроизведении из библиотеки Запоминать перемешивание Запоминать режим перемешивания для новых треков Сначала перемотать трек @@ -146,8 +146,8 @@ Перемешать Перемешать всё ОК - При воспроизведении из сведений - Воспроизведение с показанного элемента + При воспроизведении из сведений + Воспроизведение с показанного элемента Номер песни Битрейт Диск @@ -264,7 +264,7 @@ Вики Сбросить %1$s,%2$s - Играть жанр + Играть жанр Поведение Выравнивание громкости ReplayGain Музыка diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index a00ec585d..8b958539b 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -124,8 +124,8 @@ Hoppa till nästa Upprepningsmodus Beteende - När spelar från artikeluppgifter - Spela från genre + När spelar från artikeluppgifter + Spela från genre Komma ihåg blandningsstatus Behåll blandning på när spelar en ny låt Kontent @@ -141,11 +141,11 @@ Dölj medarbetare Skärm Bibliotekflikar - När spelar från biblioteket - Spela från visad artikel - Spela från alla låtar - Spela från konstnär - Spela från album + När spelar från biblioteket + Spela från visad artikel + Spela från alla låtar + Spela från konstnär + Spela från album Semikolon (;) Ladda om musikbiblioteket när det ändras (kräver permanent meddelande) Komma (,) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 073c463c3..3a7d65394 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -92,9 +92,9 @@ Etiketsiz ayarla Etiket ile ayarla Uyarı: Ön amfinin yüksek bir pozitif değere değiştirilmesi bazı ses parçalarında pik yapmaya neden olabilir. - Gösterilen öğeden çal - Tüm şarkılardan çal - Albümden çal + Gösterilen öğeden çal + Tüm şarkılardan çal + Albümden çal Müzik klasörleri Müzik yalnızca eklediğiniz klasörlerden yüklenecektir. %s Albümünün kapağı @@ -112,9 +112,9 @@ Kireç Sarı Turuncu - Kitaplıktan çalarken - Öğe ayrıntılarından çalarken - Sanatçıdan çal + Kitaplıktan çalarken + Öğe ayrıntılarından çalarken + Sanatçıdan çal Yüklenen sanatçılar: %d Yüklenen türler: %d %d Hz @@ -248,7 +248,7 @@ Eğik çizgi (/) Kuyruğu aç Tekrar kipi - Türden çal + Türden çal Podcast\'ler gibi müzik olmayan ses dosyalarını yok say Uyarı: Bu ayarın kullanılması bazı etiketlerin yanlışlıkla birden fazla değere sahip olarak yorumlanmasına neden olabilir. Bunu, istenmeyen ayırıcı karakterlerin önüne ters eğik çizgi (\\) koyarak çözebilirsiniz. Müzik olmayanları hariç tut diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 73f6bb64a..cc1d9cc8c 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -143,14 +143,14 @@ Режим повторення Режим Попередній підсилювач ReplayGain - Відтворити альбом - При відтворенні з бібліотеки + Відтворити альбом + При відтворенні з бібліотеки Віддавати перевагу альбому, якщо він відтворюється Стан відтворення очищено Використовувати повністю чорну тему Показувати лише тих виконавців, які безпосередньо зазначені в альбомі (найкраще працює в добре позначених бібліотеках) Увага: Встановлення високих позитивних значень попереднього підсилювача може призвести до спотворення звуку в деяких піснях. - При відтворенні з деталей предмета + При відтворенні з деталей предмета Очистити кеш тегів і повністю перезавантажити музичну бібліотеку (повільніше, але ефективніше) Автоматичне перезавантаження Перезавантажувати бібліотеку при виявленні змін (потрібне постійне сповіщення) @@ -164,10 +164,10 @@ Відстеження змін в музичній бібліотеці… Власна дія для панелі відтворення Регулювання без тегів - Відтворення з показаного елемента + Відтворення з показаного елемента Продовжити перемішування після вибору нової пісні - Відтворити виконавця - Відтворити жанр + Відтворити виконавця + Відтворити жанр Перемотати назад перед відтворенням попередньої пісні Зберегти поточний стан відтворення Пересканувати музику @@ -187,7 +187,7 @@ Перемотайте на початок пісні перед відтворенням попередньої Увімкнути заокруглені кути на додаткових елементах інтерфейсу (потрібно заокруглення обкладинок альбомів) Попередній підсилювач застосовується до наявних налаштувань під час відтворення - Відтворити всі пісні + Відтворити всі пісні Перезавантажити музичну бібліотеку, використовуючи кешовані теги, коли це можливо Скісна риска (/) Плюс (+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d75168b77..cb041474b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -23,9 +23,9 @@ 正在播放 播放 随机 - 从全部歌曲开始播放 - 从专辑开始播放 - 从艺术家播放 + 从全部歌曲开始播放 + 从专辑开始播放 + 从艺术家播放 播放队列 作为下一首播放 加入播放队列 @@ -65,7 +65,7 @@ 偏好专辑 如果已有专辑正在播放则优先增益专辑 行为 - 从音乐库中选择播放时 + 从音乐库中选择播放时 记住随机模式 播放新曲目时保留随机播放模式 切换上一曲前先倒带 @@ -162,9 +162,9 @@ 已加载艺术家数量:%d 已加载流派数量:%d 总计时长:%s - 从展示的项目播放 + 从展示的项目播放 仅从您添加的目录中加载音乐。 - 从项目详情中选择播放时 + 从项目详情中选择播放时 不会从您添加的目录中加载音乐。 高级音乐编码 (AAC) 已加载专辑数量:%d @@ -252,7 +252,7 @@ 随机播放所选 播放所选 选中了 %d 首 - 按流派播放 + 按流派播放 Wiki %1$s, %2$s 重置 diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index f3c956766..87004a171 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -27,8 +27,8 @@ auxio_pre_amp_with auxio_pre_amp_without - auxio_library_playback_mode - auxio_detail_playback_mode + auxio_play_in_list_with + auxio_play_in_parent_with KEY_KEEP_SHUFFLE KEY_PREV_REWIND KEY_LOOP_PAUSE @@ -106,34 +106,38 @@ @integer/action_mode_shuffle - - @string/set_playback_mode_songs - @string/set_playback_mode_artist - @string/set_playback_mode_album - @string/set_playback_mode_genre + + @string/set_play_song_from_all + @string/set_play_song_from_artist + @string/set_play_song_from_album + @string/set_play_song_from_genre + @string/set_play_song_by_itself - - @integer/music_mode_songs - @integer/music_mode_artist - @integer/music_mode_album - @integer/music_mode_genre + + @integer/play_song_from_all + @integer/play_song_from_album + @integer/play_song_from_artist + @integer/play_song_from_genre + @integer/play_song_itself - - @string/set_playback_mode_none - @string/set_playback_mode_songs - @string/set_playback_mode_artist - @string/set_playback_mode_album - @string/set_playback_mode_genre + + @string/set_play_song_none + @string/set_play_song_from_all + @string/set_play_song_from_artist + @string/set_play_song_from_album + @string/set_play_song_from_genre + @string/set_play_song_by_itself - - @integer/music_mode_none - @integer/music_mode_songs - @integer/music_mode_artist - @integer/music_mode_album - @integer/music_mode_genre + + @integer/play_song_none + @integer/play_song_from_all + @integer/play_song_from_album + @integer/play_song_from_artist + @integer/play_song_from_genre + @integer/play_song_itself @@ -152,11 +156,12 @@ 1 2 - -2147483648 - 0xA108 - 0xA109 - 0xA10A - 0xA10B + -2147483648 + 0xA11F + 0xA120 + 0xA121 + 0xA122 + 0xA123 0xA111 0xA112 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d610c1984..0e47fc0f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -208,13 +208,14 @@ Skip to next Repeat mode Behavior - When playing from the library - When playing from item details - Play from shown item - Play from all songs - Play from album - Play from artist - Play from genre + When playing from the library + When playing from item details + Play from shown item + Play from all songs + Play from album + Play from artist + Play from genre + Play song by itself Remember shuffle Keep shuffle on when playing a new song diff --git a/app/src/main/res/xml/preferences_personalize.xml b/app/src/main/res/xml/preferences_personalize.xml index dca8a8dfb..a9f3b9b64 100644 --- a/app/src/main/res/xml/preferences_personalize.xml +++ b/app/src/main/res/xml/preferences_personalize.xml @@ -29,19 +29,19 @@ Date: Tue, 11 Jul 2023 12:08:51 -0600 Subject: [PATCH 05/28] music: rename mode -> type Rename MusicMode to MusicType. The original naming was always a bit clunky given that it referred to both settings and data configuration. I feel that "Type" is probably better overall. --- .../org/oxycblt/auxio/home/HomeFragment.kt | 52 +++++++------- .../org/oxycblt/auxio/home/HomeSettings.kt | 6 +- .../org/oxycblt/auxio/home/HomeViewModel.kt | 68 +++++++++---------- .../auxio/home/list/AlbumListFragment.kt | 4 +- .../auxio/home/list/ArtistListFragment.kt | 4 +- .../auxio/home/list/GenreListFragment.kt | 4 +- .../auxio/home/list/PlaylistListFragment.kt | 4 +- .../auxio/home/list/SongListFragment.kt | 4 +- .../auxio/home/tabs/AdaptiveTabStrategy.kt | 14 ++-- .../java/org/oxycblt/auxio/home/tabs/Tab.kt | 34 +++++----- .../org/oxycblt/auxio/home/tabs/TabAdapter.kt | 16 ++--- .../auxio/home/tabs/TabCustomizeDialog.kt | 6 +- .../java/org/oxycblt/auxio/music/Music.kt | 30 ++++---- .../music/{MusicMode.kt => MusicType.kt} | 24 +++---- .../auxio/music/device/DeviceMusicImpl.kt | 16 ++--- .../oxycblt/auxio/music/user/PlaylistImpl.kt | 4 +- .../oxycblt/auxio/search/SearchSettings.kt | 16 +++-- .../oxycblt/auxio/search/SearchViewModel.kt | 53 +++++++-------- app/src/main/res/values/settings.xml | 2 +- .../org/oxycblt/auxio/music/MusicModeTest.kt | 8 +-- 20 files changed, 185 insertions(+), 184 deletions(-) rename app/src/main/java/org/oxycblt/auxio/music/{MusicMode.kt => MusicType.kt} (73%) diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 43dded9c6..12991e916 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -62,7 +62,7 @@ import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.music.IndexingProgress import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.NoAudioPermissionException import org.oxycblt.auxio.music.NoMusicException @@ -173,7 +173,7 @@ class HomeFragment : // --- VIEWMODEL SETUP --- collect(homeModel.recreateTabs.flow, ::handleRecreate) - collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) + collectImmediately(homeModel.currentTabType, ::updateCurrentTab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -245,7 +245,7 @@ class HomeFragment : item.isChecked = true homeModel.setSortForCurrentTab( homeModel - .getSortForTab(homeModel.currentTabMode.value) + .getSortForTab(homeModel.currentTabType.value) .withDirection(Sort.Direction.ASCENDING)) true } @@ -254,7 +254,7 @@ class HomeFragment : item.isChecked = true homeModel.setSortForCurrentTab( homeModel - .getSortForTab(homeModel.currentTabMode.value) + .getSortForTab(homeModel.currentTabType.value) .withDirection(Sort.Direction.DESCENDING)) true } @@ -265,7 +265,7 @@ class HomeFragment : logD("Updating sort mode") item.isChecked = true homeModel.setSortForCurrentTab( - homeModel.getSortForTab(homeModel.currentTabMode.value).withMode(newMode)) + homeModel.getSortForTab(homeModel.currentTabType.value).withMode(newMode)) true } else { logW("Unexpected menu item selected") @@ -277,10 +277,10 @@ class HomeFragment : private fun setupPager(binding: FragmentHomeBinding) { binding.homePager.adapter = - HomePagerAdapter(homeModel.currentTabModes, childFragmentManager, viewLifecycleOwner) + HomePagerAdapter(homeModel.currentTabTypes, childFragmentManager, viewLifecycleOwner) val toolbarParams = binding.homeToolbar.layoutParams as AppBarLayout.LayoutParams - if (homeModel.currentTabModes.size == 1) { + if (homeModel.currentTabTypes.size == 1) { // A single tab makes the tab layout redundant, hide it and disable the collapsing // behavior. logD("Single tab shown, disabling TabLayout") @@ -298,22 +298,22 @@ class HomeFragment : TabLayoutMediator( binding.homeTabs, binding.homePager, - AdaptiveTabStrategy(requireContext(), homeModel.currentTabModes)) + AdaptiveTabStrategy(requireContext(), homeModel.currentTabTypes)) .attach() } - private fun updateCurrentTab(tabMode: MusicMode) { + private fun updateCurrentTab(tabType: MusicType) { val binding = requireBinding() // Update the sort options to align with those allowed by the tab val isVisible: (Int) -> Boolean = - when (tabMode) { + when (tabType) { // Disallow sorting by count for songs - MusicMode.SONGS -> { + MusicType.SONGS -> { logD("Using song-specific menu options") ({ id -> id != R.id.option_sort_count }) } // Disallow sorting by album for albums - MusicMode.ALBUMS -> { + MusicType.ALBUMS -> { logD("Using album-specific menu options") ({ id -> id != R.id.option_sort_album }) } @@ -332,7 +332,7 @@ class HomeFragment : val sortMenu = unlikelyToBeNull(binding.homeNormalToolbar.menu.findItem(R.id.submenu_sorting).subMenu) - val toHighlight = homeModel.getSortForTab(tabMode) + val toHighlight = homeModel.getSortForTab(tabType) for (option in sortMenu) { val isCurrentMode = option.itemId == toHighlight.mode.itemId @@ -364,15 +364,15 @@ class HomeFragment : // scrolling state. This prevents the lift state from being confused as one // goes between different tabs. binding.homeAppbar.liftOnScrollTargetViewId = - when (tabMode) { - MusicMode.SONGS -> R.id.home_song_recycler - MusicMode.ALBUMS -> R.id.home_album_recycler - MusicMode.ARTISTS -> R.id.home_artist_recycler - MusicMode.GENRES -> R.id.home_genre_recycler - MusicMode.PLAYLISTS -> R.id.home_playlist_recycler + when (tabType) { + MusicType.SONGS -> R.id.home_song_recycler + MusicType.ALBUMS -> R.id.home_album_recycler + MusicType.ARTISTS -> R.id.home_artist_recycler + MusicType.GENRES -> R.id.home_genre_recycler + MusicType.PLAYLISTS -> R.id.home_playlist_recycler } - if (tabMode != MusicMode.PLAYLISTS) { + if (tabType != MusicType.PLAYLISTS) { logD("Flipping to shuffle button") binding.homeFab.flipTo(R.drawable.ic_shuffle_off_24, R.string.desc_shuffle_all) { playbackModel.shuffleAll() @@ -632,18 +632,18 @@ class HomeFragment : * [FragmentStateAdapter]. */ private class HomePagerAdapter( - private val tabs: List, + private val tabs: List, fragmentManager: FragmentManager, lifecycleOwner: LifecycleOwner ) : FragmentStateAdapter(fragmentManager, lifecycleOwner.lifecycle) { override fun getItemCount() = tabs.size override fun createFragment(position: Int): Fragment = when (tabs[position]) { - MusicMode.SONGS -> SongListFragment() - MusicMode.ALBUMS -> AlbumListFragment() - MusicMode.ARTISTS -> ArtistListFragment() - MusicMode.GENRES -> GenreListFragment() - MusicMode.PLAYLISTS -> PlaylistListFragment() + MusicType.SONGS -> SongListFragment() + MusicType.ALBUMS -> AlbumListFragment() + MusicType.ARTISTS -> ArtistListFragment() + MusicType.GENRES -> GenreListFragment() + MusicType.PLAYLISTS -> PlaylistListFragment() } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt index 4e468ec95..5fc218cfe 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt @@ -24,7 +24,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.home.tabs.Tab -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull @@ -75,9 +75,9 @@ class HomeSettingsImpl @Inject constructor(@ApplicationContext context: Context) logD("Old tabs: $oldTabs") // The playlist tab is now parsed, but it needs to be made visible. - val playlistIndex = oldTabs.indexOfFirst { it.mode == MusicMode.PLAYLISTS } + val playlistIndex = oldTabs.indexOfFirst { it.type == MusicType.PLAYLISTS } check(playlistIndex > -1) // This should exist, otherwise we are in big trouble - oldTabs[playlistIndex] = Tab.Visible(MusicMode.PLAYLISTS) + oldTabs[playlistIndex] = Tab.Visible(MusicType.PLAYLISTS) logD("New tabs: $oldTabs") sharedPreferences.edit { diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 538dcb541..eb5efc322 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -29,9 +29,9 @@ import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicSettings +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaySong @@ -108,15 +108,15 @@ constructor( get() = playbackSettings.playInListWith /** - * A list of [MusicMode] corresponding to the current [Tab] configuration, excluding invisible + * A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible * [Tab]s. */ - var currentTabModes = makeTabModes() + var currentTabTypes = makeTabTypes() private set - private val _currentTabMode = MutableStateFlow(currentTabModes[0]) - /** The [MusicMode] of the currently shown [Tab]. */ - val currentTabMode: StateFlow = _currentTabMode + private val _currentTabType = MutableStateFlow(currentTabTypes[0]) + /** The [MusicType] of the currently shown [Tab]. */ + val currentTabType: StateFlow = _currentTabType private val _shouldRecreate = MutableEvent() /** @@ -177,8 +177,8 @@ constructor( override fun onTabsChanged() { // Tabs changed, update the current tabs and set up a re-create event. - currentTabModes = makeTabModes() - logD("Updating tabs: ${currentTabMode.value}") + currentTabTypes = makeTabTypes() + logD("Updating tabs: ${currentTabType.value}") _shouldRecreate.put(Unit) } @@ -192,16 +192,16 @@ constructor( /** * Get the preferred [Sort] for a given [Tab]. * - * @param tabMode The [MusicMode] of the [Tab] desired. + * @param tabType The [MusicType] of the [Tab] desired. * @return The [Sort] preferred for that [Tab] */ - fun getSortForTab(tabMode: MusicMode) = - when (tabMode) { - MusicMode.SONGS -> musicSettings.songSort - MusicMode.ALBUMS -> musicSettings.albumSort - MusicMode.ARTISTS -> musicSettings.artistSort - MusicMode.GENRES -> musicSettings.genreSort - MusicMode.PLAYLISTS -> musicSettings.playlistSort + fun getSortForTab(tabType: MusicType) = + when (tabType) { + MusicType.SONGS -> musicSettings.songSort + MusicType.ALBUMS -> musicSettings.albumSort + MusicType.ARTISTS -> musicSettings.artistSort + MusicType.GENRES -> musicSettings.genreSort + MusicType.PLAYLISTS -> musicSettings.playlistSort } /** @@ -211,33 +211,33 @@ constructor( */ fun setSortForCurrentTab(sort: Sort) { // Can simply re-sort the current list of items without having to access the library. - when (val mode = _currentTabMode.value) { - MusicMode.SONGS -> { - logD("Updating song [$mode] sort mode to $sort") + when (val type = _currentTabType.value) { + MusicType.SONGS -> { + logD("Updating song [$type] sort mode to $sort") musicSettings.songSort = sort _songsInstructions.put(UpdateInstructions.Replace(0)) _songsList.value = sort.songs(_songsList.value) } - MusicMode.ALBUMS -> { - logD("Updating album [$mode] sort mode to $sort") + MusicType.ALBUMS -> { + logD("Updating album [$type] sort mode to $sort") musicSettings.albumSort = sort _albumsInstructions.put(UpdateInstructions.Replace(0)) _albumsLists.value = sort.albums(_albumsLists.value) } - MusicMode.ARTISTS -> { - logD("Updating artist [$mode] sort mode to $sort") + MusicType.ARTISTS -> { + logD("Updating artist [$type] sort mode to $sort") musicSettings.artistSort = sort _artistsInstructions.put(UpdateInstructions.Replace(0)) _artistsList.value = sort.artists(_artistsList.value) } - MusicMode.GENRES -> { - logD("Updating genre [$mode] sort mode to $sort") + MusicType.GENRES -> { + logD("Updating genre [$type] sort mode to $sort") musicSettings.genreSort = sort _genresInstructions.put(UpdateInstructions.Replace(0)) _genresList.value = sort.genres(_genresList.value) } - MusicMode.PLAYLISTS -> { - logD("Updating playlist [$mode] sort mode to $sort") + MusicType.PLAYLISTS -> { + logD("Updating playlist [$type] sort mode to $sort") musicSettings.playlistSort = sort _playlistsInstructions.put(UpdateInstructions.Replace(0)) _playlistsList.value = sort.playlists(_playlistsList.value) @@ -246,13 +246,13 @@ constructor( } /** - * Update [currentTabMode] to reflect a new ViewPager2 position + * Update [currentTabType] to reflect a new ViewPager2 position * * @param pagerPos The new position of the ViewPager2 instance. */ fun synchronizeTabPosition(pagerPos: Int) { - logD("Updating current tab to ${currentTabModes[pagerPos]}") - _currentTabMode.value = currentTabModes[pagerPos] + logD("Updating current tab to ${currentTabTypes[pagerPos]}") + _currentTabType.value = currentTabTypes[pagerPos] } /** @@ -266,11 +266,11 @@ constructor( } /** - * Create a list of [MusicMode]s representing a simpler version of the [Tab] configuration. + * Create a list of [MusicType]s representing a simpler version of the [Tab] configuration. * - * @return A list of the [MusicMode]s for each visible [Tab] in the configuration, ordered in + * @return A list of the [MusicType]s for each visible [Tab] in the configuration, ordered in * the same way as the configuration. */ - private fun makeTabModes() = - homeSettings.homeTabs.filterIsInstance().map { it.mode } + private fun makeTabTypes() = + homeSettings.homeTabs.filterIsInstance().map { it.type } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index 984551733..ce8fcfb20 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -39,8 +39,8 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -99,7 +99,7 @@ class AlbumListFragment : override fun getPopup(pos: Int): String? { val album = homeModel.albumsList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicMode.ALBUMS).mode) { + return when (homeModel.getSortForTab(MusicType.ALBUMS).mode) { // By Name -> Use Name is Sort.Mode.ByName -> album.name.thumb diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index ab9928003..8cda450e7 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -37,8 +37,8 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.ArtistViewHolder import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -94,7 +94,7 @@ class ArtistListFragment : override fun getPopup(pos: Int): String? { val artist = homeModel.artistsList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicMode.ARTISTS).mode) { + return when (homeModel.getSortForTab(MusicType.ARTISTS).mode) { // By Name -> Use Name is Sort.Mode.ByName -> artist.name.thumb diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index 2ecd73f90..bba02ff20 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -37,8 +37,8 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.GenreViewHolder import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -93,7 +93,7 @@ class GenreListFragment : override fun getPopup(pos: Int): String? { val genre = homeModel.genresList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicMode.GENRES).mode) { + return when (homeModel.getSortForTab(MusicType.GENRES).mode) { // By Name -> Use Name is Sort.Mode.ByName -> genre.name.thumb diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt index 299ceb39c..164b0da92 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt @@ -35,8 +35,8 @@ import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.PlaylistViewHolder import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song @@ -91,7 +91,7 @@ class PlaylistListFragment : override fun getPopup(pos: Int): String? { val playlist = homeModel.playlistsList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicMode.GENRES).mode) { + return when (homeModel.getSortForTab(MusicType.GENRES).mode) { // By Name -> Use Name is Sort.Mode.ByName -> playlist.name.thumb diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 74bd833ed..3b2782525 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -37,8 +37,8 @@ import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -98,7 +98,7 @@ class SongListFragment : // Change how we display the popup depending on the current sort mode. // Note: We don't use the more correct individual artist name here, as sorts are largely // based off the names of the parent objects and not the child objects. - return when (homeModel.getSortForTab(MusicMode.SONGS).mode) { + return when (homeModel.getSortForTab(MusicType.SONGS).mode) { // Name -> Use name is Sort.Mode.ByName -> song.name.thumb diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/AdaptiveTabStrategy.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/AdaptiveTabStrategy.kt index 36aed93bf..73170ef4c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/AdaptiveTabStrategy.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/AdaptiveTabStrategy.kt @@ -22,7 +22,7 @@ import android.content.Context import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.MusicType /** * A [TabLayoutMediator.TabConfigurationStrategy] that uses larger/smaller tab configurations @@ -32,7 +32,7 @@ import org.oxycblt.auxio.music.MusicMode * @param tabs Current tab configuration from settings * @author Alexander Capehart (OxygenCobalt) */ -class AdaptiveTabStrategy(context: Context, private val tabs: List) : +class AdaptiveTabStrategy(context: Context, private val tabs: List) : TabLayoutMediator.TabConfigurationStrategy { private val width = context.resources.configuration.smallestScreenWidthDp @@ -41,23 +41,23 @@ class AdaptiveTabStrategy(context: Context, private val tabs: List) : val string: Int when (tabs[position]) { - MusicMode.SONGS -> { + MusicType.SONGS -> { icon = R.drawable.ic_song_24 string = R.string.lbl_songs } - MusicMode.ALBUMS -> { + MusicType.ALBUMS -> { icon = R.drawable.ic_album_24 string = R.string.lbl_albums } - MusicMode.ARTISTS -> { + MusicType.ARTISTS -> { icon = R.drawable.ic_artist_24 string = R.string.lbl_artists } - MusicMode.GENRES -> { + MusicType.GENRES -> { icon = R.drawable.ic_genre_24 string = R.string.lbl_genres } - MusicMode.PLAYLISTS -> { + MusicType.PLAYLISTS -> { icon = R.drawable.ic_playlist_24 string = R.string.lbl_playlists } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt index 5cacd084b..aee964e45 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt @@ -18,30 +18,30 @@ package org.oxycblt.auxio.home.tabs -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logW /** * A representation of a library tab suitable for configuration. * - * @param mode The type of list in the home view this instance corresponds to. + * @param type The type of list in the home view this instance corresponds to. * @author Alexander Capehart (OxygenCobalt) */ -sealed class Tab(open val mode: MusicMode) { +sealed class Tab(open val type: MusicType) { /** * A visible tab. This will be visible in the home and tab configuration views. * - * @param mode The type of list in the home view this instance corresponds to. + * @param type The type of list in the home view this instance corresponds to. */ - data class Visible(override val mode: MusicMode) : Tab(mode) + data class Visible(override val type: MusicType) : Tab(type) /** * A visible tab. This will be visible in the tab configuration view, but not in the home view. * - * @param mode The type of list in the home view this instance corresponds to. + * @param type The type of list in the home view this instance corresponds to. */ - data class Invisible(override val mode: MusicMode) : Tab(mode) + data class Invisible(override val type: MusicType) : Tab(type) companion object { // Like other IO-bound datatypes in Auxio, tabs are stored in a binary format. However, tabs @@ -67,14 +67,14 @@ sealed class Tab(open val mode: MusicMode) { */ const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_1100 - /** Maps between the integer code in the tab sequence and it's [MusicMode]. */ + /** Maps between the integer code in the tab sequence and it's [MusicType]. */ private val MODE_TABLE = arrayOf( - MusicMode.SONGS, - MusicMode.ALBUMS, - MusicMode.ARTISTS, - MusicMode.GENRES, - MusicMode.PLAYLISTS) + MusicType.SONGS, + MusicType.ALBUMS, + MusicType.ARTISTS, + MusicType.GENRES, + MusicType.PLAYLISTS) /** * Convert an array of [Tab]s into it's integer representation. @@ -84,7 +84,7 @@ sealed class Tab(open val mode: MusicMode) { */ fun toIntCode(tabs: Array): Int { // Like when deserializing, make sure there are no duplicate tabs for whatever reason. - val distinct = tabs.distinctBy { it.mode } + val distinct = tabs.distinctBy { it.type } if (tabs.size != distinct.size) { logW( "Tab sequences should not have duplicates [old: ${tabs.size} new: ${distinct.size}]") @@ -95,8 +95,8 @@ sealed class Tab(open val mode: MusicMode) { for (tab in distinct) { val bin = when (tab) { - is Visible -> 1.shl(3) or MODE_TABLE.indexOf(tab.mode) - is Invisible -> MODE_TABLE.indexOf(tab.mode) + is Visible -> 1.shl(3) or MODE_TABLE.indexOf(tab.type) + is Invisible -> MODE_TABLE.indexOf(tab.type) } sequence = sequence or bin.shl(shift) @@ -131,7 +131,7 @@ sealed class Tab(open val mode: MusicMode) { } // Make sure there are no duplicate tabs - val distinct = tabs.distinctBy { it.mode } + val distinct = tabs.distinctBy { it.type } if (tabs.size != distinct.size) { logW( "Tab sequences should not have duplicates [old: ${tabs.size} new: ${distinct.size}]") diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt index 277c0c39b..aa71b89f1 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt @@ -26,7 +26,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemTabBinding import org.oxycblt.auxio.list.EditClickListListener import org.oxycblt.auxio.list.recycler.DialogRecyclerView -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.logD @@ -107,14 +107,14 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) : fun bind(tab: Tab, listener: EditClickListListener) { listener.bind(tab, this, dragHandle = binding.tabDragHandle) binding.tabCheckBox.apply { - // Update the CheckBox name to align with the mode + // Update the CheckBox name to align with the type setText( - when (tab.mode) { - MusicMode.SONGS -> R.string.lbl_songs - MusicMode.ALBUMS -> R.string.lbl_albums - MusicMode.ARTISTS -> R.string.lbl_artists - MusicMode.GENRES -> R.string.lbl_genres - MusicMode.PLAYLISTS -> R.string.lbl_playlists + when (tab.type) { + MusicType.SONGS -> R.string.lbl_songs + MusicType.ALBUMS -> R.string.lbl_albums + MusicType.ARTISTS -> R.string.lbl_artists + MusicType.GENRES -> R.string.lbl_genres + MusicType.PLAYLISTS -> R.string.lbl_playlists }) // Unlike in other adapters, we update the checked state alongside diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index fd6b36ae7..57bc73c15 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -91,13 +91,13 @@ class TabCustomizeDialog : override fun onClick(item: Tab, viewHolder: RecyclerView.ViewHolder) { // We will need the exact index of the tab to update on in order to // notify the adapter of the change. - val index = tabAdapter.tabs.indexOfFirst { it.mode == item.mode } + val index = tabAdapter.tabs.indexOfFirst { it.type == item.type } val old = tabAdapter.tabs[index] val new = when (old) { // Invert the visibility of the tab - is Tab.Visible -> Tab.Invisible(old.mode) - is Tab.Invisible -> Tab.Visible(old.mode) + is Tab.Visible -> Tab.Invisible(old.type) + is Tab.Invisible -> Tab.Visible(old.type) } logD("Flipping tab visibility [from: $old to: $new]") tabAdapter.setTab(index, new) diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index fc8a51390..766ea462c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -80,23 +80,23 @@ sealed interface Music : Item { class UID private constructor( private val format: Format, - private val mode: MusicMode, + private val type: MusicType, private val uuid: UUID ) : Parcelable { // Cache the hashCode for HashMap efficiency. @IgnoredOnParcel private var hashCode = format.hashCode() init { - hashCode = 31 * hashCode + mode.hashCode() + hashCode = 31 * hashCode + type.hashCode() hashCode = 31 * hashCode + uuid.hashCode() } override fun hashCode() = hashCode override fun equals(other: Any?) = - other is UID && format == other.format && mode == other.mode && uuid == other.uuid + other is UID && format == other.format && type == other.type && uuid == other.uuid - override fun toString() = "${format.namespace}:${mode.intCode.toString(16)}-$uuid" + override fun toString() = "${format.namespace}:${type.intCode.toString(16)}-$uuid" /** * Internal marker of [Music.UID] format type. @@ -124,23 +124,23 @@ sealed interface Music : Item { * Creates an Auxio-style [UID] of random composition. Used if there is no * non-subjective, unlikely-to-change metadata of the music. * - * @param mode The analogous [MusicMode] of the item that created this [UID]. + * @param type The analogous [MusicType] of the item that created this [UID]. */ - fun auxio(mode: MusicMode): UID { - return UID(Format.AUXIO, mode, UUID.randomUUID()) + fun auxio(type: MusicType): UID { + return UID(Format.AUXIO, type, UUID.randomUUID()) } /** * Creates an Auxio-style [UID] with a [UUID] composed of a hash of the non-subjective, * unlikely-to-change metadata of the music. * - * @param mode The analogous [MusicMode] of the item that created this [UID]. + * @param type The analogous [MusicType] of the item that created this [UID]. * @param updates Block to update the [MessageDigest] hash with the metadata of the * item. Make sure the metadata hashed semantically aligns with the format * specification. * @return A new auxio-style [UID]. */ - fun auxio(mode: MusicMode, updates: MessageDigest.() -> Unit): UID { + fun auxio(type: MusicType, updates: MessageDigest.() -> Unit): UID { val digest = MessageDigest.getInstance("SHA-256").run { updates() @@ -170,19 +170,19 @@ sealed interface Music : Item { .or(digest[13].toLong().and(0xFF).shl(16)) .or(digest[14].toLong().and(0xFF).shl(8)) .or(digest[15].toLong().and(0xFF))) - return UID(Format.AUXIO, mode, uuid) + return UID(Format.AUXIO, type, uuid) } /** * Creates a MusicBrainz-style [UID] with a [UUID] derived from the MusicBrainz ID * extracted from a file. * - * @param mode The analogous [MusicMode] of the item that created this [UID]. + * @param type The analogous [MusicType] of the item that created this [UID]. * @param mbid The analogous MusicBrainz ID for this item that was extracted from a * file. * @return A new MusicBrainz-style [UID]. */ - fun musicBrainz(mode: MusicMode, mbid: UUID) = UID(Format.MUSICBRAINZ, mode, mbid) + fun musicBrainz(type: MusicType, mbid: UUID) = UID(Format.MUSICBRAINZ, type, mbid) /** * Convert a [UID]'s string representation back into a concrete [UID] instance. @@ -210,10 +210,10 @@ sealed interface Music : Item { return null } - val mode = - MusicMode.fromIntCode(ids[0].toIntOrNull(16) ?: return null) ?: return null + val type = + MusicType.fromIntCode(ids[0].toIntOrNull(16) ?: return null) ?: return null val uuid = ids[1].toUuidOrNull() ?: return null - return UID(format, mode, uuid) + return UID(format, type, uuid) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicType.kt similarity index 73% rename from app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt rename to app/src/main/java/org/oxycblt/auxio/music/MusicType.kt index 03ec48dae..19f535af1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicType.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2022 Auxio Project - * MusicMode.kt is part of Auxio. + * MusicType.kt is part of Auxio. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,20 +21,20 @@ package org.oxycblt.auxio.music import org.oxycblt.auxio.IntegerTable /** - * Represents a data configuration corresponding to a specific type of [Music], + * General configuration enum to control what kind of music is being worked with. * * @author Alexander Capehart (OxygenCobalt) */ -enum class MusicMode { - /** Configure with respect to [Song] instances. */ +enum class MusicType { + /** @see Song */ SONGS, - /** Configure with respect to [Album] instances. */ + /** @see Album */ ALBUMS, - /** Configure with respect to [Artist] instances. */ + /** @see Artist */ ARTISTS, - /** Configure with respect to [Genre] instances. */ + /** @see Genre */ GENRES, - /** Configure with respect to [Playlist] instances. */ + /** @see Playlist */ PLAYLISTS; /** @@ -54,11 +54,11 @@ enum class MusicMode { companion object { /** - * Convert a [MusicMode] integer representation into an instance. + * Convert a [MusicType] integer representation into an instance. * - * @param intCode An integer representation of a [MusicMode] - * @return The corresponding [MusicMode], or null if the [MusicMode] is invalid. - * @see MusicMode.intCode + * @param intCode An integer representation of a [MusicType] + * @return The corresponding [MusicType], or null if the [MusicType] is invalid. + * @see MusicType.intCode */ fun fromIntCode(intCode: Int) = when (intCode) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt index 2f12b7290..e568b8f16 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt @@ -25,8 +25,8 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicSettings +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.fs.MimeType import org.oxycblt.auxio.music.fs.Path @@ -54,8 +54,8 @@ import org.oxycblt.auxio.util.update class SongImpl(private val rawSong: RawSong, musicSettings: MusicSettings) : Song { override val uid = // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. - rawSong.musicBrainzId?.toUuidOrNull()?.let { Music.UID.musicBrainz(MusicMode.SONGS, it) } - ?: Music.UID.auxio(MusicMode.SONGS) { + rawSong.musicBrainzId?.toUuidOrNull()?.let { Music.UID.musicBrainz(MusicType.SONGS, it) } + ?: Music.UID.auxio(MusicType.SONGS) { // Song UIDs are based on the raw data without parsing so that they remain // consistent across music setting changes. Parents are not held up to the // same standard since grouping is already inherently linked to settings. @@ -251,8 +251,8 @@ class AlbumImpl( override val uid = // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. - rawAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicMode.ALBUMS, it) } - ?: Music.UID.auxio(MusicMode.ALBUMS) { + rawAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ALBUMS, it) } + ?: Music.UID.auxio(MusicType.ALBUMS) { // Hash based on only names despite the presence of a date to increase stability. // I don't know if there is any situation where an artist will have two albums with // the exact same name, but if there is, I would love to know. @@ -366,8 +366,8 @@ class ArtistImpl(grouping: Grouping, musicSettings: MusicSetti override val uid = // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. - rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicMode.ARTISTS, it) } - ?: Music.UID.auxio(MusicMode.ARTISTS) { update(rawArtist.name) } + rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) } + ?: Music.UID.auxio(MusicType.ARTISTS) { update(rawArtist.name) } override val name = rawArtist.name?.let { Name.Known.from(it, rawArtist.sortName, musicSettings) } ?: Name.Unknown(R.string.def_artist) @@ -461,7 +461,7 @@ class ArtistImpl(grouping: Grouping, musicSettings: MusicSetti class GenreImpl(grouping: Grouping, musicSettings: MusicSettings) : Genre { private val rawGenre = grouping.raw.inner - override val uid = Music.UID.auxio(MusicMode.GENRES) { update(rawGenre.name) } + override val uid = Music.UID.auxio(MusicType.GENRES) { update(rawGenre.name) } override val name = rawGenre.name?.let { Name.Known.from(it, rawGenre.name, musicSettings) } ?: Name.Unknown(R.string.def_genre) diff --git a/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt index 9ad14f411..19cf12401 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/user/PlaylistImpl.kt @@ -19,8 +19,8 @@ package org.oxycblt.auxio.music.user import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicSettings +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.device.DeviceLibrary @@ -78,7 +78,7 @@ private constructor( */ fun from(name: String, songs: List, musicSettings: MusicSettings) = PlaylistImpl( - Music.UID.auxio(MusicMode.PLAYLISTS), + Music.UID.auxio(MusicType.PLAYLISTS), Name.Known.from(name, null, musicSettings), songs) diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt index 16edab48b..71fd94583 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt @@ -23,7 +23,7 @@ import androidx.core.content.edit import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.settings.Settings /** @@ -32,19 +32,21 @@ import org.oxycblt.auxio.settings.Settings * @author Alexander Capehart (OxygenCobalt) */ interface SearchSettings : Settings { - /** The type of Music the search view is currently filtering to. */ - var searchFilterMode: MusicMode? + /** The type of Music the search view is should filter to. */ + var filterTo: MusicType? } class SearchSettingsImpl @Inject constructor(@ApplicationContext context: Context) : Settings.Impl(context), SearchSettings { - override var searchFilterMode: MusicMode? + override var filterTo: MusicType? get() = - MusicMode.fromIntCode( - sharedPreferences.getInt(getString(R.string.set_key_search_filter), Int.MIN_VALUE)) + MusicType.fromIntCode( + sharedPreferences.getInt( + getString(R.string.set_key_search_filter_to), Int.MIN_VALUE)) set(value) { sharedPreferences.edit { - putInt(getString(R.string.set_key_search_filter), value?.intCode ?: Int.MIN_VALUE) + putInt( + getString(R.string.set_key_search_filter_to), value?.intCode ?: Int.MIN_VALUE) apply() } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 9bf784010..3b8c9d004 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -33,8 +33,8 @@ import org.oxycblt.auxio.list.BasicHeader import org.oxycblt.auxio.list.Divider import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Sort -import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.device.DeviceLibrary import org.oxycblt.auxio.music.user.UserLibrary @@ -117,12 +117,12 @@ constructor( userLibrary: UserLibrary, query: String ): List { - val filterMode = searchSettings.searchFilterMode + val filter = searchSettings.filterTo val items = - if (filterMode == null) { - // A nulled filter mode means to not filter anything. - logD("No filter mode specified, using entire library") + if (filter == null) { + // A nulled filter type means to not filter anything. + logD("No filter specified, using entire library") SearchEngine.Items( deviceLibrary.songs, deviceLibrary.albums, @@ -130,14 +130,13 @@ constructor( deviceLibrary.genres, userLibrary.playlists) } else { - logD("Filter mode specified, filtering library") + logD("Filter specified, reducing library") SearchEngine.Items( - songs = if (filterMode == MusicMode.SONGS) deviceLibrary.songs else null, - albums = if (filterMode == MusicMode.ALBUMS) deviceLibrary.albums else null, - artists = if (filterMode == MusicMode.ARTISTS) deviceLibrary.artists else null, - genres = if (filterMode == MusicMode.GENRES) deviceLibrary.genres else null, - playlists = - if (filterMode == MusicMode.PLAYLISTS) userLibrary.playlists else null) + songs = if (filter == MusicType.SONGS) deviceLibrary.songs else null, + albums = if (filter == MusicType.ALBUMS) deviceLibrary.albums else null, + artists = if (filter == MusicType.ARTISTS) deviceLibrary.artists else null, + genres = if (filter == MusicType.GENRES) deviceLibrary.genres else null, + playlists = if (filter == MusicType.PLAYLISTS) userLibrary.playlists else null) } val results = searchEngine.search(items, query) @@ -199,35 +198,35 @@ constructor( */ @IdRes fun getFilterOptionId() = - when (searchSettings.searchFilterMode) { - MusicMode.SONGS -> R.id.option_filter_songs - MusicMode.ALBUMS -> R.id.option_filter_albums - MusicMode.ARTISTS -> R.id.option_filter_artists - MusicMode.GENRES -> R.id.option_filter_genres - MusicMode.PLAYLISTS -> R.id.option_filter_playlists + when (searchSettings.filterTo) { + MusicType.SONGS -> R.id.option_filter_songs + MusicType.ALBUMS -> R.id.option_filter_albums + MusicType.ARTISTS -> R.id.option_filter_artists + MusicType.GENRES -> R.id.option_filter_genres + MusicType.PLAYLISTS -> R.id.option_filter_playlists // Null maps to filtering nothing. null -> R.id.option_filter_all } /** - * Update the filter mode with the newly-selected filter option. + * Update the filter type with the newly-selected filter option. * * @return A menu item ID of the new filtering option selected. */ fun setFilterOptionId(@IdRes id: Int) { - val newFilterMode = + val newFilter = when (id) { - R.id.option_filter_songs -> MusicMode.SONGS - R.id.option_filter_albums -> MusicMode.ALBUMS - R.id.option_filter_artists -> MusicMode.ARTISTS - R.id.option_filter_genres -> MusicMode.GENRES - R.id.option_filter_playlists -> MusicMode.PLAYLISTS + R.id.option_filter_songs -> MusicType.SONGS + R.id.option_filter_albums -> MusicType.ALBUMS + R.id.option_filter_artists -> MusicType.ARTISTS + R.id.option_filter_genres -> MusicType.GENRES + R.id.option_filter_playlists -> MusicType.PLAYLISTS // Null maps to filtering nothing. R.id.option_filter_all -> null else -> error("Invalid option ID provided") } - logD("Updating filter mode to $newFilterMode") - searchSettings.searchFilterMode = newFilterMode + logD("Updating filter type to $newFilter") + searchSettings.filterTo = newFilter search(lastQuery) } diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 87004a171..dbc493109 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -42,7 +42,7 @@ auxio_bar_action auxio_notif_action - KEY_SEARCH_FILTER + KEY_SEARCH_FILTER auxio_songs_sort auxio_albums_sort diff --git a/app/src/test/java/org/oxycblt/auxio/music/MusicModeTest.kt b/app/src/test/java/org/oxycblt/auxio/music/MusicModeTest.kt index 1cd68bb51..c11985970 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/MusicModeTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/MusicModeTest.kt @@ -24,9 +24,9 @@ import org.junit.Test class MusicModeTest { @Test fun intCode() { - assertEquals(MusicMode.SONGS, MusicMode.fromIntCode(MusicMode.SONGS.intCode)) - assertEquals(MusicMode.ALBUMS, MusicMode.fromIntCode(MusicMode.ALBUMS.intCode)) - assertEquals(MusicMode.ARTISTS, MusicMode.fromIntCode(MusicMode.ARTISTS.intCode)) - assertEquals(MusicMode.GENRES, MusicMode.fromIntCode(MusicMode.GENRES.intCode)) + assertEquals(MusicType.SONGS, MusicType.fromIntCode(MusicType.SONGS.intCode)) + assertEquals(MusicType.ALBUMS, MusicType.fromIntCode(MusicType.ALBUMS.intCode)) + assertEquals(MusicType.ARTISTS, MusicType.fromIntCode(MusicType.ARTISTS.intCode)) + assertEquals(MusicType.GENRES, MusicType.fromIntCode(MusicType.GENRES.intCode)) } } From ecc84dd8c81a44576d56ad60cbbdef0b4bc3d1c1 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 11 Jul 2023 14:44:30 -0600 Subject: [PATCH 06/28] playback: fix parent play mode not being applied Caused by a trivial problem with key use. --- .../main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt index 481db77c5..82a5c656b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt @@ -82,8 +82,9 @@ class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Cont override val inParentPlaybackMode: PlaySong? get() = PlaySong.fromIntCode( - sharedPreferences.getInt( - getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE), + sharedPreferences + .getInt(getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE) + .also { logD(it) }, null) override val barAction: ActionMode From 32a0d97e5d8e1f794856e28dbd44cdcd5453f42a Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 11 Jul 2023 14:45:08 -0600 Subject: [PATCH 07/28] list: add ability to play/shuffle songs in menu Add play and shuffle options for all song menus. These will override the shuffle state, unlike other song play interactions. This required a good bit of refactoring to menu, some of which might be ported to other commands in future changes. --- .../java/org/oxycblt/auxio/IntegerTable.kt | 14 +- .../auxio/detail/AlbumDetailFragment.kt | 5 +- .../auxio/detail/ArtistDetailFragment.kt | 9 +- .../auxio/detail/GenreDetailFragment.kt | 8 +- .../auxio/detail/PlaylistDetailFragment.kt | 5 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 14 +- .../auxio/home/list/SongListFragment.kt | 2 +- .../org/oxycblt/auxio/list/ListViewModel.kt | 71 ++++++- .../auxio/list/menu/MenuDialogFragment.kt | 50 +++-- .../auxio/list/menu/MenuDialogFragmentImpl.kt | 194 +++++++++--------- .../oxycblt/auxio/list/menu/MenuViewModel.kt | 62 ++++-- .../auxio/music/device/DeviceLibrary.kt | 6 +- .../org/oxycblt/auxio/playback/PlaySong.kt | 63 +++++- .../auxio/playback/PlaybackSettings.kt | 9 +- .../auxio/playback/PlaybackViewModel.kt | 14 +- .../oxycblt/auxio/search/SearchFragment.kt | 17 +- app/src/main/res/menu/item_album_song.xml | 16 +- app/src/main/res/menu/item_artist_song.xml | 16 +- app/src/main/res/menu/item_playlist_song.xml | 16 +- app/src/main/res/menu/item_song.xml | 8 + app/src/main/res/navigation/main.xml | 35 +--- app/src/main/res/values/settings.xml | 19 +- 22 files changed, 373 insertions(+), 280 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index 68240c781..54d59eb50 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -121,16 +121,16 @@ object IntegerTable { const val COVER_MODE_MEDIA_STORE = 0xA11D /** CoverMode.Quality */ const val COVER_MODE_QUALITY = 0xA11E - /** PlaySong.ByItself */ - const val PLAY_SONG_BY_ITSELF = 0xA11F /** PlaySong.FromAll */ - const val PLAY_SONG_FROM_ALL = 0xA120 + const val PLAY_SONG_FROM_ALL = 0xA11F /** PlaySong.FromAlbum */ - const val PLAY_SONG_FROM_ALBUM = 0xA121 + const val PLAY_SONG_FROM_ALBUM = 0xA120 /** PlaySong.FromArtist */ - const val PLAY_SONG_FROM_ARTIST = 0xA122 + const val PLAY_SONG_FROM_ARTIST = 0xA121 /** PlaySong.FromGenre */ - const val PLAY_SONG_FROM_GENRE = 0xA123 + const val PLAY_SONG_FROM_GENRE = 0xA122 /** PlaySong.FromPlaylist */ - const val PLAY_SONG_FROM_PLAYLIST = 0xA124 + const val PLAY_SONG_FROM_PLAYLIST = 0xA123 + /** PlaySong.ByItself */ + const val PLAY_SONG_BY_ITSELF = 0xA124 } 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 61ac27fef..9fb0b25ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -183,7 +183,7 @@ class AlbumDetailFragment : } override fun onOpenMenu(item: Song, anchor: View) { - listModel.openMenu(R.menu.item_album_song, item) + listModel.openMenu(R.menu.item_album_song, item, detailModel.playInAlbumWith) } override fun onPlay() { @@ -302,8 +302,7 @@ class AlbumDetailFragment : if (menu == null) return val directions = when (menu) { - is Menu.ForSong -> - AlbumDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) + is Menu.ForSong -> AlbumDetailFragmentDirections.openSongMenu(menu.parcel) is Menu.ForAlbum, is Menu.ForArtist, is Menu.ForGenre, 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 1673da9de..9997496b6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -186,7 +186,8 @@ class ArtistDetailFragment : override fun onOpenMenu(item: Music, anchor: View) { when (item) { - is Song -> listModel.openMenu(R.menu.item_artist_song, item) + is Song -> + listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith) is Album -> listModel.openMenu(R.menu.item_artist_album, item) else -> error("Unexpected datatype: ${item::class.simpleName}") } @@ -306,10 +307,8 @@ class ArtistDetailFragment : if (menu == null) return val directions = when (menu) { - is Menu.ForSong -> - ArtistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) - is Menu.ForAlbum -> - ArtistDetailFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid) + is Menu.ForSong -> ArtistDetailFragmentDirections.openSongMenu(menu.parcel) + is Menu.ForAlbum -> ArtistDetailFragmentDirections.openAlbumMenu(menu.parcel) is Menu.ForArtist, is Menu.ForGenre, is Menu.ForPlaylist -> error("Unexpected menu $menu") 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 4a62f16a8..dfb9adc29 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -185,7 +185,7 @@ class GenreDetailFragment : override fun onOpenMenu(item: Music, anchor: View) { when (item) { is Artist -> listModel.openMenu(R.menu.item_parent, item) - is Song -> listModel.openMenu(R.menu.item_song, item) + is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith) else -> error("Unexpected datatype: ${item::class.simpleName}") } } @@ -294,10 +294,8 @@ class GenreDetailFragment : if (menu == null) return val directions = when (menu) { - is Menu.ForSong -> - GenreDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) - is Menu.ForArtist -> - GenreDetailFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid) + is Menu.ForSong -> GenreDetailFragmentDirections.openSongMenu(menu.parcel) + is Menu.ForArtist -> GenreDetailFragmentDirections.openArtistMenu(menu.parcel) is Menu.ForAlbum, is Menu.ForGenre, is Menu.ForPlaylist -> error("Unexpected menu $menu") diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt index 5365e9fba..4f25b96f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -237,7 +237,7 @@ class PlaylistDetailFragment : } override fun onOpenMenu(item: Song, anchor: View) { - listModel.openMenu(R.menu.item_playlist_song, item) + listModel.openMenu(R.menu.item_playlist_song, item, detailModel.playInPlaylistWith) } override fun onPlay() { @@ -344,8 +344,7 @@ class PlaylistDetailFragment : if (menu == null) return val directions = when (menu) { - is Menu.ForSong -> - PlaylistDetailFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) + is Menu.ForSong -> PlaylistDetailFragmentDirections.openSongMenu(menu.parcel) is Menu.ForArtist, is Menu.ForAlbum, is Menu.ForGenre, diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 12991e916..73a122006 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -577,15 +577,11 @@ class HomeFragment : if (menu == null) return val directions = when (menu) { - is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) - is Menu.ForAlbum -> - HomeFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid) - is Menu.ForArtist -> - HomeFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid) - is Menu.ForGenre -> - HomeFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid) - is Menu.ForPlaylist -> - HomeFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid) + is Menu.ForSong -> HomeFragmentDirections.openSongMenu(menu.parcel) + is Menu.ForAlbum -> HomeFragmentDirections.openAlbumMenu(menu.parcel) + is Menu.ForArtist -> HomeFragmentDirections.openArtistMenu(menu.parcel) + is Menu.ForGenre -> HomeFragmentDirections.openGenreMenu(menu.parcel) + is Menu.ForPlaylist -> HomeFragmentDirections.openPlaylistMenu(menu.parcel) } findNavController().navigateSafe(directions) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 3b2782525..26b9282ea 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -141,7 +141,7 @@ class SongListFragment : } override fun onOpenMenu(item: Song, anchor: View) { - listModel.openMenu(R.menu.item_song, item) + listModel.openMenu(R.menu.item_song, item, homeModel.playWith) } private fun updateSongs(songs: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt index 217865df7..a32b8a0f5 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/ListViewModel.kt @@ -18,12 +18,14 @@ package org.oxycblt.auxio.list +import android.os.Parcelable import androidx.annotation.MenuRes import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.parcelize.Parcelize import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -33,6 +35,7 @@ import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.PlaySong import org.oxycblt.auxio.util.Event import org.oxycblt.auxio.util.MutableEvent import org.oxycblt.auxio.util.logD @@ -146,10 +149,12 @@ constructor( * * @param menuRes The resource of the menu to use. * @param song The [Song] to show. + * @param playWith A [PlaySong] command to give context to what "Play" and "Shuffle" actions + * should do. */ - fun openMenu(@MenuRes menuRes: Int, song: Song) { + fun openMenu(@MenuRes menuRes: Int, song: Song, playWith: PlaySong) { logD("Opening menu for $song") - openImpl(Menu.ForSong(menuRes, song)) + openImpl(Menu.ForSong(menuRes, song, playWith)) } /** @@ -216,19 +221,63 @@ constructor( * @author Alexander Capehart (OxygenCobalt) */ sealed interface Menu { - /** The android resource ID of the menu options to display in the dialog. */ - val menuRes: Int - /** The [Music] that the menu should act on. */ - val music: Music + /** The menu resource to inflate in the menu dialog. */ + @get:MenuRes val res: Int + /** A [Parcel] version of this instance that can be used as a navigation argument. */ + val parcel: Parcel + sealed interface Parcel : Parcelable /** Navigate to a [Song] menu dialog. */ - class ForSong(@MenuRes override val menuRes: Int, override val music: Song) : Menu + class ForSong(@MenuRes override val res: Int, val song: Song, val playWith: PlaySong) : Menu { + override val parcel: Parcel + get() { + val playWithUid = + when (playWith) { + is PlaySong.FromArtist -> playWith.which?.uid + is PlaySong.FromGenre -> playWith.which?.uid + is PlaySong.FromPlaylist -> playWith.which.uid + is PlaySong.FromAll, + is PlaySong.FromAlbum, + is PlaySong.ByItself -> null + } + + return Parcel(res, song.uid, playWith.intCode, playWithUid) + } + + @Parcelize + data class Parcel( + val res: Int, + val songUid: Music.UID, + val playWithCode: Int, + val playWithUid: Music.UID? + ) : Menu.Parcel + } + /** Navigate to a [Album] menu dialog. */ - class ForAlbum(@MenuRes override val menuRes: Int, override val music: Album) : Menu + class ForAlbum(@MenuRes override val res: Int, val album: Album) : Menu { + override val parcel + get() = Parcel(res, album.uid) + @Parcelize data class Parcel(val res: Int, val albumUid: Music.UID) : Menu.Parcel + } + /** Navigate to a [Artist] menu dialog. */ - class ForArtist(@MenuRes override val menuRes: Int, override val music: Artist) : Menu + class ForArtist(@MenuRes override val res: Int, val artist: Artist) : Menu { + override val parcel + get() = Parcel(res, artist.uid) + @Parcelize data class Parcel(val res: Int, val artistUid: Music.UID) : Menu.Parcel + } + /** Navigate to a [Genre] menu dialog. */ - class ForGenre(@MenuRes override val menuRes: Int, override val music: Genre) : Menu + class ForGenre(@MenuRes override val res: Int, val genre: Genre) : Menu { + override val parcel + get() = Parcel(res, genre.uid) + @Parcelize data class Parcel(val res: Int, val genreUid: Music.UID) : Menu.Parcel + } + /** Navigate to a [Playlist] menu dialog. */ - class ForPlaylist(@MenuRes override val menuRes: Int, override val music: Playlist) : Menu + class ForPlaylist(@MenuRes override val res: Int, val playlist: Playlist) : Menu { + override val parcel + get() = Parcel(res, playlist.uid) + @Parcelize data class Parcel(val res: Int, val playlistUid: Music.UID) : Menu.Parcel + } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt index 305234449..a65f993bc 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt @@ -30,8 +30,8 @@ import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.ListViewModel +import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.adapter.UpdateInstructions -import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD @@ -44,40 +44,36 @@ import org.oxycblt.auxio.util.logD * * TODO: Extend the amount of music info shown in the dialog */ -abstract class MenuDialogFragment : +abstract class MenuDialogFragment : ViewBindingBottomSheetDialogFragment(), ClickableListListener { protected abstract val menuModel: MenuViewModel protected abstract val listModel: ListViewModel private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this) - /** The android resource ID of the menu options to display in the dialog. */ - abstract val menuRes: Int - - /** The [Music.UID] of the [T] to display menu options for. */ - abstract val uid: Music.UID + abstract val parcel: Menu.Parcel /** - * Get the options to disable in the context of the currently shown [T]. + * Get the options to disable in the context of the currently shown [M]. * - * @param music The currently-shown music [T]. + * @param menu The currently-shown menu [M]. */ - abstract fun getDisabledItemIds(music: T): Set + abstract fun getDisabledItemIds(menu: M): Set /** - * Update the displayed information about the currently shown [T]. + * Update the displayed information about the currently shown [M]. * * @param binding The [DialogMenuBinding] to bind information to. - * @param music The currently-shown music [T]. + * @param menu The currently-shown menu [M]. */ - abstract fun updateMusic(binding: DialogMenuBinding, music: T) + abstract fun updateMenu(binding: DialogMenuBinding, menu: M) /** * Forward the clicked [MenuItem] to it's corresponding handler in another module. * * @param item The [MenuItem] that was clicked. - * @param music The currently-shown music [T]. + * @param menu The currently-shown menu [M]. */ - abstract fun onClick(item: MenuItem, music: T) + abstract fun onClick(item: MenuItem, menu: M) override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater) @@ -94,8 +90,8 @@ abstract class MenuDialogFragment : // --- VIEWMODEL SETUP --- listModel.menu.consume() - menuModel.setMusic(uid) - collectImmediately(menuModel.currentMusic, this::updateMusic) + menuModel.setMenu(parcel) + collectImmediately(menuModel.currentMenu, this::updateMenu) } override fun onDestroyBinding(binding: DialogMenuBinding) { @@ -105,23 +101,25 @@ abstract class MenuDialogFragment : binding.menuOptionRecycler.adapter = null } - private fun updateMusic(music: Music?) { - if (music == null) { - logD("No music to show, navigating away") + private fun updateMenu(menu: Menu?) { + if (menu == null) { + logD("No menu to show, navigating away") findNavController().navigateUp() + return } - @Suppress("UNCHECKED_CAST") val castedMusic = music as T + @Suppress("UNCHECKED_CAST") val casted = menu as? M + check(casted != null) { "Unexpected menu instance ${menu::class.simpleName}" } - // We need to inflate the menu on every music update since it might have changed + // We need to inflate the menu on every menu update since it might have changed // what options are available (ex. if an artist with no songs has had new songs added). // Since we don't have (and don't want) a dummy view to inflate this menu, just // depend on the AndroidX Toolbar internal API and hope for the best. @SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext()) - MenuInflater(requireContext()).inflate(menuRes, builder) + MenuInflater(requireContext()).inflate(casted.res, builder) // Disable any menu options as specified by the impl - val disabledIds = getDisabledItemIds(castedMusic) + val disabledIds = getDisabledItemIds(casted) val visible = builder.children.mapTo(mutableListOf()) { it.isEnabled = !disabledIds.contains(it.itemId) @@ -130,7 +128,7 @@ abstract class MenuDialogFragment : menuAdapter.update(visible, UpdateInstructions.Diff) // Delegate to impl how to show music - updateMusic(requireBinding(), castedMusic) + updateMenu(requireBinding(), casted) } final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) { @@ -138,6 +136,6 @@ abstract class MenuDialogFragment : // TODO: This should change if the app is 100% migrated to menu dialogs findNavController().navigateUp() // Delegate to impl on how to handle items - @Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMusic.value as T) + @Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMenu.value as M) } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt index 4aad05948..ac768b627 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt @@ -27,10 +27,9 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.list.ListViewModel -import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song @@ -46,7 +45,7 @@ import org.oxycblt.auxio.util.showToast * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class SongMenuDialogFragment : MenuDialogFragment() { +class SongMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() @@ -54,38 +53,37 @@ class SongMenuDialogFragment : MenuDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() private val args: SongMenuDialogFragmentArgs by navArgs() - override val menuRes: Int - get() = args.menuRes - override val uid: Music.UID - get() = args.songUid + override val parcel + get() = args.parcel // Nothing to disable in song menus. - override fun getDisabledItemIds(music: Song) = setOf() + override fun getDisabledItemIds(menu: Menu.ForSong) = setOf() - override fun updateMusic(binding: DialogMenuBinding, music: Song) { + override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForSong) { val context = requireContext() - binding.menuCover.bind(music) + binding.menuCover.bind(menu.song) binding.menuType.text = getString(R.string.lbl_song) - binding.menuName.text = music.name.resolve(context) - binding.menuInfo.text = music.artists.resolveNames(context) + binding.menuName.text = menu.song.name.resolve(context) + binding.menuInfo.text = menu.song.artists.resolveNames(context) } - override fun onClick(item: MenuItem, music: Song) { + override fun onClick(item: MenuItem, menu: Menu.ForSong) { when (item.itemId) { - // TODO: Song play and shuffle as soon as PlaybackMode is refactored + R.id.action_play -> playbackModel.playExplicit(menu.song, menu.playWith) + R.id.action_shuffle -> playbackModel.shuffleExplicit(menu.song, menu.playWith) R.id.action_play_next -> { - playbackModel.playNext(music) + playbackModel.playNext(menu.song) requireContext().showToast(R.string.lng_queue_added) } R.id.action_queue_add -> { - playbackModel.addToQueue(music) + playbackModel.addToQueue(menu.song) requireContext().showToast(R.string.lng_queue_added) } - R.id.action_artist_details -> detailModel.showArtist(music) - R.id.action_album_details -> detailModel.showAlbum(music) - R.id.action_share -> requireContext().share(music) - R.id.action_playlist_add -> musicModel.addToPlaylist(music) - R.id.action_detail -> detailModel.showSong(music) + R.id.action_artist_details -> detailModel.showArtist(menu.song) + R.id.action_album_details -> detailModel.showAlbum(menu.song) + R.id.action_share -> requireContext().share(menu.song) + R.id.action_playlist_add -> musicModel.addToPlaylist(menu.song) + R.id.action_detail -> detailModel.showSong(menu.song) else -> error("Unexpected menu item selected $item") } } @@ -97,7 +95,7 @@ class SongMenuDialogFragment : MenuDialogFragment() { * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class AlbumMenuDialogFragment : MenuDialogFragment() { +class AlbumMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() override val listModel: ListViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() @@ -105,38 +103,36 @@ class AlbumMenuDialogFragment : MenuDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() private val args: AlbumMenuDialogFragmentArgs by navArgs() - override val menuRes: Int - get() = args.menuRes - override val uid: Music.UID - get() = args.albumUid + override val parcel + get() = args.parcel // Nothing to disable in album menus. - override fun getDisabledItemIds(music: Album) = setOf() + override fun getDisabledItemIds(menu: Menu.ForAlbum) = setOf() - override fun updateMusic(binding: DialogMenuBinding, music: Album) { + override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForAlbum) { val context = requireContext() - binding.menuCover.bind(music) - binding.menuType.text = getString(music.releaseType.stringRes) - binding.menuName.text = music.name.resolve(context) - binding.menuInfo.text = music.artists.resolveNames(context) + binding.menuCover.bind(menu.album) + binding.menuType.text = getString(menu.album.releaseType.stringRes) + binding.menuName.text = menu.album.name.resolve(context) + binding.menuInfo.text = menu.album.artists.resolveNames(context) } - override fun onClick(item: MenuItem, music: Album) { + override fun onClick(item: MenuItem, menu: Menu.ForAlbum) { when (item.itemId) { - R.id.action_play -> playbackModel.play(music) - R.id.action_shuffle -> playbackModel.shuffle(music) - R.id.action_detail -> detailModel.showAlbum(music) + R.id.action_play -> playbackModel.play(menu.album) + R.id.action_shuffle -> playbackModel.shuffle(menu.album) + R.id.action_detail -> detailModel.showAlbum(menu.album) R.id.action_play_next -> { - playbackModel.playNext(music) + playbackModel.playNext(menu.album) requireContext().showToast(R.string.lng_queue_added) } R.id.action_queue_add -> { - playbackModel.addToQueue(music) + playbackModel.addToQueue(menu.album) requireContext().showToast(R.string.lng_queue_added) } - R.id.action_artist_details -> detailModel.showArtist(music) - R.id.action_playlist_add -> musicModel.addToPlaylist(music) - R.id.action_share -> requireContext().share(music) + R.id.action_artist_details -> detailModel.showArtist(menu.album) + R.id.action_playlist_add -> musicModel.addToPlaylist(menu.album) + R.id.action_share -> requireContext().share(menu.album) else -> error("Unexpected menu item selected $item") } } @@ -148,7 +144,7 @@ class AlbumMenuDialogFragment : MenuDialogFragment() { * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class ArtistMenuDialogFragment : MenuDialogFragment() { +class ArtistMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() override val listModel: ListViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() @@ -156,13 +152,11 @@ class ArtistMenuDialogFragment : MenuDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() private val args: ArtistMenuDialogFragmentArgs by navArgs() - override val menuRes: Int - get() = args.menuRes - override val uid: Music.UID - get() = args.artistUid + override val parcel + get() = args.parcel - override fun getDisabledItemIds(music: Artist) = - if (music.songs.isEmpty()) { + override fun getDisabledItemIds(menu: Menu.ForArtist) = + if (menu.artist.songs.isEmpty()) { // Disable any operations that require some kind of songs to work with, as there won't // be any in an empty artist. setOf( @@ -176,37 +170,37 @@ class ArtistMenuDialogFragment : MenuDialogFragment() { setOf() } - override fun updateMusic(binding: DialogMenuBinding, music: Artist) { + override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForArtist) { val context = requireContext() - binding.menuCover.bind(music) + binding.menuCover.bind(menu.artist) binding.menuType.text = getString(R.string.lbl_artist) - binding.menuName.text = music.name.resolve(context) + binding.menuName.text = menu.artist.name.resolve(context) binding.menuInfo.text = getString( R.string.fmt_two, - context.getPlural(R.plurals.fmt_album_count, music.albums.size), - if (music.songs.isNotEmpty()) { - context.getPlural(R.plurals.fmt_song_count, music.songs.size) + context.getPlural(R.plurals.fmt_album_count, menu.artist.albums.size), + if (menu.artist.songs.isNotEmpty()) { + context.getPlural(R.plurals.fmt_song_count, menu.artist.songs.size) } else { getString(R.string.def_song_count) }) } - override fun onClick(item: MenuItem, music: Artist) { + override fun onClick(item: MenuItem, menu: Menu.ForArtist) { when (item.itemId) { - R.id.action_play -> playbackModel.play(music) - R.id.action_shuffle -> playbackModel.shuffle(music) - R.id.action_detail -> detailModel.showArtist(music) + R.id.action_play -> playbackModel.play(menu.artist) + R.id.action_shuffle -> playbackModel.shuffle(menu.artist) + R.id.action_detail -> detailModel.showArtist(menu.artist) R.id.action_play_next -> { - playbackModel.playNext(music) + playbackModel.playNext(menu.artist) requireContext().showToast(R.string.lng_queue_added) } R.id.action_queue_add -> { - playbackModel.addToQueue(music) + playbackModel.addToQueue(menu.artist) requireContext().showToast(R.string.lng_queue_added) } - R.id.action_playlist_add -> musicModel.addToPlaylist(music) - R.id.action_share -> requireContext().share(music) + R.id.action_playlist_add -> musicModel.addToPlaylist(menu.artist) + R.id.action_share -> requireContext().share(menu.artist) else -> error("Unexpected menu item $item") } } @@ -218,7 +212,7 @@ class ArtistMenuDialogFragment : MenuDialogFragment() { * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class GenreMenuDialogFragment : MenuDialogFragment() { +class GenreMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() override val listModel: ListViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() @@ -226,40 +220,38 @@ class GenreMenuDialogFragment : MenuDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() private val args: GenreMenuDialogFragmentArgs by navArgs() - override val menuRes: Int - get() = args.menuRes - override val uid: Music.UID - get() = args.genreUid + override val parcel + get() = args.parcel - override fun getDisabledItemIds(music: Genre) = setOf() + override fun getDisabledItemIds(menu: Menu.ForGenre) = setOf() - override fun updateMusic(binding: DialogMenuBinding, music: Genre) { + override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForGenre) { val context = requireContext() - binding.menuCover.bind(music) + binding.menuCover.bind(menu.genre) binding.menuType.text = getString(R.string.lbl_genre) - binding.menuName.text = music.name.resolve(context) + binding.menuName.text = menu.genre.name.resolve(context) binding.menuInfo.text = getString( R.string.fmt_two, - context.getPlural(R.plurals.fmt_artist_count, music.artists.size), - context.getPlural(R.plurals.fmt_song_count, music.songs.size)) + context.getPlural(R.plurals.fmt_artist_count, menu.genre.artists.size), + context.getPlural(R.plurals.fmt_song_count, menu.genre.songs.size)) } - override fun onClick(item: MenuItem, music: Genre) { + override fun onClick(item: MenuItem, menu: Menu.ForGenre) { when (item.itemId) { - R.id.action_play -> playbackModel.play(music) - R.id.action_shuffle -> playbackModel.shuffle(music) - R.id.action_detail -> detailModel.showGenre(music) + R.id.action_play -> playbackModel.play(menu.genre) + R.id.action_shuffle -> playbackModel.shuffle(menu.genre) + R.id.action_detail -> detailModel.showGenre(menu.genre) R.id.action_play_next -> { - playbackModel.playNext(music) + playbackModel.playNext(menu.genre) requireContext().showToast(R.string.lng_queue_added) } R.id.action_queue_add -> { - playbackModel.addToQueue(music) + playbackModel.addToQueue(menu.genre) requireContext().showToast(R.string.lng_queue_added) } - R.id.action_playlist_add -> musicModel.addToPlaylist(music) - R.id.action_share -> requireContext().share(music) + R.id.action_playlist_add -> musicModel.addToPlaylist(menu.genre) + R.id.action_share -> requireContext().share(menu.genre) else -> error("Unexpected menu item $item") } } @@ -271,7 +263,7 @@ class GenreMenuDialogFragment : MenuDialogFragment() { * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class PlaylistMenuDialogFragment : MenuDialogFragment() { +class PlaylistMenuDialogFragment : MenuDialogFragment() { override val menuModel: MenuViewModel by viewModels() override val listModel: ListViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() @@ -279,13 +271,11 @@ class PlaylistMenuDialogFragment : MenuDialogFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() private val args: PlaylistMenuDialogFragmentArgs by navArgs() - override val menuRes: Int - get() = args.menuRes - override val uid: Music.UID - get() = args.playlistUid + override val parcel + get() = args.parcel - override fun getDisabledItemIds(music: Playlist) = - if (music.songs.isEmpty()) { + override fun getDisabledItemIds(menu: Menu.ForPlaylist) = + if (menu.playlist.songs.isEmpty()) { // Disable any operations that require some kind of songs to work with, as there won't // be any in an empty playlist. setOf( @@ -299,35 +289,35 @@ class PlaylistMenuDialogFragment : MenuDialogFragment() { setOf() } - override fun updateMusic(binding: DialogMenuBinding, music: Playlist) { + override fun updateMenu(binding: DialogMenuBinding, menu: Menu.ForPlaylist) { val context = requireContext() - binding.menuCover.bind(music) + binding.menuCover.bind(menu.playlist) binding.menuType.text = getString(R.string.lbl_playlist) - binding.menuName.text = music.name.resolve(context) + binding.menuName.text = menu.playlist.name.resolve(context) binding.menuInfo.text = - if (music.songs.isNotEmpty()) { - context.getPlural(R.plurals.fmt_song_count, music.songs.size) + if (menu.playlist.songs.isNotEmpty()) { + context.getPlural(R.plurals.fmt_song_count, menu.playlist.songs.size) } else { getString(R.string.def_song_count) } } - override fun onClick(item: MenuItem, music: Playlist) { + override fun onClick(item: MenuItem, menu: Menu.ForPlaylist) { when (item.itemId) { - R.id.action_play -> playbackModel.play(music) - R.id.action_shuffle -> playbackModel.shuffle(music) - R.id.action_detail -> detailModel.showPlaylist(music) + R.id.action_play -> playbackModel.play(menu.playlist) + R.id.action_shuffle -> playbackModel.shuffle(menu.playlist) + R.id.action_detail -> detailModel.showPlaylist(menu.playlist) R.id.action_play_next -> { - playbackModel.playNext(music) + playbackModel.playNext(menu.playlist) requireContext().showToast(R.string.lng_queue_added) } R.id.action_queue_add -> { - playbackModel.addToQueue(music) + playbackModel.addToQueue(menu.playlist) requireContext().showToast(R.string.lng_queue_added) } - R.id.action_rename -> musicModel.renamePlaylist(music) - R.id.action_delete -> musicModel.deletePlaylist(music) - R.id.action_share -> requireContext().share(music) + R.id.action_rename -> musicModel.renamePlaylist(menu.playlist) + R.id.action_delete -> musicModel.deletePlaylist(menu.playlist) + R.id.action_share -> requireContext().share(menu.playlist) else -> error("Unexpected menu item $item") } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt index 3e34c1a52..577fe0e48 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt @@ -23,8 +23,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.list.Menu +import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.playback.PlaySong import org.oxycblt.auxio.util.logW /** @@ -35,32 +37,62 @@ import org.oxycblt.auxio.util.logW @HiltViewModel class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) : ViewModel(), MusicRepository.UpdateListener { - private val _currentMusic = MutableStateFlow(null) - /** The current [Music] information being shown in a menu dialog. */ - val currentMusic: StateFlow = _currentMusic + private val _currentMenu = MutableStateFlow(null) + /** The current [Menu] information being shown in a dialog. */ + val currentMenu: StateFlow = _currentMenu init { musicRepository.addUpdateListener(this) } override fun onMusicChanges(changes: MusicRepository.Changes) { - _currentMusic.value = _currentMusic.value?.let { musicRepository.find(it.uid) } + _currentMenu.value = _currentMenu.value?.let { unpackParcel(it.parcel) } } override fun onCleared() { musicRepository.removeUpdateListener(this) } - /** - * Set a new [currentMusic] from it's [Music.UID]. [currentMusic] will be updated to align with - * the new album. - * - * @param uid The [Music.UID] of the [Music] to update [currentMusic] to. Must be valid. - */ - fun setMusic(uid: Music.UID) { - _currentMusic.value = musicRepository.find(uid) - if (_currentMusic.value == null) { - logW("Given Music UID to show was invalid") + fun setMenu(parcel: Menu.Parcel) { + _currentMenu.value = unpackParcel(parcel) + if (_currentMenu.value == null) { + logW("Given menu parcel $parcel was invalid") } } + + private fun unpackParcel(parcel: Menu.Parcel) = + when (parcel) { + is Menu.ForSong.Parcel -> unpackSongParcel(parcel) + is Menu.ForAlbum.Parcel -> unpackAlbumParcel(parcel) + is Menu.ForArtist.Parcel -> unpackArtistParcel(parcel) + is Menu.ForGenre.Parcel -> unpackGenreParcel(parcel) + is Menu.ForPlaylist.Parcel -> unpackPlaylistParcel(parcel) + } + + private fun unpackSongParcel(parcel: Menu.ForSong.Parcel): Menu.ForSong? { + val song = musicRepository.deviceLibrary?.findSong(parcel.songUid) ?: return null + val parent = parcel.playWithUid?.let(musicRepository::find) as MusicParent? + val playWith = PlaySong.fromIntCode(parcel.playWithCode, parent) ?: return null + return Menu.ForSong(parcel.res, song, playWith) + } + + private fun unpackAlbumParcel(parcel: Menu.ForAlbum.Parcel): Menu.ForAlbum? { + val album = musicRepository.deviceLibrary?.findAlbum(parcel.albumUid) ?: return null + return Menu.ForAlbum(parcel.res, album) + } + + private fun unpackArtistParcel(parcel: Menu.ForArtist.Parcel): Menu.ForArtist? { + val artist = musicRepository.deviceLibrary?.findArtist(parcel.artistUid) ?: return null + return Menu.ForArtist(parcel.res, artist) + } + + private fun unpackGenreParcel(parcel: Menu.ForGenre.Parcel): Menu.ForGenre? { + val genre = musicRepository.deviceLibrary?.findGenre(parcel.genreUid) ?: return null + return Menu.ForGenre(parcel.res, genre) + } + + private fun unpackPlaylistParcel(parcel: Menu.ForPlaylist.Parcel): Menu.ForPlaylist? { + val playlist = musicRepository.userLibrary?.findPlaylist(parcel.playlistUid) ?: return null + return Menu.ForPlaylist(parcel.res, playlist) + } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt index 8032c46d7..622fd8652 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceLibrary.kt @@ -75,7 +75,7 @@ interface DeviceLibrary { * Find a [Album] instance corresponding to the given [Music.UID]. * * @param uid The [Music.UID] to search for. - * @return The corresponding [Song], or null if one was not found. + * @return The corresponding [Album], or null if one was not found. */ fun findAlbum(uid: Music.UID): Album? @@ -83,7 +83,7 @@ interface DeviceLibrary { * Find a [Artist] instance corresponding to the given [Music.UID]. * * @param uid The [Music.UID] to search for. - * @return The corresponding [Song], or null if one was not found. + * @return The corresponding [Artist], or null if one was not found. */ fun findArtist(uid: Music.UID): Artist? @@ -91,7 +91,7 @@ interface DeviceLibrary { * Find a [Genre] instance corresponding to the given [Music.UID]. * * @param uid The [Music.UID] to search for. - * @return The corresponding [Song], or null if one was not found. + * @return The corresponding [Genre], or null if one was not found. */ fun findGenre(uid: Music.UID): Genre? diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt index a4983f97b..6497e9c87 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaySong.kt @@ -21,56 +21,103 @@ package org.oxycblt.auxio.playback import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Playlist +/** + * Configuration to play a song in a desired way. + * + * Since songs are not [MusicParent]s, the way the queue is generated around them has a lot more + * flexibility. The particular strategy used can be configured the user, but it also needs to be + * transferred between views at points (such as menus). [PlaySong] provides both of these, being a + * enum-like datatype when configuration is needed, and an algebraic datatype when data transfer is + * needed. + * + * @author Alexander Capehart (OxygenCobalt) + */ sealed interface PlaySong { + /** + * The integer representation of this instance. + * + * @see fromIntCode + */ val intCode: Int + /** Play a Song from the entire library of songs. */ object FromAll : PlaySong { override val intCode = IntegerTable.PLAY_SONG_FROM_ALL } + /** Play a song from it's album. */ object FromAlbum : PlaySong { override val intCode = IntegerTable.PLAY_SONG_FROM_ALBUM } + /** + * Play a song from (possibly) one of it's [Artist]s. + * + * @param which The [Artist] to specifically play from. If null, the user will be prompted for + * an [Artist] to choose of the song has multiple. Otherwise, the only [Artist] will be used. + */ data class FromArtist(val which: Artist?) : PlaySong { override val intCode = IntegerTable.PLAY_SONG_FROM_ARTIST } + /** + * Play a song from (possibly) one of it's [Genre]s. + * + * @param which The [Genre] to specifically play from. If null, the user will be prompted for a + * [Genre] to choose of the song has multiple. Otherwise, the only [Genre] will be used. + */ data class FromGenre(val which: Genre?) : PlaySong { override val intCode = IntegerTable.PLAY_SONG_FROM_GENRE } + /** + * Play a song from one of it's [Playlist]s. + * + * @param which The [Playlist] to specifically play from. This must be provided. + */ data class FromPlaylist(val which: Playlist) : PlaySong { override val intCode = IntegerTable.PLAY_SONG_FROM_PLAYLIST } + /** Only play the given song, include nothing else in the queue. */ object ByItself : PlaySong { override val intCode = IntegerTable.PLAY_SONG_BY_ITSELF } companion object { - fun fromIntCode(intCode: Int, inner: Music?): PlaySong? = + /** + * Convert a [PlaySong] integer representation into an instance. + * + * @param intCode An integer representation of a [PlaySong] + * @param which Optional [MusicParent] to automatically populate a [FromArtist], + * [FromGenre], or [FromPlaylist] instance. If the type of the [MusicParent] does not + * match, it will be considered invalid and null will be returned. + * @return The corresponding [PlaySong], or null if the [PlaySong] is invalid. + * @see PlaySong.intCode + */ + fun fromIntCode(intCode: Int, which: MusicParent? = null): PlaySong? = when (intCode) { IntegerTable.PLAY_SONG_BY_ITSELF -> ByItself + IntegerTable.PLAY_SONG_FROM_ALL -> FromAll IntegerTable.PLAY_SONG_FROM_ALBUM -> FromAlbum IntegerTable.PLAY_SONG_FROM_ARTIST -> - if (inner is Artist?) { - FromArtist(inner) + if (which is Artist?) { + FromArtist(which) } else { null } IntegerTable.PLAY_SONG_FROM_GENRE -> - if (inner is Genre?) { - FromGenre(inner) + if (which is Genre?) { + FromGenre(which) } else { null } IntegerTable.PLAY_SONG_FROM_PLAYLIST -> - if (inner is Playlist) { - FromPlaylist(inner) + if (which is Playlist) { + FromPlaylist(which) } else { null } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt index 82a5c656b..a270c5c07 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt @@ -75,17 +75,14 @@ class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Cont get() = PlaySong.fromIntCode( sharedPreferences.getInt( - getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE), - null) + getString(R.string.set_key_play_in_list_with), Int.MIN_VALUE)) ?: PlaySong.FromAll override val inParentPlaybackMode: PlaySong? get() = PlaySong.fromIntCode( - sharedPreferences - .getInt(getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE) - .also { logD(it) }, - null) + sharedPreferences.getInt( + getString(R.string.set_key_play_in_parent_with), Int.MIN_VALUE)) override val barAction: ActionMode get() = diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index d0472e556..cebba2850 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -184,13 +184,13 @@ constructor( playWithImpl(song, with, isImplicitlyShuffled()) } - // fun playExplicit(song: Song, with: PlaySong) { - // playWithImpl(song, with, false) - // } - // - // fun shuffleExplicit(song: Song, with: PlaySong) { - // playWithImpl(song, with, true) - // } + fun playExplicit(song: Song, with: PlaySong) { + playWithImpl(song, with, false) + } + + fun shuffleExplicit(song: Song, with: PlaySong) { + playWithImpl(song, with, true) + } /** Shuffle all songs in the music library. */ fun shuffleAll() { 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 3a7a71233..27abdeab1 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -184,7 +184,7 @@ class SearchFragment : ListFragment() { override fun onOpenMenu(item: Music, anchor: View) { when (item) { - is Song -> listModel.openMenu(R.menu.item_song, item) + is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith) is Album -> listModel.openMenu(R.menu.item_album, item) is Artist -> listModel.openMenu(R.menu.item_parent, item) is Genre -> listModel.openMenu(R.menu.item_parent, item) @@ -256,16 +256,11 @@ class SearchFragment : ListFragment() { if (menu == null) return val directions = when (menu) { - is Menu.ForSong -> - SearchFragmentDirections.openSongMenu(menu.menuRes, menu.music.uid) - is Menu.ForAlbum -> - SearchFragmentDirections.openAlbumMenu(menu.menuRes, menu.music.uid) - is Menu.ForArtist -> - SearchFragmentDirections.openArtistMenu(menu.menuRes, menu.music.uid) - is Menu.ForGenre -> - SearchFragmentDirections.openGenreMenu(menu.menuRes, menu.music.uid) - is Menu.ForPlaylist -> - SearchFragmentDirections.openPlaylistMenu(menu.menuRes, menu.music.uid) + is Menu.ForSong -> SearchFragmentDirections.openSongMenu(menu.parcel) + is Menu.ForAlbum -> SearchFragmentDirections.openAlbumMenu(menu.parcel) + is Menu.ForArtist -> SearchFragmentDirections.openArtistMenu(menu.parcel) + is Menu.ForGenre -> SearchFragmentDirections.openGenreMenu(menu.parcel) + is Menu.ForPlaylist -> SearchFragmentDirections.openPlaylistMenu(menu.parcel) } findNavController().navigateSafe(directions) // Keyboard is no longer needed. diff --git a/app/src/main/res/menu/item_album_song.xml b/app/src/main/res/menu/item_album_song.xml index e31ea20b7..fdbc3fc5f 100644 --- a/app/src/main/res/menu/item_album_song.xml +++ b/app/src/main/res/menu/item_album_song.xml @@ -1,13 +1,13 @@ - - - - - - - - + + - - - - - - - - + + - - - - - - - - + + + + - + android:name="parcel" + app:argType="org.oxycblt.auxio.list.Menu$ForSong$Parcel" /> - + android:name="parcel" + app:argType="org.oxycblt.auxio.list.Menu$ForAlbum$Parcel" /> - + android:name="parcel" + app:argType="org.oxycblt.auxio.list.Menu$ForArtist$Parcel" /> - + android:name="parcel" + app:argType="org.oxycblt.auxio.list.Menu$ForGenre$Parcel" /> - + android:name="parcel" + app:argType="org.oxycblt.auxio.list.Menu$ForPlaylist$Parcel" /> @string/set_play_song_from_all - @string/set_play_song_from_artist @string/set_play_song_from_album + @string/set_play_song_from_artist @string/set_play_song_from_genre @string/set_play_song_by_itself @@ -119,14 +119,14 @@ @integer/play_song_from_album @integer/play_song_from_artist @integer/play_song_from_genre - @integer/play_song_itself + @integer/play_song_by_itself @string/set_play_song_none @string/set_play_song_from_all - @string/set_play_song_from_artist @string/set_play_song_from_album + @string/set_play_song_from_artist @string/set_play_song_from_genre @string/set_play_song_by_itself @@ -137,7 +137,7 @@ @integer/play_song_from_album @integer/play_song_from_artist @integer/play_song_from_genre - @integer/play_song_itself + @integer/play_song_by_itself @@ -157,11 +157,12 @@ 2 -2147483648 - 0xA11F - 0xA120 - 0xA121 - 0xA122 - 0xA123 + 0xA11F + 0xA120 + 0xA121 + 0xA122 + 0xA123 + 0xA124 0xA111 0xA112 From 9111a6eec716f09dc126f5323dfd7bc9edd54338 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 11 Jul 2023 15:39:58 -0600 Subject: [PATCH 08/28] ui: tweak bottom sheet dialog transitions Use a native slide animation for entering/editing bottom sheet dialogs, which looks a good amount nicer overall. --- .../ViewBindingBottomSheetDialogFragment.kt | 43 +++++++++++++++---- .../main/res/anim/bottom_sheet_slide_in.xml | 25 +++++++++++ .../main/res/anim/bottom_sheet_slide_out.xml | 10 +++++ app/src/main/res/values/styles_ui.xml | 8 +++- 4 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/anim/bottom_sheet_slide_in.xml create mode 100644 app/src/main/res/anim/bottom_sheet_slide_out.xml diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt index 2072d62e7..99be86b6f 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt @@ -18,13 +18,16 @@ package org.oxycblt.auxio.ui +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog +import androidx.annotation.StyleRes import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull @@ -39,13 +42,8 @@ abstract class ViewBindingBottomSheetDialogFragment : BottomSheetDialogFragment() { private var _binding: VB? = null - /** - * Configure the [AlertDialog.Builder] during [onCreateDialog]. - * - * @param builder The [AlertDialog.Builder] to configure. - * @see onCreateDialog - */ - protected open fun onConfigDialog(builder: AlertDialog.Builder) {} + override fun onCreateDialog(savedInstanceState: Bundle?): BottomSheetDialog = + RealAnimationBottomSheetDialog(requireContext(), theme) /** * Inflate the [ViewBinding] during [onCreateView]. @@ -108,4 +106,33 @@ abstract class ViewBindingBottomSheetDialogFragment : _binding = null logD("Fragment destroyed") } + + private inner class RealAnimationBottomSheetDialog + @JvmOverloads + constructor(context: Context, @StyleRes theme: Int = 0) : BottomSheetDialog(context, theme) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // The dialog already supplies an implementation for dismissing with the normal + // bottom sheet sliding, which is odd. It works well save the scrim not actually + // activating until the sheet is out of view, but that is tolerated for now. + // TODO: Replace with custom impl that runs the scrim animation and bottom sheet + // animation in parallel. Might just switch back to the stock animation if I can + // eliminate the opacity. + dismissWithAnimation = true + } + + override fun onStart() { + super.onStart() + // We have to manually trigger a hidden -> expanded transition when the dialog + // is initially opened. Hence, we set the state to hidden now and then as soon + // as we're drawing updating it to expanded. + behavior.state = BottomSheetBehavior.STATE_HIDDEN + requireView().post { behavior.state = BottomSheetBehavior.STATE_EXPANDED } + } + + override fun dismiss() { + super.dismiss() + behavior.state = BottomSheetBehavior.STATE_HIDDEN + } + } } diff --git a/app/src/main/res/anim/bottom_sheet_slide_in.xml b/app/src/main/res/anim/bottom_sheet_slide_in.xml new file mode 100644 index 000000000..b2aa8afdb --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_slide_in.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/anim/bottom_sheet_slide_out.xml b/app/src/main/res/anim/bottom_sheet_slide_out.xml new file mode 100644 index 000000000..b8ade7731 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_slide_out.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 0f5675812..98228e1aa 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -50,7 +50,13 @@ + +