playback: re-add play from genre

Re-add the "Play from genre" setting.

It's much easier to implement this now than prior with the picker
framework, so may as well retain consistency.
This commit is contained in:
Alexander Capehart 2022-12-27 16:24:52 -07:00
parent cce7b766d7
commit 195ea074ca
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
41 changed files with 306 additions and 131 deletions

View file

@ -74,10 +74,9 @@ class AuxioApp : Application(), ImageLoaderFactory {
.build() .build()
companion object { companion object {
/** The ID of the "Shuffle All" shortcut. */
const val SHORTCUT_SHUFFLE_ID = "shortcut_shuffle"
/** The [Intent] name for the "Shuffle All" shortcut. */ /** The [Intent] name for the "Shuffle All" shortcut. */
const val INTENT_KEY_SHORTCUT_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE_ALL" const val INTENT_KEY_SHORTCUT_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE_ALL"
/** The ID of the "Shuffle All" shortcut. */
private const val SHORTCUT_SHUFFLE_ID = "shortcut_shuffle"
} }
} }

View file

@ -61,6 +61,8 @@ object IntegerTable {
const val REPEAT_MODE_ALL = 0xA101 const val REPEAT_MODE_ALL = 0xA101
/** RepeatMode.TRACK */ /** RepeatMode.TRACK */
const val REPEAT_MODE_TRACK = 0xA102 const val REPEAT_MODE_TRACK = 0xA102
/** PlaybackMode.IN_GENRE */
const val PLAYBACK_MODE_IN_GENRE = 0xA103
/** PlaybackMode.IN_ARTIST */ /** PlaybackMode.IN_ARTIST */
const val PLAYBACK_MODE_IN_ARTIST = 0xA104 const val PLAYBACK_MODE_IN_ARTIST = 0xA104
/** PlaybackMode.IN_ALBUM */ /** PlaybackMode.IN_ALBUM */

View file

@ -128,9 +128,10 @@ class MainFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collect(navModel.mainNavigationAction, ::handleMainNavigation) collect(navModel.mainNavigationAction, ::handleMainNavigation)
collect(navModel.exploreNavigationItem, ::handleExploreNavigation) collect(navModel.exploreNavigationItem, ::handleExploreNavigation)
collect(navModel.exploreNavigationArtists, ::handleExplorePicker) collect(navModel.exploreArtistNavigationItem, ::handleArtistNavigationPicker)
collectImmediately(playbackModel.song, ::updateSong) collectImmediately(playbackModel.song, ::updateSong)
collect(playbackModel.artistPlaybackPickerSong, ::handlePlaybackArtistPicker) collect(playbackModel.artistPickerSong, ::handlePlaybackArtistPicker)
collect(playbackModel.genrePickerSong, ::handlePlaybackGenrePicker)
} }
override fun onStart() { override fun onStart() {
@ -278,13 +279,11 @@ class MainFragment :
} }
} }
private fun handleExplorePicker(items: List<Artist>?) { private fun handleArtistNavigationPicker(item: Music?) {
if (items != null) { if (item != null) {
// Navigate to the analogous artist picker dialog.
navModel.mainNavigateTo( navModel.mainNavigateTo(
MainNavigationAction.Directions( MainNavigationAction.Directions(
MainFragmentDirections.actionPickNavigationArtist( MainFragmentDirections.actionPickNavigationArtist(item.uid)))
items.map { it.uid }.toTypedArray())))
navModel.finishExploreNavigation() navModel.finishExploreNavigation()
} }
} }
@ -299,7 +298,6 @@ class MainFragment :
private fun handlePlaybackArtistPicker(song: Song?) { private fun handlePlaybackArtistPicker(song: Song?) {
if (song != null) { if (song != null) {
// Navigate to the analogous artist picker dialog.
navModel.mainNavigateTo( navModel.mainNavigateTo(
MainNavigationAction.Directions( MainNavigationAction.Directions(
MainFragmentDirections.actionPickPlaybackArtist(song.uid))) MainFragmentDirections.actionPickPlaybackArtist(song.uid)))
@ -307,6 +305,15 @@ class MainFragment :
} }
} }
private fun handlePlaybackGenrePicker(song: Song?) {
if (song != null) {
navModel.mainNavigateTo(
MainNavigationAction.Directions(
MainFragmentDirections.actionPickPlaybackGenre(song.uid)))
playbackModel.finishPlaybackArtistPicker()
}
}
private fun tryExpandSheets() { private fun tryExpandSheets() {
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =

View file

@ -128,14 +128,14 @@ class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAd
override fun onRealClick(music: Music) { override fun onRealClick(music: Music) {
check(music is Song) { "Unexpected datatype: ${music::class.java}" } check(music is Song) { "Unexpected datatype: ${music::class.java}" }
when (val mode = Settings(requireContext()).detailPlaybackMode) { when (Settings(requireContext()).detailPlaybackMode) {
// "Play from shown item" and "Play from album" functionally have the same // "Play from shown item" and "Play from album" functionally have the same
// behavior since a song can only have one album. // behavior since a song can only have one album.
null, null,
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: $mode") MusicMode.GENRES -> playbackModel.playFromGenre(music)
} }
} }
@ -171,7 +171,7 @@ class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAd
} }
override fun onNavigateToParentArtist() { override fun onNavigateToParentArtist() {
navModel.exploreNavigateTo(unlikelyToBeNull(detailModel.currentAlbum.value).artists) navModel.exploreNavigateToParentArtist(unlikelyToBeNull(detailModel.currentAlbum.value))
} }
private fun updateAlbum(album: Album?) { private fun updateAlbum(album: Album?) {
@ -180,7 +180,6 @@ class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAd
findNavController().navigateUp() findNavController().navigateUp()
return return
} }
requireBinding().detailToolbar.title = album.resolveName(requireContext()) requireBinding().detailToolbar.title = album.resolveName(requireContext())
} }

View file

@ -124,16 +124,16 @@ class ArtistDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapte
override fun onRealClick(music: Music) { override fun onRealClick(music: Music) {
when (music) { when (music) {
is Song -> { is Song -> {
when (val mode = Settings(requireContext()).detailPlaybackMode) { when (Settings(requireContext()).detailPlaybackMode) {
// "Play from selected item" and "Play from artist" differ, as the latter // When configured to play from the selected item, we already have an Artist
// actually should show a picker choice in the case of multiple artists. // to play from.
null -> null ->
playbackModel.playFromArtist( playbackModel.playFromArtist(
music, unlikelyToBeNull(detailModel.currentArtist.value)) music, unlikelyToBeNull(detailModel.currentArtist.value))
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: $mode") MusicMode.GENRES -> playbackModel.playFromGenre(music)
} }
} }
is Album -> navModel.exploreNavigateTo(music) is Album -> navModel.exploreNavigateTo(music)

View file

@ -124,15 +124,16 @@ class GenreDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapter
when (music) { when (music) {
is Artist -> navModel.exploreNavigateTo(music) is Artist -> navModel.exploreNavigateTo(music)
is Song -> is Song ->
when (val mode = Settings(requireContext()).detailPlaybackMode) { when (Settings(requireContext()).detailPlaybackMode) {
// Only way to play from the genre is through "Play from selected item". // When configured to play from the selected item, we already have a Genre
// to play from.
null -> null ->
playbackModel.playFromGenre( playbackModel.playFromGenre(
music, unlikelyToBeNull(detailModel.currentGenre.value)) music, unlikelyToBeNull(detailModel.currentGenre.value))
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: $mode") MusicMode.GENRES -> playbackModel.playFromGenre(music)
} }
else -> error("Unexpected datatype: ${music::class.simpleName}") else -> error("Unexpected datatype: ${music::class.simpleName}")
} }

View file

@ -52,7 +52,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
// DetailViewModel handles most initialization from the navigation argument. // DetailViewModel handles most initialization from the navigation argument.
detailModel.setSongUid(args.songUid) detailModel.setSongUid(args.itemUid)
collectImmediately(detailModel.currentSong, ::updateSong) collectImmediately(detailModel.currentSong, ::updateSong)
} }

View file

@ -155,9 +155,7 @@ class HomeFragment :
collect(homeModel.shouldRecreate, ::handleRecreate) collect(homeModel.shouldRecreate, ::handleRecreate)
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
collectImmediately(homeModel.songLists, homeModel.isFastScrolling, ::updateFab) collectImmediately(homeModel.songLists, homeModel.isFastScrolling, ::updateFab)
collectImmediately(musicModel.indexerState, ::updateIndexerState) collectImmediately(musicModel.indexerState, ::updateIndexerState)
collect(navModel.exploreNavigationItem, ::handleNavigation) collect(navModel.exploreNavigationItem, ::handleNavigation)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
} }
@ -484,9 +482,7 @@ class HomeFragment :
fragmentManager: FragmentManager, fragmentManager: FragmentManager,
lifecycleOwner: LifecycleOwner lifecycleOwner: LifecycleOwner
) : FragmentStateAdapter(fragmentManager, lifecycleOwner.lifecycle) { ) : FragmentStateAdapter(fragmentManager, lifecycleOwner.lifecycle) {
override fun getItemCount() = tabs.size override fun getItemCount() = tabs.size
override fun createFragment(position: Int): Fragment = override fun createFragment(position: Int): Fragment =
when (tabs[position]) { when (tabs[position]) {
MusicMode.SONGS -> SongListFragment() MusicMode.SONGS -> SongListFragment()

View file

@ -132,11 +132,11 @@ class SongListFragment :
override fun onRealClick(music: Music) { override fun onRealClick(music: Music) {
check(music is Song) { "Unexpected datatype: ${music::class.java}" } check(music is Song) { "Unexpected datatype: ${music::class.java}" }
when (val mode = Settings(requireContext()).libPlaybackMode) { when (Settings(requireContext()).libPlaybackMode) {
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: $mode") MusicMode.GENRES -> playbackModel.playFromGenre(music)
} }
} }

View file

@ -90,7 +90,7 @@ abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), Selecta
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_go_artist -> { R.id.action_go_artist -> {
navModel.exploreNavigateTo(song.artists) navModel.exploreNavigateToParentArtist(song)
} }
R.id.action_go_album -> { R.id.action_go_album -> {
navModel.exploreNavigateTo(song.album) navModel.exploreNavigateTo(song.album)
@ -134,7 +134,7 @@ abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), Selecta
requireContext().showToast(R.string.lng_queue_added) requireContext().showToast(R.string.lng_queue_added)
} }
R.id.action_go_artist -> { R.id.action_go_artist -> {
navModel.exploreNavigateTo(album.artists) navModel.exploreNavigateToParentArtist(album)
} }
else -> { else -> {
error("Unexpected menu item selected") error("Unexpected menu item selected")

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
/** /**
* An adapter responsible for showing a list of [Artist] choices in [ArtistPickerDialog]. * An [RecyclerView.Adapter] that displays a list of [Artist] choices.
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
* @author OxygenCobalt. * @author OxygenCobalt.
*/ */

View file

@ -31,12 +31,12 @@ import org.oxycblt.auxio.ui.NavigationViewModel
*/ */
class ArtistNavigationPickerDialog : ArtistPickerDialog() { class ArtistNavigationPickerDialog : ArtistPickerDialog() {
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
// Information about what artists to display is initially within the navigation arguments // Information about what Song to show choices for is initially within the navigation arguments
// as a list of UIDs, as that is the only safe way to parcel an artist. // as UIDs, as that is the only safe way to parcel a Song.
private val args: ArtistNavigationPickerDialogArgs by navArgs() private val args: ArtistNavigationPickerDialogArgs by navArgs()
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
pickerModel.setArtistUids(args.artistUids) pickerModel.setItemUid(args.itemUid)
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
} }

View file

@ -52,11 +52,9 @@ abstract class ArtistPickerDialog :
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
binding.pickerRecycler.adapter = artistAdapter binding.pickerRecycler.adapter = artistAdapter
collectImmediately(pickerModel.currentArtists) { artists -> collectImmediately(pickerModel.artistChoices) { artists ->
if (!artists.isNullOrEmpty()) { if (artists.isNotEmpty()) {
// Make sure the artist choices align with any changes in the music library. // Make sure the artist choices align with any changes in the music library.
// TODO: I really don't think it makes sense to do this. I'd imagine it would
// be more productive to just exit this dialog rather than try to update it.
artistAdapter.submitList(artists) artistAdapter.submitList(artists)
} else { } else {
// Not showing any choices, navigate up. // Not showing any choices, navigate up.

View file

@ -22,6 +22,7 @@ import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
@ -31,19 +32,21 @@ import org.oxycblt.auxio.util.androidActivityViewModels
*/ */
class ArtistPlaybackPickerDialog : ArtistPickerDialog() { class ArtistPlaybackPickerDialog : ArtistPickerDialog() {
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
// Information about what artists to display is initially within the navigation arguments // Information about what Song to show choices for is initially within the navigation arguments
// as a list of UIDs, as that is the only safe way to parcel an artist. // as UIDs, as that is the only safe way to parcel a Song.
private val args: ArtistPlaybackPickerDialogArgs by navArgs() private val args: ArtistPlaybackPickerDialogArgs by navArgs()
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
pickerModel.setSongUid(args.songUid) pickerModel.setItemUid(args.itemUid)
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
} }
override fun onClick(item: Item) { override fun onClick(item: Item) {
super.onClick(item) super.onClick(item)
check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" }
// User made a choice, play the given song from that artist. // User made a choice, play the given song from that artist.
pickerModel.currentSong.value?.let { song -> playbackModel.playFromArtist(song, item) } check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" }
val song = pickerModel.currentItem.value
check(song is Song) { "Unexpected datatype: ${item::class.simpleName}" }
playbackModel.playFromArtist(song, item)
} }
} }

View file

@ -0,0 +1,68 @@
package org.oxycblt.auxio.music.picker
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemPickerChoiceBinding
import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
/**
* An [RecyclerView.Adapter] that displays a list of [Genre] choices.
* @param listener A [ClickableListListener] to bind interactions to.
* @author OxygenCobalt.
*/
class GenreChoiceAdapter(private val listener: ClickableListListener) :
RecyclerView.Adapter<GenreChoiceViewHolder>() {
private var genres = listOf<Genre>()
override fun getItemCount() = genres.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
GenreChoiceViewHolder.new(parent)
override fun onBindViewHolder(holder: GenreChoiceViewHolder, position: Int) =
holder.bind(genres[position], listener)
/**
* Immediately update the [Genre] choices.
* @param newGenres The new [Genre]s to show.
*/
fun submitList(newGenres: List<Genre>) {
if (newGenres != genres) {
genres = newGenres
@Suppress("NotifyDataSetChanged") notifyDataSetChanged()
}
}
}
/**
* A [DialogRecyclerView.ViewHolder] that displays a smaller variant of a typical [Genre] item, for
* use with [GenreChoiceAdapter]. Use [new] to create an instance.
*/
class GenreChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
DialogRecyclerView.ViewHolder(binding.root) {
/**
* Bind new data to this instance.
* @param genre The new [Genre] to bind.
* @param listener A [ClickableListListener] to bind interactions to.
*/
fun bind(genre: Genre, listener: ClickableListListener) {
binding.root.setOnClickListener { listener.onClick(genre) }
binding.pickerImage.bind(genre)
binding.pickerName.text = genre.resolveName(binding.context)
}
companion object {
/**
* Create a new instance.
* @param parent The parent to inflate this instance from.
* @return A new instance.
*/
fun new(parent: View) =
GenreChoiceViewHolder(ItemPickerChoiceBinding.inflate(parent.context.inflater))
}
}

View file

@ -0,0 +1,66 @@
package org.oxycblt.auxio.music.picker
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately
/**
* A picker [ViewBindingDialogFragment] intended for when [Genre] playback is ambiguous.
* @author Alexander Capehart (OxygenCobalt)
*/
class GenrePlaybackPickerDialog : ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener {
private val pickerModel: PickerViewModel by viewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
// Information about what Song to show choices for is initially within the navigation arguments
// as UIDs, as that is the only safe way to parcel a Song.
private val args: GenrePlaybackPickerDialogArgs by navArgs()
// Okay to leak this since the Listener will not be called until after initialization.
private val genreAdapter = GenreChoiceAdapter(@Suppress("LeakingThis") this)
override fun onCreateBinding(inflater: LayoutInflater) =
DialogMusicPickerBinding.inflate(inflater)
override fun onConfigDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.lbl_genres).setNegativeButton(R.string.lbl_cancel, null)
}
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
binding.pickerRecycler.adapter = genreAdapter
pickerModel.setItemUid(args.itemUid)
collectImmediately(pickerModel.genreChoices) { genres ->
if (genres.isNotEmpty()) {
// Make sure the genre choices align with any changes in the music library.
genreAdapter.submitList(genres)
} else {
// Not showing any choices, navigate up.
findNavController().navigateUp()
}
}
}
override fun onDestroyBinding(binding: DialogMusicPickerBinding) {
binding.pickerRecycler.adapter = null
}
override fun onClick(item: Item) {
// User made a choice, play the given song from that genre.
check(item is Genre) { "Unexpected datatype: ${item::class.simpleName}" }
val song = pickerModel.currentItem.value
check(song is Song) { "Unexpected datatype: ${item::class.simpleName}" }
playbackModel.playFromGenre(song, item)
}
}

View file

@ -20,10 +20,7 @@ package org.oxycblt.auxio.music.picker
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
@ -32,24 +29,21 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class PickerViewModel : ViewModel(), MusicStore.Callback { class PickerViewModel : ViewModel(), MusicStore.Callback {
// TODO: Refactor
private val musicStore = MusicStore.getInstance() private val musicStore = MusicStore.getInstance()
private val _currentSong = MutableStateFlow<Song?>(null) private val _currentItem = MutableStateFlow<Music?>(null)
/** /** The current item whose artists should be shown in the picker. Null if there is no item. */
* The current [Song] whose choices are being shown in the picker. Null if there is no [Song]. val currentItem: StateFlow<Music?> get() = _currentItem
*/
val currentSong: StateFlow<Song?>
get() = _currentSong
private val _currentArtists = MutableStateFlow<List<Artist>?>(null) private val _artistChoices = MutableStateFlow<List<Artist>>(listOf())
/** /** The current [Artist] choices. Empty if no item is shown in the picker. */
* The current [Artist] whose choices are being shown in the picker. Null/Empty if there is val artistChoices: StateFlow<List<Artist>>
* none. get() = _artistChoices
*/
val currentArtists: StateFlow<List<Artist>?> private val _genreChoices = MutableStateFlow<List<Genre>>(listOf())
get() = _currentArtists /** The current [Genre] choices. Empty if no item is shown in the picker. */
val genreChoices: StateFlow<List<Genre>>
get() = _genreChoices
override fun onCleared() { override fun onCleared() {
musicStore.removeCallback(this) musicStore.removeCallback(this)
@ -57,37 +51,29 @@ class PickerViewModel : ViewModel(), MusicStore.Callback {
override fun onLibraryChanged(library: MusicStore.Library?) { override fun onLibraryChanged(library: MusicStore.Library?) {
if (library != null) { if (library != null) {
// If we are showing any item right now, we will need to refresh it (and any information refreshChoices()
// related to it) with the new library in order to prevent stale items from appearing
// in the UI.
val song = _currentSong.value
val artists = _currentArtists.value
if (song != null) {
_currentSong.value = library.sanitize(song)
_currentArtists.value = _currentSong.value?.artists
} else if (artists != null) {
_currentArtists.value = artists.mapNotNull { library.sanitize(it) }
}
} }
} }
/** /**
* Set a new [currentSong] from it's [Music.UID]. * Set a new [currentItem] from it's [Music.UID].
* @param uid The [Music.UID] of the [Song] to update to. * @param uid The [Music.UID] of the [Song] to update to.
*/ */
fun setSongUid(uid: Music.UID) { fun setItemUid(uid: Music.UID) {
val library = unlikelyToBeNull(musicStore.library) val library = unlikelyToBeNull(musicStore.library)
_currentSong.value = library.find(uid) _currentItem.value = library.find(uid)
_currentArtists.value = _currentSong.value?.artists refreshChoices()
} }
/** private fun refreshChoices() {
* Set a new [currentArtists] list from a list of [Music.UID]'s. when (val item = _currentItem.value) {
* @param uids The [Music.UID]s of the [Artist]s to [currentArtists] to. is Song -> {
*/ _artistChoices.value = item.artists
fun setArtistUids(uids: Array<Music.UID>) { _genreChoices.value = item.genres
val library = unlikelyToBeNull(musicStore.library) }
// Map the UIDs to artist instances and filter out the ones that can't be found. is Album -> _artistChoices.value = item.artists
_currentArtists.value = uids.mapNotNull { library.find<Artist>(it) }.ifEmpty { null } else -> {}
}
} }
} }

View file

@ -210,7 +210,7 @@ class PlaybackPanelFragment :
/** Navigate to one of the currently playing [Song]'s Artists. */ /** Navigate to one of the currently playing [Song]'s Artists. */
private fun navigateToCurrentArtist() { private fun navigateToCurrentArtist() {
val song = playbackModel.song.value ?: return val song = playbackModel.song.value ?: return
navModel.exploreNavigateTo(song.artists) navModel.exploreNavigateToParentArtist(song)
} }
/** Navigate to the currently playing [Song]'s albums. */ /** Navigate to the currently playing [Song]'s albums. */

View file

@ -70,13 +70,21 @@ class PlaybackViewModel(application: Application) :
private val _artistPlaybackPickerSong = MutableStateFlow<Song?>(null) private val _artistPlaybackPickerSong = MutableStateFlow<Song?>(null)
/** /**
* Flag signaling to open a picker dialog in order to resolve an ambiguous [Artist] choice when * Flag signaling to open a picker dialog in order to resolve an ambiguous choice when
* playing a [Song] from one of it's [Artist]s. * playing a [Song] from one of it's [Artist]s.
* @see playFromArtist * @see playFromArtist
*/ */
val artistPlaybackPickerSong: StateFlow<Song?> val artistPickerSong: StateFlow<Song?>
get() = _artistPlaybackPickerSong get() = _artistPlaybackPickerSong
private val _genrePlaybackPickerSong = MutableStateFlow<Song?>(null)
/**
* Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing
* a [Song] from one of it's [Genre]s.
*/
val genrePickerSong: StateFlow<Song?>
get() = _genrePlaybackPickerSong
/** /**
* The current audio session ID of the internal player. Null if no [InternalPlayer] is * The current audio session ID of the internal player. Null if no [InternalPlayer] is
* available. * available.
@ -178,11 +186,18 @@ class PlaybackViewModel(application: Application) :
/** /**
* PLay a [Song] from one of it's [Genre]s. * PLay a [Song] from one of it's [Genre]s.
* @param song The [Song] to play. * @param song The [Song] to play.
* @param genre The [Genre] to play from. Must be linked to the [Song]. * @param genre The [Genre] to play from. Must be linked to the [Song]. If null, the user will
* be prompted on what artist to play. Defaults to null.
*/ */
fun playFromGenre(song: Song, genre: Genre) { fun playFromGenre(song: Song, genre: Genre? = null) {
check(genre.songs.contains(song)) { "Invalid input: Genre is not linked to song" } if (genre != null) {
playbackManager.play(song, genre, settings) check(genre.songs.contains(song)) { "Invalid input: Genre is not linked to song" }
playbackManager.play(song, genre, settings)
} else if (song.genres.size == 1) {
playbackManager.play(song, song.genres[0], settings)
} else {
_genrePlaybackPickerSong.value = song
}
} }
/** /**

View file

@ -137,11 +137,11 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
override fun onRealClick(music: Music) { override fun onRealClick(music: Music) {
when (music) { when (music) {
is Song -> is Song ->
when (val mode = Settings(requireContext()).libPlaybackMode) { when (Settings(requireContext()).libPlaybackMode) {
MusicMode.SONGS -> playbackModel.playFromAll(music) MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music) MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music) MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: $mode") MusicMode.GENRES -> playbackModel.playFromGenre(music)
} }
is MusicParent -> navModel.exploreNavigateTo(music) is MusicParent -> navModel.exploreNavigateTo(music)
} }

View file

@ -113,10 +113,11 @@ class Settings(private val context: Context, private val callback: Callback? = n
fun Int.migratePlaybackMode() = fun Int.migratePlaybackMode() =
when (this) { when (this) {
// Genre playback mode was removed in 3.0.0 // Convert PlaybackMode into MusicMode
IntegerTable.PLAYBACK_MODE_ALL_SONGS -> MusicMode.SONGS IntegerTable.PLAYBACK_MODE_ALL_SONGS -> MusicMode.SONGS
IntegerTable.PLAYBACK_MODE_IN_ARTIST -> MusicMode.ARTISTS IntegerTable.PLAYBACK_MODE_IN_ARTIST -> MusicMode.ARTISTS
IntegerTable.PLAYBACK_MODE_IN_ALBUM -> MusicMode.ALBUMS IntegerTable.PLAYBACK_MODE_IN_ALBUM -> MusicMode.ALBUMS
IntegerTable.PLAYBACK_MODE_IN_GENRE -> MusicMode.GENRES
else -> null else -> null
} }

View file

@ -21,8 +21,10 @@ import androidx.lifecycle.ViewModel
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
/** A [ViewModel] that handles complicated navigation functionality. */ /** A [ViewModel] that handles complicated navigation functionality. */
@ -42,14 +44,14 @@ class NavigationViewModel : ViewModel() {
val exploreNavigationItem: StateFlow<Music?> val exploreNavigationItem: StateFlow<Music?>
get() = _exploreNavigationItem get() = _exploreNavigationItem
private val _exploreNavigationArtists = MutableStateFlow<List<Artist>?>(null) private val _exploreArtistNavigationItem = MutableStateFlow<Music?>(null)
/** /**
* Variation of [exploreNavigationItem] for situations where the choice of [Artist] to navigate * Variation of [exploreNavigationItem] for situations where the choice of parent [Artist] to
* to is ambiguous. Only intended for use by MainFragment, as the resolved choice will * navigate to is ambiguous. Only intended for use by MainFragment, as the resolved choice will
* eventually be assigned to [exploreNavigationItem]. * eventually be assigned to [exploreNavigationItem].
*/ */
val exploreNavigationArtists: StateFlow<List<Artist>?> val exploreArtistNavigationItem: StateFlow<Music?>
get() = _exploreNavigationArtists get() = _exploreArtistNavigationItem
/** /**
* Navigate to something in the main navigation graph. This can be used by UIs in the explore * Navigate to something in the main navigation graph. This can be used by UIs in the explore
@ -89,12 +91,25 @@ class NavigationViewModel : ViewModel() {
} }
/** /**
* Navigate to an [Artist] out of a list of [Artist]s, like [exploreNavigateTo]. * Navigate to one of the parent [Artist]'s of the given [Song].
* @param artists The [Artist]s to navigate to. In the case of multiple artists, the user will * @param song The [Song] to navigate with. If there are multiple parent [Artist]s,
* be prompted with a choice on which [Artist] to navigate to. * a picker dialog will be shown.
*/ */
fun exploreNavigateTo(artists: List<Artist>) { fun exploreNavigateToParentArtist(song: Song) {
if (_exploreNavigationArtists.value != null) { exploreNavigateToParentArtistImpl(song, song.artists)
}
/**
* Navigate to one of the parent [Artist]'s of the given [Album].
* @param album The [Album] to navigate with. If there are multiple parent [Artist]s,
* a picker dialog will be shown.
*/
fun exploreNavigateToParentArtist(album: Album) {
exploreNavigateToParentArtistImpl(album, album.artists)
}
private fun exploreNavigateToParentArtistImpl(item: Music, artists: List<Artist>) {
if (_exploreArtistNavigationItem.value != null) {
logD("Already navigating, not doing explore action") logD("Already navigating, not doing explore action")
return return
} }
@ -103,7 +118,7 @@ class NavigationViewModel : ViewModel() {
exploreNavigateTo(artists[0]) exploreNavigateTo(artists[0])
} else { } else {
logD("Navigating to a choice of ${artists.map { it.rawName }}") logD("Navigating to a choice of ${artists.map { it.rawName }}")
_exploreNavigationArtists.value = artists _exploreArtistNavigationItem.value = item
} }
} }
@ -114,7 +129,7 @@ class NavigationViewModel : ViewModel() {
fun finishExploreNavigation() { fun finishExploreNavigation() {
logD("Finishing explore navigation process") logD("Finishing explore navigation process")
_exploreNavigationItem.value = null _exploreNavigationItem.value = null
_exploreNavigationArtists.value = null _exploreArtistNavigationItem.value = null
} }
} }

View file

@ -23,6 +23,9 @@
<action <action
android:id="@+id/action_pick_navigation_artist" android:id="@+id/action_pick_navigation_artist"
app:destination="@id/artist_navigation_picker_dialog" /> app:destination="@id/artist_navigation_picker_dialog" />
<action
android:id="@+id/action_pick_playback_genre"
app:destination="@id/genre_playback_picker_dialog" />
</fragment> </fragment>
<dialog <dialog
@ -31,7 +34,7 @@
android:label="artist_playback_picker_dialog" android:label="artist_playback_picker_dialog"
tools:layout="@layout/dialog_music_picker"> tools:layout="@layout/dialog_music_picker">
<argument <argument
android:name="songUid" android:name="itemUid"
app:argType="org.oxycblt.auxio.music.Music$UID" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
@ -41,8 +44,18 @@
android:label="artist_navigation_picker_dialog" android:label="artist_navigation_picker_dialog"
tools:layout="@layout/dialog_music_picker"> tools:layout="@layout/dialog_music_picker">
<argument <argument
android:name="artistUids" android:name="itemUid"
app:argType="org.oxycblt.auxio.music.Music$UID[]" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog>
<dialog
android:id="@+id/genre_playback_picker_dialog"
android:name="org.oxycblt.auxio.music.picker.GenrePlaybackPickerDialog"
android:label="genre_playback_picker_dialog"
tools:layout="@layout/dialog_music_picker">
<argument
android:name="itemUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>
<dialog <dialog
@ -51,7 +64,7 @@
android:label="song_detail_dialog" android:label="song_detail_dialog"
tools:layout="@layout/dialog_song_detail"> tools:layout="@layout/dialog_song_detail">
<argument <argument
android:name="songUid" android:name="itemUid"
app:argType="org.oxycblt.auxio.music.Music$UID" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
</dialog> </dialog>

View file

@ -23,7 +23,7 @@
<string name="lbl_playback">يعمل الآن</string> <string name="lbl_playback">يعمل الآن</string>
<string name="lbl_play">تشغيل</string> <string name="lbl_play">تشغيل</string>
<string name="lbl_shuffle">خلط</string> <string name="lbl_shuffle">خلط</string>
<string name="set_playback_mode_all">تشغيل من جميع الاغاني</string> <string name="set_playback_mode_songs">تشغيل من جميع الاغاني</string>
<string name="set_playback_mode_album">تشغيل من البوم</string> <string name="set_playback_mode_album">تشغيل من البوم</string>
<string name="set_playback_mode_artist">تشغيل من فنان</string> <string name="set_playback_mode_artist">تشغيل من فنان</string>
<string name="lbl_queue">طابور</string> <string name="lbl_queue">طابور</string>

View file

@ -29,7 +29,7 @@
<string name="lbl_playback">Právě hraje</string> <string name="lbl_playback">Právě hraje</string>
<string name="lbl_play">Přehrát</string> <string name="lbl_play">Přehrát</string>
<string name="lbl_shuffle">Náhodně</string> <string name="lbl_shuffle">Náhodně</string>
<string name="set_playback_mode_all">Přehrát ze všech skladeb</string> <string name="set_playback_mode_songs">Přehrát ze všech skladeb</string>
<string name="set_playback_mode_album">Přehrát z alba</string> <string name="set_playback_mode_album">Přehrát z alba</string>
<string name="set_playback_mode_artist">Přehrát od umělce</string> <string name="set_playback_mode_artist">Přehrát od umělce</string>
<string name="lbl_queue">Fronta</string> <string name="lbl_queue">Fronta</string>

View file

@ -15,7 +15,7 @@
<string name="lbl_sort_asc">Aufsteigend</string> <string name="lbl_sort_asc">Aufsteigend</string>
<string name="lbl_play">Abspielen</string> <string name="lbl_play">Abspielen</string>
<string name="lbl_shuffle">Zufällig</string> <string name="lbl_shuffle">Zufällig</string>
<string name="set_playback_mode_all">Von allen Lieder abspielen</string> <string name="set_playback_mode_songs">Von allen Lieder abspielen</string>
<string name="set_playback_mode_album">Von Album abspielen</string> <string name="set_playback_mode_album">Von Album abspielen</string>
<string name="lbl_playback">Aktuelle Wiedergabe</string> <string name="lbl_playback">Aktuelle Wiedergabe</string>
<string name="lbl_queue">Warteschlange</string> <string name="lbl_queue">Warteschlange</string>

View file

@ -23,7 +23,7 @@
<string name="lbl_playback">En reproducción</string> <string name="lbl_playback">En reproducción</string>
<string name="lbl_play">Reproducir</string> <string name="lbl_play">Reproducir</string>
<string name="lbl_shuffle">Mezcla</string> <string name="lbl_shuffle">Mezcla</string>
<string name="set_playback_mode_all">Reproducir todo</string> <string name="set_playback_mode_songs">Reproducir todo</string>
<string name="set_playback_mode_album">Reproducir por álbum</string> <string name="set_playback_mode_album">Reproducir por álbum</string>
<string name="set_playback_mode_artist">Reproducir por artista</string> <string name="set_playback_mode_artist">Reproducir por artista</string>
<string name="lbl_queue">Cola</string> <string name="lbl_queue">Cola</string>

View file

@ -85,7 +85,7 @@
<string name="set_pre_amp_warning">Upozorenje: Postavljanje pretpojačala na visoke razine može uzrokovati vrhunce tonova u nekim zvučnim zapisima.</string> <string name="set_pre_amp_warning">Upozorenje: Postavljanje pretpojačala na visoke razine može uzrokovati vrhunce tonova u nekim zvučnim zapisima.</string>
<string name="set_library_song_playback_mode">Kada se reproducira iz zbirke</string> <string name="set_library_song_playback_mode">Kada se reproducira iz zbirke</string>
<string name="set_detail_song_playback_mode">Kada se reproducira iz detalja o predmetu</string> <string name="set_detail_song_playback_mode">Kada se reproducira iz detalja o predmetu</string>
<string name="set_playback_mode_all">Reproduciraj od svih pjesama</string> <string name="set_playback_mode_songs">Reproduciraj od svih pjesama</string>
<string name="set_reindex">Aktualiziraj glazbu</string> <string name="set_reindex">Aktualiziraj glazbu</string>
<string name="set_reindex_desc">Ponovo učitaj glazbenu biblioteku, koristeći predmemorirane oznake kada je to moguće</string> <string name="set_reindex_desc">Ponovo učitaj glazbenu biblioteku, koristeći predmemorirane oznake kada je to moguće</string>
<string name="set_dirs">Mape glazbe</string> <string name="set_dirs">Mape glazbe</string>

View file

@ -95,7 +95,7 @@
<string name="set_pre_amp_with">Penyesuaian dengan tag</string> <string name="set_pre_amp_with">Penyesuaian dengan tag</string>
<string name="set_pre_amp_warning">Peringatan: Mengubah pre-amp ke nilai positif yang tinggi dapat mengakibatkan puncak pada beberapa trek audio.</string> <string name="set_pre_amp_warning">Peringatan: Mengubah pre-amp ke nilai positif yang tinggi dapat mengakibatkan puncak pada beberapa trek audio.</string>
<string name="set_playback_mode_none">Putar dari item yang ditampilkan</string> <string name="set_playback_mode_none">Putar dari item yang ditampilkan</string>
<string name="set_playback_mode_all">Putar dari semua lagu</string> <string name="set_playback_mode_songs">Putar dari semua lagu</string>
<string name="set_keep_shuffle_desc">Tetap mengacak saat memutar lagu baru</string> <string name="set_keep_shuffle_desc">Tetap mengacak saat memutar lagu baru</string>
<string name="set_repeat_pause">Jeda pada pengulangan</string> <string name="set_repeat_pause">Jeda pada pengulangan</string>
<string name="set_rewind_prev">Putar balik sebelum melompat ke belakang</string> <string name="set_rewind_prev">Putar balik sebelum melompat ke belakang</string>

View file

@ -23,7 +23,7 @@
<string name="lbl_playback">Ora in riproduzione</string> <string name="lbl_playback">Ora in riproduzione</string>
<string name="lbl_play">Riproduci</string> <string name="lbl_play">Riproduci</string>
<string name="lbl_shuffle">Mescola</string> <string name="lbl_shuffle">Mescola</string>
<string name="set_playback_mode_all">Riproduci da tutte le canzoni</string> <string name="set_playback_mode_songs">Riproduci da tutte le canzoni</string>
<string name="set_playback_mode_album">Riproduci dal disco</string> <string name="set_playback_mode_album">Riproduci dal disco</string>
<string name="set_playback_mode_artist">Riproduci dall\'artista</string> <string name="set_playback_mode_artist">Riproduci dall\'artista</string>
<string name="lbl_queue">Coda</string> <string name="lbl_queue">Coda</string>

View file

@ -27,7 +27,7 @@
<string name="lbl_playback">지금 재생 중</string> <string name="lbl_playback">지금 재생 중</string>
<string name="lbl_play">재생</string> <string name="lbl_play">재생</string>
<string name="lbl_shuffle">셔플</string> <string name="lbl_shuffle">셔플</string>
<string name="set_playback_mode_all">모든 곡에서 재생</string> <string name="set_playback_mode_songs">모든 곡에서 재생</string>
<string name="set_playback_mode_album">앨범에서 재생</string> <string name="set_playback_mode_album">앨범에서 재생</string>
<string name="set_playback_mode_artist">아티스트에서 재생</string> <string name="set_playback_mode_artist">아티스트에서 재생</string>
<string name="lbl_queue">대기열</string> <string name="lbl_queue">대기열</string>

View file

@ -176,7 +176,7 @@
<string name="set_dirs_mode_exclude_desc">Muzika <b>nebus</b> įkeliama iš pridėtų aplankų jūs pridėsite.</string> <string name="set_dirs_mode_exclude_desc">Muzika <b>nebus</b> įkeliama iš pridėtų aplankų jūs pridėsite.</string>
<string name="set_dirs_mode_include">Įtraukti</string> <string name="set_dirs_mode_include">Įtraukti</string>
<string name="desc_clear_queue_item">Pašalinti šią eilės dainą</string> <string name="desc_clear_queue_item">Pašalinti šią eilės dainą</string>
<string name="set_playback_mode_all">Groti iš visų dainų</string> <string name="set_playback_mode_songs">Groti iš visų dainų</string>
<string name="set_playback_mode_none">Groti iš parodyto elemento</string> <string name="set_playback_mode_none">Groti iš parodyto elemento</string>
<string name="set_playback_mode_album">Groti iš albumo</string> <string name="set_playback_mode_album">Groti iš albumo</string>
<string name="set_playback_mode_artist">Groti iš atlikėjo</string> <string name="set_playback_mode_artist">Groti iš atlikėjo</string>

View file

@ -17,7 +17,7 @@
<string name="lbl_sort_asc">Oplopend</string> <string name="lbl_sort_asc">Oplopend</string>
<string name="lbl_play">Afspelen</string> <string name="lbl_play">Afspelen</string>
<string name="lbl_shuffle">Shuffle</string> <string name="lbl_shuffle">Shuffle</string>
<string name="set_playback_mode_all">Speel van alle nummers </string> <string name="set_playback_mode_songs">Speel van alle nummers </string>
<string name="set_playback_mode_album">Speel af van album </string> <string name="set_playback_mode_album">Speel af van album </string>
<string name="set_playback_mode_artist">Speel van artiest </string> <string name="set_playback_mode_artist">Speel van artiest </string>
<string name="lbl_playback">Afspeelscherm</string> <string name="lbl_playback">Afspeelscherm</string>

View file

@ -136,7 +136,7 @@
<string name="lbl_equalizer">Equalizer</string> <string name="lbl_equalizer">Equalizer</string>
<string name="lbl_size">Rozmiar</string> <string name="lbl_size">Rozmiar</string>
<string name="err_no_dirs">Brak folderów</string> <string name="err_no_dirs">Brak folderów</string>
<string name="set_playback_mode_all">Odtwórz wszystkie utwory</string> <string name="set_playback_mode_songs">Odtwórz wszystkie utwory</string>
<string name="set_playback_mode_album">Odtwórz album</string> <string name="set_playback_mode_album">Odtwórz album</string>
<string name="set_headset_autoplay_desc">Zacznij odtawrzanie po podłączeniu słuchawek (może nie działać na wszystkich urządzeniach)</string> <string name="set_headset_autoplay_desc">Zacznij odtawrzanie po podłączeniu słuchawek (może nie działać na wszystkich urządzeniach)</string>
<string name="set_reindex">Załaduj ponownie bibliotekę</string> <string name="set_reindex">Załaduj ponownie bibliotekę</string>

View file

@ -166,7 +166,7 @@
<string name="set_round_mode_desc">Ativar cantos arredondados em elementos adicionais da interface do usuário (requer que as capas dos álbuns sejam arredondadas)</string> <string name="set_round_mode_desc">Ativar cantos arredondados em elementos adicionais da interface do usuário (requer que as capas dos álbuns sejam arredondadas)</string>
<string name="set_replay_gain">Modo de normalização de volume (ReplayGain)</string> <string name="set_replay_gain">Modo de normalização de volume (ReplayGain)</string>
<string name="set_playback_mode_none">Reproduzir a partir do item mostrado</string> <string name="set_playback_mode_none">Reproduzir a partir do item mostrado</string>
<string name="set_playback_mode_all">Reproduzir de todas as músicas</string> <string name="set_playback_mode_songs">Reproduzir de todas as músicas</string>
<string name="set_replay_gain_album">Preferir álbum</string> <string name="set_replay_gain_album">Preferir álbum</string>
<string name="set_replay_gain_dynamic">Prefira o álbum se estiver tocando</string> <string name="set_replay_gain_dynamic">Prefira o álbum se estiver tocando</string>
<string name="set_observing">Recarregamento automático</string> <string name="set_observing">Recarregamento automático</string>

View file

@ -189,7 +189,7 @@
<string name="set_replay_gain">Estratégia do ganho de repetição</string> <string name="set_replay_gain">Estratégia do ganho de repetição</string>
<string name="set_replay_gain_album">Preferir álbum</string> <string name="set_replay_gain_album">Preferir álbum</string>
<string name="set_pre_amp_desc">O pré-amplificador é aplicado ao ajuste existente durante a reprodução</string> <string name="set_pre_amp_desc">O pré-amplificador é aplicado ao ajuste existente durante a reprodução</string>
<string name="set_playback_mode_all">Reproduzir de todas as músicas</string> <string name="set_playback_mode_songs">Reproduzir de todas as músicas</string>
<string name="set_repeat_pause_desc">Pausa quando uma música se repete</string> <string name="set_repeat_pause_desc">Pausa quando uma música se repete</string>
<string name="set_wipe_desc">Limpe o estado de reprodução salvo anteriormente (se houver)</string> <string name="set_wipe_desc">Limpe o estado de reprodução salvo anteriormente (se houver)</string>
<string name="set_restore_state">Restaurar o estado de reprodução</string> <string name="set_restore_state">Restaurar o estado de reprodução</string>

View file

@ -23,7 +23,7 @@
<string name="lbl_playback">Сейчас играет</string> <string name="lbl_playback">Сейчас играет</string>
<string name="lbl_play">Играть</string> <string name="lbl_play">Играть</string>
<string name="lbl_shuffle">Перемешать</string> <string name="lbl_shuffle">Перемешать</string>
<string name="set_playback_mode_all">Играть все треки</string> <string name="set_playback_mode_songs">Играть все треки</string>
<string name="set_playback_mode_album">Играть альбом</string> <string name="set_playback_mode_album">Играть альбом</string>
<string name="set_playback_mode_artist">Играть исполнителя</string> <string name="set_playback_mode_artist">Играть исполнителя</string>
<string name="lbl_queue">Очередь</string> <string name="lbl_queue">Очередь</string>

View file

@ -94,7 +94,7 @@
<string name="set_pre_amp_with">Etiket ile ayarla</string> <string name="set_pre_amp_with">Etiket ile ayarla</string>
<string name="set_pre_amp_warning">Uyarı: Ön amfinin yüksek bir pozitif değere değiştirilmesi bazı ses parçalarında pik yapmaya neden olabilir.</string> <string name="set_pre_amp_warning">Uyarı: Ön amfinin yüksek bir pozitif değere değiştirilmesi bazı ses parçalarında pik yapmaya neden olabilir.</string>
<string name="set_playback_mode_none">Gösterilen öğeden çal</string> <string name="set_playback_mode_none">Gösterilen öğeden çal</string>
<string name="set_playback_mode_all">Tüm şarkılardan çal</string> <string name="set_playback_mode_songs">Tüm şarkılardan çal</string>
<string name="set_playback_mode_album">Albümden çal</string> <string name="set_playback_mode_album">Albümden çal</string>
<string name="set_dirs">Müzik klasörleri</string> <string name="set_dirs">Müzik klasörleri</string>
<string name="set_dirs_mode_include_desc">Müzik <b>yalnızca</b> eklediğiniz klasörlerden yüklenecektir.</string> <string name="set_dirs_mode_include_desc">Müzik <b>yalnızca</b> eklediğiniz klasörlerden yüklenecektir.</string>

View file

@ -23,7 +23,7 @@
<string name="lbl_playback">正在播放</string> <string name="lbl_playback">正在播放</string>
<string name="lbl_play">播放</string> <string name="lbl_play">播放</string>
<string name="lbl_shuffle">随机</string> <string name="lbl_shuffle">随机</string>
<string name="set_playback_mode_all">从全部歌曲开始播放</string> <string name="set_playback_mode_songs">从全部歌曲开始播放</string>
<string name="set_playback_mode_album">从专辑开始播放</string> <string name="set_playback_mode_album">从专辑开始播放</string>
<string name="set_playback_mode_artist">从艺术家播放</string> <string name="set_playback_mode_artist">从艺术家播放</string>
<string name="lbl_queue">播放队列</string> <string name="lbl_queue">播放队列</string>

View file

@ -99,22 +99,25 @@
</integer-array> </integer-array>
<string-array name="entries_library_song_playback_mode"> <string-array name="entries_library_song_playback_mode">
<item>@string/set_playback_mode_all</item> <item>@string/set_playback_mode_songs</item>
<item>@string/set_playback_mode_artist</item> <item>@string/set_playback_mode_artist</item>
<item>@string/set_playback_mode_album</item> <item>@string/set_playback_mode_album</item>
<item>@string/set_playback_mode_genre</item>
</string-array> </string-array>
<integer-array name="values_library_song_playback_mode"> <integer-array name="values_library_song_playback_mode">
<item>@integer/music_mode_songs</item> <item>@integer/music_mode_songs</item>
<item>@integer/music_mode_artist</item> <item>@integer/music_mode_artist</item>
<item>@integer/music_mode_album</item> <item>@integer/music_mode_album</item>
<item>@integer/music_mode_genre</item>
</integer-array> </integer-array>
<string-array name="entries_detail_song_playback_mode"> <string-array name="entries_detail_song_playback_mode">
<item>@string/set_playback_mode_none</item> <item>@string/set_playback_mode_none</item>
<item>@string/set_playback_mode_all</item> <item>@string/set_playback_mode_songs</item>
<item>@string/set_playback_mode_artist</item> <item>@string/set_playback_mode_artist</item>
<item>@string/set_playback_mode_album</item> <item>@string/set_playback_mode_album</item>
<item>@string/set_playback_mode_genre</item>
</string-array> </string-array>
<integer-array name="values_detail_song_playback_mode"> <integer-array name="values_detail_song_playback_mode">
@ -122,6 +125,7 @@
<item>@integer/music_mode_songs</item> <item>@integer/music_mode_songs</item>
<item>@integer/music_mode_artist</item> <item>@integer/music_mode_artist</item>
<item>@integer/music_mode_album</item> <item>@integer/music_mode_album</item>
<item>@integer/music_mode_genre</item>
</integer-array> </integer-array>
<string-array name="entries_replay_gain"> <string-array name="entries_replay_gain">
@ -141,6 +145,7 @@
<integer name="theme_dark">2</integer> <integer name="theme_dark">2</integer>
<integer name="music_mode_none">-2147483648</integer> <integer name="music_mode_none">-2147483648</integer>
<integer name="music_mode_genre">0xA108</integer>
<integer name="music_mode_artist">0xA109</integer> <integer name="music_mode_artist">0xA109</integer>
<integer name="music_mode_album">0xA10A</integer> <integer name="music_mode_album">0xA10A</integer>
<integer name="music_mode_songs">0xA10B</integer> <integer name="music_mode_songs">0xA10B</integer>

View file

@ -202,9 +202,10 @@
<string name="set_library_song_playback_mode">When playing from the library</string> <string name="set_library_song_playback_mode">When playing from the library</string>
<string name="set_detail_song_playback_mode">When playing from item details</string> <string name="set_detail_song_playback_mode">When playing from item details</string>
<string name="set_playback_mode_none">Play from shown item</string> <string name="set_playback_mode_none">Play from shown item</string>
<string name="set_playback_mode_all">Play from all songs</string> <string name="set_playback_mode_songs">Play from all songs</string>
<string name="set_playback_mode_album">Play from album</string> <string name="set_playback_mode_album">Play from album</string>
<string name="set_playback_mode_artist">Play from artist</string> <string name="set_playback_mode_artist">Play from artist</string>
<string name="set_playback_mode_genre">Play from genre</string>
<string name="set_keep_shuffle">Remember shuffle</string> <string name="set_keep_shuffle">Remember shuffle</string>
<string name="set_keep_shuffle_desc">Keep shuffle on when playing a new song</string> <string name="set_keep_shuffle_desc">Keep shuffle on when playing a new song</string>
<string name="set_rewind_prev">Rewind before skipping back</string> <string name="set_rewind_prev">Rewind before skipping back</string>