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:
parent
cce7b766d7
commit
195ea074ca
41 changed files with 306 additions and 131 deletions
|
@ -74,10 +74,9 @@ class AuxioApp : Application(), ImageLoaderFactory {
|
|||
.build()
|
||||
|
||||
companion object {
|
||||
/** The ID of the "Shuffle All" shortcut. */
|
||||
const val SHORTCUT_SHUFFLE_ID = "shortcut_shuffle"
|
||||
|
||||
/** The [Intent] name for the "Shuffle All" shortcut. */
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ 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 */
|
||||
|
|
|
@ -128,9 +128,10 @@ class MainFragment :
|
|||
// --- VIEWMODEL SETUP ---
|
||||
collect(navModel.mainNavigationAction, ::handleMainNavigation)
|
||||
collect(navModel.exploreNavigationItem, ::handleExploreNavigation)
|
||||
collect(navModel.exploreNavigationArtists, ::handleExplorePicker)
|
||||
collect(navModel.exploreArtistNavigationItem, ::handleArtistNavigationPicker)
|
||||
collectImmediately(playbackModel.song, ::updateSong)
|
||||
collect(playbackModel.artistPlaybackPickerSong, ::handlePlaybackArtistPicker)
|
||||
collect(playbackModel.artistPickerSong, ::handlePlaybackArtistPicker)
|
||||
collect(playbackModel.genrePickerSong, ::handlePlaybackGenrePicker)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
@ -278,13 +279,11 @@ class MainFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleExplorePicker(items: List<Artist>?) {
|
||||
if (items != null) {
|
||||
// Navigate to the analogous artist picker dialog.
|
||||
private fun handleArtistNavigationPicker(item: Music?) {
|
||||
if (item != null) {
|
||||
navModel.mainNavigateTo(
|
||||
MainNavigationAction.Directions(
|
||||
MainFragmentDirections.actionPickNavigationArtist(
|
||||
items.map { it.uid }.toTypedArray())))
|
||||
MainFragmentDirections.actionPickNavigationArtist(item.uid)))
|
||||
navModel.finishExploreNavigation()
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +298,6 @@ class MainFragment :
|
|||
|
||||
private fun handlePlaybackArtistPicker(song: Song?) {
|
||||
if (song != null) {
|
||||
// Navigate to the analogous artist picker dialog.
|
||||
navModel.mainNavigateTo(
|
||||
MainNavigationAction.Directions(
|
||||
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() {
|
||||
val binding = requireBinding()
|
||||
val playbackSheetBehavior =
|
||||
|
|
|
@ -128,14 +128,14 @@ class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAd
|
|||
|
||||
override fun onRealClick(music: Music) {
|
||||
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
|
||||
// behavior since a song can only have one album.
|
||||
null,
|
||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
|
||||
MusicMode.SONGS -> playbackModel.playFromAll(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() {
|
||||
navModel.exploreNavigateTo(unlikelyToBeNull(detailModel.currentAlbum.value).artists)
|
||||
navModel.exploreNavigateToParentArtist(unlikelyToBeNull(detailModel.currentAlbum.value))
|
||||
}
|
||||
|
||||
private fun updateAlbum(album: Album?) {
|
||||
|
@ -180,7 +180,6 @@ class AlbumDetailFragment : ListFragment<FragmentDetailBinding>(), AlbumDetailAd
|
|||
findNavController().navigateUp()
|
||||
return
|
||||
}
|
||||
|
||||
requireBinding().detailToolbar.title = album.resolveName(requireContext())
|
||||
}
|
||||
|
||||
|
|
|
@ -124,16 +124,16 @@ class ArtistDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapte
|
|||
override fun onRealClick(music: Music) {
|
||||
when (music) {
|
||||
is Song -> {
|
||||
when (val mode = Settings(requireContext()).detailPlaybackMode) {
|
||||
// "Play from selected item" and "Play from artist" differ, as the latter
|
||||
// actually should show a picker choice in the case of multiple artists.
|
||||
when (Settings(requireContext()).detailPlaybackMode) {
|
||||
// When configured to play from the selected item, we already have an Artist
|
||||
// to play from.
|
||||
null ->
|
||||
playbackModel.playFromArtist(
|
||||
music, unlikelyToBeNull(detailModel.currentArtist.value))
|
||||
MusicMode.SONGS -> playbackModel.playFromAll(music)
|
||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
|
||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
|
||||
else -> error("Unexpected playback mode: $mode")
|
||||
MusicMode.GENRES -> playbackModel.playFromGenre(music)
|
||||
}
|
||||
}
|
||||
is Album -> navModel.exploreNavigateTo(music)
|
||||
|
|
|
@ -124,15 +124,16 @@ class GenreDetailFragment : ListFragment<FragmentDetailBinding>(), DetailAdapter
|
|||
when (music) {
|
||||
is Artist -> navModel.exploreNavigateTo(music)
|
||||
is Song ->
|
||||
when (val mode = Settings(requireContext()).detailPlaybackMode) {
|
||||
// Only way to play from the genre is through "Play from selected item".
|
||||
when (Settings(requireContext()).detailPlaybackMode) {
|
||||
// When configured to play from the selected item, we already have a Genre
|
||||
// to play from.
|
||||
null ->
|
||||
playbackModel.playFromGenre(
|
||||
music, unlikelyToBeNull(detailModel.currentGenre.value))
|
||||
MusicMode.SONGS -> playbackModel.playFromAll(music)
|
||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
|
||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
|
||||
else -> error("Unexpected playback mode: $mode")
|
||||
MusicMode.GENRES -> playbackModel.playFromGenre(music)
|
||||
}
|
||||
else -> error("Unexpected datatype: ${music::class.simpleName}")
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
|
|||
override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
// DetailViewModel handles most initialization from the navigation argument.
|
||||
detailModel.setSongUid(args.songUid)
|
||||
detailModel.setSongUid(args.itemUid)
|
||||
collectImmediately(detailModel.currentSong, ::updateSong)
|
||||
}
|
||||
|
||||
|
|
|
@ -155,9 +155,7 @@ class HomeFragment :
|
|||
collect(homeModel.shouldRecreate, ::handleRecreate)
|
||||
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
|
||||
collectImmediately(homeModel.songLists, homeModel.isFastScrolling, ::updateFab)
|
||||
|
||||
collectImmediately(musicModel.indexerState, ::updateIndexerState)
|
||||
|
||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||
collectImmediately(selectionModel.selected, ::updateSelection)
|
||||
}
|
||||
|
@ -484,9 +482,7 @@ class HomeFragment :
|
|||
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()
|
||||
|
|
|
@ -132,11 +132,11 @@ class SongListFragment :
|
|||
|
||||
override fun onRealClick(music: Music) {
|
||||
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.ALBUMS -> playbackModel.playFromAlbum(music)
|
||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
|
||||
else -> error("Unexpected playback mode: $mode")
|
||||
MusicMode.GENRES -> playbackModel.playFromGenre(music)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), Selecta
|
|||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_go_artist -> {
|
||||
navModel.exploreNavigateTo(song.artists)
|
||||
navModel.exploreNavigateToParentArtist(song)
|
||||
}
|
||||
R.id.action_go_album -> {
|
||||
navModel.exploreNavigateTo(song.album)
|
||||
|
@ -134,7 +134,7 @@ abstract class ListFragment<VB : ViewBinding> : SelectionFragment<VB>(), Selecta
|
|||
requireContext().showToast(R.string.lng_queue_added)
|
||||
}
|
||||
R.id.action_go_artist -> {
|
||||
navModel.exploreNavigateTo(album.artists)
|
||||
navModel.exploreNavigateToParentArtist(album)
|
||||
}
|
||||
else -> {
|
||||
error("Unexpected menu item selected")
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.util.context
|
|||
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.
|
||||
* @author OxygenCobalt.
|
||||
*/
|
||||
|
|
|
@ -31,12 +31,12 @@ import org.oxycblt.auxio.ui.NavigationViewModel
|
|||
*/
|
||||
class ArtistNavigationPickerDialog : ArtistPickerDialog() {
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
// Information about what artists to display is initially within the navigation arguments
|
||||
// as a list of UIDs, as that is the only safe way to parcel an artist.
|
||||
// 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: ArtistNavigationPickerDialogArgs by navArgs()
|
||||
|
||||
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
|
||||
pickerModel.setArtistUids(args.artistUids)
|
||||
pickerModel.setItemUid(args.itemUid)
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
}
|
||||
|
||||
|
|
|
@ -52,11 +52,9 @@ abstract class ArtistPickerDialog :
|
|||
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
|
||||
binding.pickerRecycler.adapter = artistAdapter
|
||||
|
||||
collectImmediately(pickerModel.currentArtists) { artists ->
|
||||
if (!artists.isNullOrEmpty()) {
|
||||
collectImmediately(pickerModel.artistChoices) { artists ->
|
||||
if (artists.isNotEmpty()) {
|
||||
// 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)
|
||||
} else {
|
||||
// Not showing any choices, navigate up.
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.navigation.fragment.navArgs
|
|||
import org.oxycblt.auxio.databinding.DialogMusicPickerBinding
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
|
||||
|
@ -31,19 +32,21 @@ import org.oxycblt.auxio.util.androidActivityViewModels
|
|||
*/
|
||||
class ArtistPlaybackPickerDialog : ArtistPickerDialog() {
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
// Information about what artists to display is initially within the navigation arguments
|
||||
// as a list of UIDs, as that is the only safe way to parcel an artist.
|
||||
// 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: ArtistPlaybackPickerDialogArgs by navArgs()
|
||||
|
||||
override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) {
|
||||
pickerModel.setSongUid(args.songUid)
|
||||
pickerModel.setItemUid(args.itemUid)
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onClick(item: Item) {
|
||||
super.onClick(item)
|
||||
check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" }
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -20,10 +20,7 @@ package org.oxycblt.auxio.music.picker
|
|||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.*
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
|
@ -32,24 +29,21 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class PickerViewModel : ViewModel(), MusicStore.Callback {
|
||||
// TODO: Refactor
|
||||
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
|
||||
private val _currentSong = MutableStateFlow<Song?>(null)
|
||||
/**
|
||||
* The current [Song] whose choices are being shown in the picker. Null if there is no [Song].
|
||||
*/
|
||||
val currentSong: StateFlow<Song?>
|
||||
get() = _currentSong
|
||||
private val _currentItem = MutableStateFlow<Music?>(null)
|
||||
/** The current item whose artists should be shown in the picker. Null if there is no item. */
|
||||
val currentItem: StateFlow<Music?> get() = _currentItem
|
||||
|
||||
private val _currentArtists = MutableStateFlow<List<Artist>?>(null)
|
||||
/**
|
||||
* The current [Artist] whose choices are being shown in the picker. Null/Empty if there is
|
||||
* none.
|
||||
*/
|
||||
val currentArtists: StateFlow<List<Artist>?>
|
||||
get() = _currentArtists
|
||||
private val _artistChoices = MutableStateFlow<List<Artist>>(listOf())
|
||||
/** The current [Artist] choices. Empty if no item is shown in the picker. */
|
||||
val artistChoices: StateFlow<List<Artist>>
|
||||
get() = _artistChoices
|
||||
|
||||
private val _genreChoices = MutableStateFlow<List<Genre>>(listOf())
|
||||
/** The current [Genre] choices. Empty if no item is shown in the picker. */
|
||||
val genreChoices: StateFlow<List<Genre>>
|
||||
get() = _genreChoices
|
||||
|
||||
override fun onCleared() {
|
||||
musicStore.removeCallback(this)
|
||||
|
@ -57,37 +51,29 @@ class PickerViewModel : ViewModel(), MusicStore.Callback {
|
|||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
if (library != null) {
|
||||
// If we are showing any item right now, we will need to refresh it (and any information
|
||||
// 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) }
|
||||
}
|
||||
refreshChoices()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun setSongUid(uid: Music.UID) {
|
||||
fun setItemUid(uid: Music.UID) {
|
||||
val library = unlikelyToBeNull(musicStore.library)
|
||||
_currentSong.value = library.find(uid)
|
||||
_currentArtists.value = _currentSong.value?.artists
|
||||
_currentItem.value = library.find(uid)
|
||||
refreshChoices()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new [currentArtists] list from a list of [Music.UID]'s.
|
||||
* @param uids The [Music.UID]s of the [Artist]s to [currentArtists] to.
|
||||
*/
|
||||
fun setArtistUids(uids: Array<Music.UID>) {
|
||||
val library = unlikelyToBeNull(musicStore.library)
|
||||
// Map the UIDs to artist instances and filter out the ones that can't be found.
|
||||
_currentArtists.value = uids.mapNotNull { library.find<Artist>(it) }.ifEmpty { null }
|
||||
private fun refreshChoices() {
|
||||
when (val item = _currentItem.value) {
|
||||
is Song -> {
|
||||
_artistChoices.value = item.artists
|
||||
_genreChoices.value = item.genres
|
||||
}
|
||||
is Album -> _artistChoices.value = item.artists
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -210,7 +210,7 @@ class PlaybackPanelFragment :
|
|||
/** Navigate to one of the currently playing [Song]'s Artists. */
|
||||
private fun navigateToCurrentArtist() {
|
||||
val song = playbackModel.song.value ?: return
|
||||
navModel.exploreNavigateTo(song.artists)
|
||||
navModel.exploreNavigateToParentArtist(song)
|
||||
}
|
||||
|
||||
/** Navigate to the currently playing [Song]'s albums. */
|
||||
|
|
|
@ -70,13 +70,21 @@ class PlaybackViewModel(application: Application) :
|
|||
|
||||
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.
|
||||
* @see playFromArtist
|
||||
*/
|
||||
val artistPlaybackPickerSong: StateFlow<Song?>
|
||||
val artistPickerSong: StateFlow<Song?>
|
||||
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
|
||||
* available.
|
||||
|
@ -178,11 +186,18 @@ class PlaybackViewModel(application: Application) :
|
|||
/**
|
||||
* PLay a [Song] from one of it's [Genre]s.
|
||||
* @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) {
|
||||
check(genre.songs.contains(song)) { "Invalid input: Genre is not linked to song" }
|
||||
playbackManager.play(song, genre, settings)
|
||||
fun playFromGenre(song: Song, genre: Genre? = null) {
|
||||
if (genre != null) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -137,11 +137,11 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
|||
override fun onRealClick(music: Music) {
|
||||
when (music) {
|
||||
is Song ->
|
||||
when (val mode = Settings(requireContext()).libPlaybackMode) {
|
||||
when (Settings(requireContext()).libPlaybackMode) {
|
||||
MusicMode.SONGS -> playbackModel.playFromAll(music)
|
||||
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
|
||||
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
|
||||
else -> error("Unexpected playback mode: $mode")
|
||||
MusicMode.GENRES -> playbackModel.playFromGenre(music)
|
||||
}
|
||||
is MusicParent -> navModel.exploreNavigateTo(music)
|
||||
}
|
||||
|
|
|
@ -113,10 +113,11 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
|
||||
fun Int.migratePlaybackMode() =
|
||||
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_IN_ARTIST -> MusicMode.ARTISTS
|
||||
IntegerTable.PLAYBACK_MODE_IN_ALBUM -> MusicMode.ALBUMS
|
||||
IntegerTable.PLAYBACK_MODE_IN_GENRE -> MusicMode.GENRES
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
|
@ -21,8 +21,10 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.navigation.NavDirections
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/** A [ViewModel] that handles complicated navigation functionality. */
|
||||
|
@ -42,14 +44,14 @@ class NavigationViewModel : ViewModel() {
|
|||
val exploreNavigationItem: StateFlow<Music?>
|
||||
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
|
||||
* to is ambiguous. Only intended for use by MainFragment, as the resolved choice will
|
||||
* Variation of [exploreNavigationItem] for situations where the choice of parent [Artist] to
|
||||
* navigate to is ambiguous. Only intended for use by MainFragment, as the resolved choice will
|
||||
* eventually be assigned to [exploreNavigationItem].
|
||||
*/
|
||||
val exploreNavigationArtists: StateFlow<List<Artist>?>
|
||||
get() = _exploreNavigationArtists
|
||||
val exploreArtistNavigationItem: StateFlow<Music?>
|
||||
get() = _exploreArtistNavigationItem
|
||||
|
||||
/**
|
||||
* 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].
|
||||
* @param artists The [Artist]s to navigate to. In the case of multiple artists, the user will
|
||||
* be prompted with a choice on which [Artist] to navigate to.
|
||||
* Navigate to one of the parent [Artist]'s of the given [Song].
|
||||
* @param song The [Song] to navigate with. If there are multiple parent [Artist]s,
|
||||
* a picker dialog will be shown.
|
||||
*/
|
||||
fun exploreNavigateTo(artists: List<Artist>) {
|
||||
if (_exploreNavigationArtists.value != null) {
|
||||
fun exploreNavigateToParentArtist(song: Song) {
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
@ -103,7 +118,7 @@ class NavigationViewModel : ViewModel() {
|
|||
exploreNavigateTo(artists[0])
|
||||
} else {
|
||||
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() {
|
||||
logD("Finishing explore navigation process")
|
||||
_exploreNavigationItem.value = null
|
||||
_exploreNavigationArtists.value = null
|
||||
_exploreArtistNavigationItem.value = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
<action
|
||||
android:id="@+id/action_pick_navigation_artist"
|
||||
app:destination="@id/artist_navigation_picker_dialog" />
|
||||
<action
|
||||
android:id="@+id/action_pick_playback_genre"
|
||||
app:destination="@id/genre_playback_picker_dialog" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
|
@ -31,7 +34,7 @@
|
|||
android:label="artist_playback_picker_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
<argument
|
||||
android:name="songUid"
|
||||
android:name="itemUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
|
@ -41,8 +44,18 @@
|
|||
android:label="artist_navigation_picker_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
<argument
|
||||
android:name="artistUids"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
|
||||
android:name="itemUid"
|
||||
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
|
||||
|
@ -51,7 +64,7 @@
|
|||
android:label="song_detail_dialog"
|
||||
tools:layout="@layout/dialog_song_detail">
|
||||
<argument
|
||||
android:name="songUid"
|
||||
android:name="itemUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="lbl_playback">يعمل الآن</string>
|
||||
<string name="lbl_play">تشغيل</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_artist">تشغيل من فنان</string>
|
||||
<string name="lbl_queue">طابور</string>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<string name="lbl_playback">Právě hraje</string>
|
||||
<string name="lbl_play">Přehrát</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_artist">Přehrát od umělce</string>
|
||||
<string name="lbl_queue">Fronta</string>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<string name="lbl_sort_asc">Aufsteigend</string>
|
||||
<string name="lbl_play">Abspielen</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="lbl_playback">Aktuelle Wiedergabe</string>
|
||||
<string name="lbl_queue">Warteschlange</string>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="lbl_playback">En reproducción</string>
|
||||
<string name="lbl_play">Reproducir</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_artist">Reproducir por artista</string>
|
||||
<string name="lbl_queue">Cola</string>
|
||||
|
|
|
@ -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_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_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_desc">Ponovo učitaj glazbenu biblioteku, koristeći predmemorirane oznake kada je to moguće</string>
|
||||
<string name="set_dirs">Mape glazbe</string>
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
<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_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_repeat_pause">Jeda pada pengulangan</string>
|
||||
<string name="set_rewind_prev">Putar balik sebelum melompat ke belakang</string>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="lbl_playback">Ora in riproduzione</string>
|
||||
<string name="lbl_play">Riproduci</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_artist">Riproduci dall\'artista</string>
|
||||
<string name="lbl_queue">Coda</string>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<string name="lbl_playback">지금 재생 중</string>
|
||||
<string name="lbl_play">재생</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_artist">아티스트에서 재생</string>
|
||||
<string name="lbl_queue">대기열</string>
|
||||
|
|
|
@ -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_include">Įtraukti</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_album">Groti iš albumo</string>
|
||||
<string name="set_playback_mode_artist">Groti iš atlikėjo</string>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<string name="lbl_sort_asc">Oplopend</string>
|
||||
<string name="lbl_play">Afspelen</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_artist">Speel van artiest </string>
|
||||
<string name="lbl_playback">Afspeelscherm</string>
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
<string name="lbl_equalizer">Equalizer</string>
|
||||
<string name="lbl_size">Rozmiar</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_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>
|
||||
|
|
|
@ -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_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_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_dynamic">Prefira o álbum se estiver tocando</string>
|
||||
<string name="set_observing">Recarregamento automático</string>
|
||||
|
|
|
@ -189,7 +189,7 @@
|
|||
<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_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_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>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="lbl_playback">Сейчас играет</string>
|
||||
<string name="lbl_play">Играть</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_artist">Играть исполнителя</string>
|
||||
<string name="lbl_queue">Очередь</string>
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<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_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_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>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="lbl_playback">正在播放</string>
|
||||
<string name="lbl_play">播放</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_artist">从艺术家播放</string>
|
||||
<string name="lbl_queue">播放队列</string>
|
||||
|
|
|
@ -99,22 +99,25 @@
|
|||
</integer-array>
|
||||
|
||||
<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_album</item>
|
||||
<item>@string/set_playback_mode_genre</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="values_library_song_playback_mode">
|
||||
<item>@integer/music_mode_songs</item>
|
||||
<item>@integer/music_mode_artist</item>
|
||||
<item>@integer/music_mode_album</item>
|
||||
<item>@integer/music_mode_genre</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="entries_detail_song_playback_mode">
|
||||
<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_album</item>
|
||||
<item>@string/set_playback_mode_genre</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="values_detail_song_playback_mode">
|
||||
|
@ -122,6 +125,7 @@
|
|||
<item>@integer/music_mode_songs</item>
|
||||
<item>@integer/music_mode_artist</item>
|
||||
<item>@integer/music_mode_album</item>
|
||||
<item>@integer/music_mode_genre</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="entries_replay_gain">
|
||||
|
@ -141,6 +145,7 @@
|
|||
<integer name="theme_dark">2</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_album">0xA10A</integer>
|
||||
<integer name="music_mode_songs">0xA10B</integer>
|
||||
|
|
|
@ -202,9 +202,10 @@
|
|||
<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_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_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_desc">Keep shuffle on when playing a new song</string>
|
||||
<string name="set_rewind_prev">Rewind before skipping back</string>
|
||||
|
|
Loading…
Reference in a new issue