From 25dd276bd81d49070cdd5b5d83cbabb04a1ffbfd Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 22 Dec 2021 16:31:26 -0700 Subject: [PATCH] playback: use single-queue system Switch auxio to a single-queue system. "Play next" adds songs to the top of the queue, similar to before, and then "Add to queue" adds songs to the bottom of the queue. This enables many more enhancements to be made to the playback experience, at the cost of a feature I preferred. Resolves #44. --- .../java/org/oxycblt/auxio/MainFragment.kt | 1 - .../auxio/detail/AlbumDetailFragment.kt | 8 +- .../auxio/detail/GenreDetailFragment.kt | 6 - .../home/AdaptiveFloatingActionButton.kt | 1 + .../auxio/playback/PlaybackFragment.kt | 15 +- .../auxio/playback/PlaybackViewModel.kt | 185 ++++-------------- .../auxio/playback/queue/QueueAdapter.kt | 30 +-- .../auxio/playback/queue/QueueDragCallback.kt | 26 ++- .../auxio/playback/queue/QueueFragment.kt | 5 +- .../playback/state/PlaybackStateDatabase.kt | 55 ++---- .../playback/state/PlaybackStateManager.kt | 167 +++++----------- .../auxio/settings/SettingsListFragment.kt | 2 +- .../java/org/oxycblt/auxio/ui/ActionMenu.kt | 22 ++- app/src/main/res/anim/anim_stationary.xml | 9 - app/src/main/res/menu/menu_album_actions.xml | 3 + app/src/main/res/menu/menu_album_detail.xml | 9 +- .../main/res/menu/menu_album_song_actions.xml | 3 + .../res/menu/menu_artist_album_actions.xml | 6 +- .../res/menu/menu_artist_song_actions.xml | 3 + app/src/main/res/menu/menu_song_actions.xml | 3 + app/src/main/res/values-cs/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-el/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 4 +- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-hu/strings.xml | 4 +- app/src/main/res/values-in/strings.xml | 4 +- app/src/main/res/values-it/strings.xml | 4 +- app/src/main/res/values-ko/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-pl/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-pt-rPT/strings.xml | 4 +- app/src/main/res/values-ro/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-tr/strings.xml | 4 +- app/src/main/res/values-uk/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values/settings.xml | 20 -- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/values/styles_ui.xml | 5 - info/ADDITIONS.md | 1 - 43 files changed, 187 insertions(+), 478 deletions(-) delete mode 100644 app/src/main/res/anim/anim_stationary.xml diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 4dffc6ff6..bab9092a7 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -29,7 +29,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.findNavController -import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.detail.DetailViewModel diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 5a433900e..c3e315fa4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -66,7 +66,7 @@ class AlbumDetailFragment : DetailFragment() { setupToolbar(detailModel.curAlbum.value!!, R.menu.menu_album_detail) { itemId -> if (itemId == R.id.action_queue_add) { - playbackModel.addToUserQueue(detailModel.curAlbum.value!!) + playbackModel.playNext(detailModel.curAlbum.value!!) requireContext().showToast(R.string.lbl_queue_added) true } else { @@ -147,12 +147,6 @@ class AlbumDetailFragment : DetailFragment() { } } - playbackModel.isInUserQueue.observe(viewLifecycleOwner) { inUserQueue -> - if (inUserQueue) { - detailAdapter.highlightSong(null, binding.detailRecycler) - } - } - logD("Fragment created.") return binding.root diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 2a1f471b7..5ba446e52 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -115,12 +115,6 @@ class GenreDetailFragment : DetailFragment() { } } - playbackModel.isInUserQueue.observe(viewLifecycleOwner) { inUserQueue -> - if (inUserQueue) { - detailAdapter.highlightSong(null, binding.detailRecycler) - } - } - logD("Fragment created.") return binding.root diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt index 90ffba753..4fec30a73 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt @@ -15,6 +15,7 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor( size = SIZE_NORMAL if (resources.configuration.smallestScreenWidthDp >= 640) { + // Use a large FAB on large screens, as it makes it easier to touch. customSize = resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_size) setMaxImageSize( resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_max_image_size) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index 5235d69c5..3d11de7a7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -131,8 +131,10 @@ class PlaybackFragment : Fragment() { binding.playbackSeekBar.setProgress(pos) } - playbackModel.displayQueue.observe(viewLifecycleOwner) { - updateQueueIcon(queueItem) + playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { + // The queue icon uses a selector that will automatically tint the icon as active or + // inactive. We just need to set the flag. + queueItem.isEnabled = playbackModel.nextItemsInQueue.value!!.isNotEmpty() } playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying -> @@ -155,13 +157,4 @@ class PlaybackFragment : Fragment() { // so we can't really do much (requireView().parent.parent.parent as PlaybackLayout).collapse() } - - private fun updateQueueIcon(queueItem: MenuItem) { - val userQueue = playbackModel.userQueue.value!! - val nextQueue = playbackModel.nextItemsInQueue.value!! - - // The queue icon uses a selector that will automatically tint the icon as active or - // inactive. We just need to set the flag. - queueItem.isEnabled = !(userQueue.isEmpty() && nextQueue.isEmpty()) - } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 3ac870a3b..9fd4bf613 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -21,24 +21,17 @@ package org.oxycblt.auxio.playback import android.content.Context import android.net.Uri import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch -import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.Header -import org.oxycblt.auxio.music.HeaderString import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.playback.queue.QueueAdapter import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackStateManager @@ -61,7 +54,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { // Queue private val mQueue = MutableLiveData(listOf()) - private val mUserQueue = MutableLiveData(listOf()) private val mIndex = MutableLiveData(0) private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS) @@ -69,7 +61,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { private val mIsPlaying = MutableLiveData(false) private val mIsShuffling = MutableLiveData(false) private val mLoopMode = MutableLiveData(LoopMode.NONE) - private val mIsInUserQueue = MutableLiveData(false) // Other private var mIntentUri: Uri? = null @@ -83,12 +74,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { /** The current queue determined by [playbackMode] and [parent] */ val queue: LiveData> get() = mQueue - /** The queue created by the user. */ - val userQueue: LiveData> get() = mUserQueue /** The current [PlaybackMode] that also determines the queue */ val playbackMode: LiveData get() = mMode - /** Whether playback is originating from the user-generated queue or not */ - val isInUserQueue: LiveData = mIsInUserQueue val isPlaying: LiveData get() = mIsPlaying val isShuffling: LiveData get() = mIsShuffling @@ -100,62 +87,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { queue.slice((mIndex.value!! + 1) until queue.size) } - /** The combined queue data used for UIs, with header data included */ - val displayQueue = MediatorLiveData>().apply { - val combine: (userQueue: List, nextQueue: List) -> List = - { userQueue, nextQueue -> - val queue = mutableListOf() - - if (userQueue.isNotEmpty()) { - queue += ActionHeader( - id = -2, - string = HeaderString.Single(R.string.lbl_next_user_queue), - icon = R.drawable.ic_clear, - desc = R.string.desc_clear_user_queue, - onClick = { playbackManager.clearUserQueue() } - ) - - queue += userQueue - } - - if (nextQueue.isNotEmpty()) { - val parentName = parent.value?.name - - queue += Header( - id = -3, - string = HeaderString.WithArg( - R.string.fmt_next_from, - if (parentName != null) { - HeaderString.Arg.Value(parentName) - } else { - HeaderString.Arg.Resource(R.string.lbl_all_songs) - } - ) - ) - - queue += nextQueue - } - - queue - } - - // Do not move these around. The transformed value must be generated through this - // observer call first before the userQueue source uses it assuming that it's not - // null. - addSource(nextItemsInQueue) { nextQueue -> - value = combine(userQueue.value!!, nextQueue) - } - - addSource(userQueue) { userQueue -> - value = combine( - userQueue, - requireNotNull(nextItemsInQueue.value) { - "Transformed value was not generated yet." - } - ) - } - } - private val playbackManager = PlaybackStateManager.maybeGetInstance() private val settingsManager = SettingsManager.getInstance() @@ -285,95 +216,65 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { } /** - * Remove an item at [adapterIndex], being a non-header data index. - * @param queueAdapter [QueueAdapter] instance to push changes to when successful. + * Remove a queue item using it's recyclerview adapter index. If the indices are valid, + * [apply] is called just before the change is committed so that the adapter can be updated. */ - fun removeQueueDataItem(adapterIndex: Int, queueAdapter: QueueAdapter) { - var index = adapterIndex.dec() + fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) { + val adjusted = adapterIndex + (mQueue.value!!.size - nextItemsInQueue.value!!.size) - // If the item is in the user queue, then remove it from there after accounting for the header. - if (index < mUserQueue.value!!.size) { - queueAdapter.removeItem(adapterIndex) - - playbackManager.removeUserQueueItem(index) - } else { - // Translate the indices into proper queue indices if removing an item from there. - index += (mQueue.value!!.size - nextItemsInQueue.value!!.size) - - if (userQueue.value!!.isNotEmpty()) { - index -= mUserQueue.value!!.size.inc() - } - - queueAdapter.removeItem(adapterIndex) - - playbackManager.removeQueueItem(index) + if (adjusted in mQueue.value!!.indices) { + apply() + playbackManager.removeQueueItem(adjusted) } } - /** - * Move a queue OR user queue item from [adapterFrom] to [adapterTo], as long as both - * indices are non-header data indices. - * @param queueAdapter [QueueAdapter] instance to push changes to when successful. + * Move queue items using their recyclerview adapter indices. If the indices are valid, + * [apply] is called just before the change is committed so that the adapter can be updated. */ - fun moveQueueDataItems( - adapterFrom: Int, - adapterTo: Int, - queueAdapter: QueueAdapter - ): Boolean { - var from = adapterFrom.dec() - var to = adapterTo.dec() + fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean { + val delta = (mQueue.value!!.size - nextItemsInQueue.value!!.size) - if (from < mUserQueue.value!!.size) { - // Ignore invalid movements to out of bounds, header, or queue positions - if (to >= mUserQueue.value!!.size || to < 0) return false - - queueAdapter.moveItems(adapterFrom, adapterTo) - - playbackManager.moveUserQueueItems(from, to) - } else { - // Ignore invalid movements to out of bounds or header positions - if (to < 0) return false - - // Get the real queue positions from the nextInQueue positions - val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size - - from += delta - to += delta - - if (userQueue.value!!.isNotEmpty()) { - // Ignore user queue positions - if (to <= mUserQueue.value!!.size.inc()) return false - - from -= mUserQueue.value!!.size.inc() - to -= mUserQueue.value!!.size.inc() - - // Ignore movements that are past the next songs - if (to <= mIndex.value!!) return false - } - - queueAdapter.moveItems(adapterFrom, adapterTo) + val from = adapterFrom + delta + val to = adapterTo + delta + if (from in mQueue.value!!.indices && to in mQueue.value!!.indices) { + apply() playbackManager.moveQueueItems(from, to) + return true } - return true + return false } /** - * Add a [Song] to the user queue. + * Add a [Song] to the top of the queue. */ - fun addToUserQueue(song: Song) { - playbackManager.addToUserQueue(song) + fun playNext(song: Song) { + playbackManager.playNext(song) } /** - * Add an [Album] to the user queue + * Add an [Album] to the top of the queue. */ - fun addToUserQueue(album: Album) { - playbackManager.addToUserQueue(settingsManager.detailAlbumSort.sortAlbum(album)) + fun playNext(album: Album) { + playbackManager.playNext(settingsManager.detailAlbumSort.sortAlbum(album)) } - // --- STATUS FUNCTIONS --- +/** + * Add a [Song] to the end of the queue. + */ + fun addToQueue(song: Song) { + playbackManager.addToQueue(song) + } + + /** + * Add an [Album] to the end of the queue. + */ + fun addToQueue(album: Album) { + playbackManager.addToQueue(settingsManager.detailAlbumSort.sortAlbum(album)) + } + +// --- STATUS FUNCTIONS --- /** * Flip the playing status, e.g from playing to paused @@ -405,7 +306,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { fun savePlaybackState(context: Context, onDone: () -> Unit) { viewModelScope.launch { playbackManager.saveStateToDatabase(context) - onDone() } } @@ -445,7 +345,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mParent.value = playbackManager.parent mQueue.value = playbackManager.queue mMode.value = playbackManager.playbackMode - mUserQueue.value = playbackManager.userQueue mIndex.value = playbackManager.index mIsPlaying.value = playbackManager.isPlaying mIsShuffling.value = playbackManager.isShuffling @@ -474,10 +373,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mQueue.value = queue } - override fun onUserQueueUpdate(userQueue: List) { - mUserQueue.value = userQueue - } - override fun onIndexUpdate(index: Int) { mIndex.value = index } @@ -497,8 +392,4 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { override fun onLoopUpdate(loopMode: LoopMode) { mLoopMode.value = loopMode } - - override fun onInUserQueueUpdate(isInUserQueue: Boolean) { - mIsInUserQueue.value = isInUserQueue - } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index 4f65d4cc6..8d665e511 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -39,7 +39,6 @@ import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.HeaderViewHolder import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE /** @@ -105,9 +104,7 @@ class QueueAdapter( * Used since [submitList] will cause QueueAdapter to freak out. */ fun moveItems(adapterFrom: Int, adapterTo: Int) { - val item = data.removeAt(adapterFrom) - data.add(adapterTo, item) - + data.add(adapterTo, data.removeAt(adapterFrom)) notifyItemMoved(adapterFrom, adapterTo) } @@ -117,30 +114,7 @@ class QueueAdapter( */ fun removeItem(adapterIndex: Int) { data.removeAt(adapterIndex) - - /* - * If the data from the next queue is now entirely empty [Signified by a header at the - * end, remove the next queue header as notify as such. - * - * If the user queue is empty [Signified by there being two headers at the beginning with - * nothing in between], then remove the user queue header and notify as such. - * - * Otherwise just remove the item as usual. - */ - if (data[data.lastIndex] is Header) { - logD("Queue is empty, removing header") - - val lastIndex = data.lastIndex - data.removeAt(lastIndex) - notifyItemRangeRemoved(lastIndex, 2) - } else if (data.lastIndex >= 1 && data[0] is ActionHeader && data[1] is Header) { - logD("User queue is empty, removing header") - - data.removeAt(0) - notifyItemRangeRemoved(0, 2) - } else { - notifyItemRemoved(adapterIndex) - } + notifyItemRemoved(adapterIndex) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 0c4560cec..67e2528ef 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -44,14 +44,9 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder - ): Int { - // Only allow dragging/swiping with the queue item ViewHolder, not the headers. - return if (viewHolder is QueueAdapter.QueueSongViewHolder) { - makeFlag( - ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN - ) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START) - } else 0 - } + ): Int = + makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or + makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START) override fun interpolateOutOfBoundsScroll( recyclerView: RecyclerView, @@ -152,15 +147,18 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { - return playbackModel.moveQueueDataItems( - viewHolder.bindingAdapterPosition, - target.bindingAdapterPosition, - queueAdapter - ) + val from = viewHolder.bindingAdapterPosition + val to = target.bindingAdapterPosition + + return playbackModel.moveQueueDataItems(from, to) { + queueAdapter.moveItems(from, to) + } } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter) + playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition) { + queueAdapter.removeItem(viewHolder.bindingAdapterPosition) + } } /** diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 6df3d3f83..2150fbcaa 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -30,8 +30,7 @@ import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.playback.PlaybackViewModel /** - * A [Fragment] that contains both the user queue and the next queue, with the ability to - * edit them as well. + * A [Fragment] that shows the queue and enables editing as well. * @author OxygenCobalt */ class QueueFragment : Fragment() { @@ -68,7 +67,7 @@ class QueueFragment : Fragment() { // --- VIEWMODEL SETUP ---- - playbackModel.displayQueue.observe(viewLifecycleOwner) { queue -> + playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { queue -> if (queue.isEmpty()) { findNavController().navigateUp() return@observe diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt index 9446fd9b9..e7558cb95 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt @@ -44,7 +44,10 @@ class PlaybackStateDatabase(context: Context) : createTable(db, TABLE_NAME_QUEUE) } - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db) + override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db) + + private fun nuke(db: SQLiteDatabase) { db.apply { execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE") execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE") @@ -53,10 +56,6 @@ class PlaybackStateDatabase(context: Context) : } } - override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - onUpgrade(db, newVersion, oldVersion) - } - // --- DATABASE CONSTRUCTION FUNCTIONS --- /** @@ -86,8 +85,7 @@ class PlaybackStateDatabase(context: Context) : .append("${StateColumns.COLUMN_QUEUE_INDEX} INTEGER NOT NULL,") .append("${StateColumns.COLUMN_PLAYBACK_MODE} INTEGER NOT NULL,") .append("${StateColumns.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,") - .append("${StateColumns.COLUMN_LOOP_MODE} INTEGER NOT NULL,") - .append("${StateColumns.COLUMN_IS_IN_USER_QUEUE} BOOLEAN NOT NULL)") + .append("${StateColumns.COLUMN_LOOP_MODE} INTEGER NOT NULL)") return command } @@ -98,8 +96,7 @@ class PlaybackStateDatabase(context: Context) : private fun constructQueueTable(command: StringBuilder): StringBuilder { command.append("${QueueColumns.ID} LONG PRIMARY KEY,") .append("${QueueColumns.SONG_HASH} INTEGER NOT NULL,") - .append("${QueueColumns.ALBUM_HASH} INTEGER NOT NULL,") - .append("${QueueColumns.IS_USER_QUEUE} BOOLEAN NOT NULL)") + .append("${QueueColumns.ALBUM_HASH} INTEGER NOT NULL)") return command } @@ -126,7 +123,6 @@ class PlaybackStateDatabase(context: Context) : put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt()) put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling) put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt()) - put(StateColumns.COLUMN_IS_IN_USER_QUEUE, state.isInUserQueue) } insert(TABLE_NAME_STATE, null, stateData) @@ -155,9 +151,6 @@ class PlaybackStateDatabase(context: Context) : val modeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_PLAYBACK_MODE) val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_IS_SHUFFLING) val loopModeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_LOOP_MODE) - val isInUserQueueIndex = cursor.getColumnIndexOrThrow( - StateColumns.COLUMN_IS_IN_USER_QUEUE - ) cursor.moveToFirst() @@ -184,7 +177,6 @@ class PlaybackStateDatabase(context: Context) : playbackMode = mode, isShuffling = cursor.getInt(shuffleIndex) == 1, loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE, - isInUserQueue = cursor.getInt(isInUserQueueIndex) == 1 ) } @@ -192,9 +184,9 @@ class PlaybackStateDatabase(context: Context) : } /** - * Write a [SavedQueue] to the database. + * Write a queue to the database. */ - fun writeQueue(queue: SavedQueue) { + fun writeQueue(queue: MutableList) { assertBackgroundThread() val database = writableDatabase @@ -205,12 +197,11 @@ class PlaybackStateDatabase(context: Context) : logD("Wiped queue db.") - writeQueueBatch(queue.user, true, 0) - writeQueueBatch(queue.queue, false, queue.user.size) + writeQueueBatch(queue, queue.size) } - private fun writeQueueBatch(queue: List, isUserQueue: Boolean, idStart: Int) { - logD("Beginning queue write [start: $idStart, userQueue: $isUserQueue]") + private fun writeQueueBatch(queue: List, idStart: Int) { + logD("Beginning queue write [start: $idStart]") val database = writableDatabase var position = 0 @@ -227,7 +218,6 @@ class PlaybackStateDatabase(context: Context) : put(QueueColumns.ID, idStart + i) put(QueueColumns.SONG_HASH, song.hash) put(QueueColumns.ALBUM_HASH, song.album.hash) - put(QueueColumns.IS_USER_QUEUE, isUserQueue) } insert(TABLE_NAME_QUEUE, null, itemData) @@ -243,29 +233,25 @@ class PlaybackStateDatabase(context: Context) : } /** - * Read a [SavedQueue] from this database. + * Read a list of queue items from this database. * @param musicStore Required to transform database songs into actual song instances */ - fun readQueue(musicStore: MusicStore): SavedQueue { + fun readQueue(musicStore: MusicStore): MutableList { assertBackgroundThread() - val queue = SavedQueue(mutableListOf(), mutableListOf()) + val queue = mutableListOf() readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor -> if (cursor.count == 0) return@queryAll val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH) val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH) - val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueColumns.IS_USER_QUEUE) while (cursor.moveToNext()) { - musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex))?.let { song -> - if (cursor.getInt(isUserQueueIndex) == 1) { - queue.user.add(song) - } else { - queue.queue.add(song) + musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex)) + ?.let { song -> + queue.add(song) } - } } } @@ -280,11 +266,8 @@ class PlaybackStateDatabase(context: Context) : val playbackMode: PlaybackMode, val isShuffling: Boolean, val loopMode: LoopMode, - val isInUserQueue: Boolean ) - data class SavedQueue(val user: MutableList, val queue: MutableList) - private object StateColumns { const val COLUMN_ID = "id" const val COLUMN_SONG_HASH = "song" @@ -294,19 +277,17 @@ class PlaybackStateDatabase(context: Context) : const val COLUMN_PLAYBACK_MODE = "playback_mode" const val COLUMN_IS_SHUFFLING = "is_shuffling" const val COLUMN_LOOP_MODE = "loop_mode" - const val COLUMN_IS_IN_USER_QUEUE = "is_in_user_queue" } private object QueueColumns { const val ID = "id" const val SONG_HASH = "song" const val ALBUM_HASH = "album" - const val IS_USER_QUEUE = "is_user_queue" } companion object { const val DB_NAME = "auxio_state_database.db" - const val DB_VERSION = 5 + const val DB_VERSION = 6 const val TABLE_NAME_STATE = "playback_state_table" const val TABLE_NAME_QUEUE = "queue_table" diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 85e4f49fb..e92aad70d 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -30,6 +30,8 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE +import kotlin.math.max +import kotlin.math.min /** * Master class (and possible god object) for the playback state. @@ -65,11 +67,6 @@ class PlaybackStateManager private constructor() { field = value callbacks.forEach { it.onQueueUpdate(value) } } - private var mUserQueue = mutableListOf() - set(value) { - field = value - callbacks.forEach { it.onUserQueueUpdate(value) } - } private var mIndex = 0 set(value) { field = value @@ -98,11 +95,7 @@ class PlaybackStateManager private constructor() { field = value callbacks.forEach { it.onLoopUpdate(value) } } - private var mIsInUserQueue = false - set(value) { - field = value - callbacks.forEach { it.onInUserQueueUpdate(value) } - } + private var mIsRestored = false private var mHasPlayed = false @@ -114,8 +107,6 @@ class PlaybackStateManager private constructor() { val position: Long get() = mPosition /** The current queue determined by [parent] and [playbackMode] */ val queue: List get() = mQueue - /** The queue created by the user. */ - val userQueue: List get() = mUserQueue /** The current index of the queue */ val index: Int get() = mIndex /** The current [PlaybackMode] */ @@ -250,8 +241,6 @@ class PlaybackStateManager private constructor() { * Update the playback to a new [song], doing all the required logic. */ private fun updatePlayback(song: Song, shouldPlay: Boolean = true) { - mIsInUserQueue = false - mSong = song mPosition = 0 @@ -264,29 +253,17 @@ class PlaybackStateManager private constructor() { * Go to the next song, along with doing all the checks that entails. */ fun next() { - // If there's anything in the user queue, go to the first song in there instead - // of incrementing the index. - if (mUserQueue.isNotEmpty()) { - updatePlayback(mUserQueue[0]) - mUserQueue.removeAt(0) - - // Mark that the playback state is currently in the user queue, for later. - mIsInUserQueue = true - - forceUserQueueUpdate() + // Increment the index, if it cannot be incremented any further, then + // loop and pause/resume playback depending on the setting + if (mIndex < mQueue.lastIndex) { + mIndex = mIndex.inc() + updatePlayback(mQueue[mIndex]) } else { - // Increment the index, if it cannot be incremented any further, then - // loop and pause/resume playback depending on the setting - if (mIndex < mQueue.lastIndex) { - mIndex = mIndex.inc() - updatePlayback(mQueue[mIndex]) - } else { - mIndex = 0 - updatePlayback(mQueue[mIndex], shouldPlay = mLoopMode == LoopMode.ALL) - } - - forceQueueUpdate() + mIndex = 0 + updatePlayback(mQueue[mIndex], shouldPlay = mLoopMode == LoopMode.ALL) } + + forceQueueUpdate() } /** @@ -297,9 +274,8 @@ class PlaybackStateManager private constructor() { if (settingsManager.rewindWithPrev && mPosition >= REWIND_THRESHOLD) { rewind() } else { - // Only decrement the index if there's a song to move back to AND if we are not exiting - // the user queue. - if (mIndex > 0 && !mIsInUserQueue) { + // Only decrement the index if there's a song to move back to + if (mIndex > 0) { mIndex = mIndex.dec() } @@ -348,63 +324,35 @@ class PlaybackStateManager private constructor() { } /** - * Add a [song] to the user queue. + * Add a [song] to the top of the queue. */ - fun addToUserQueue(song: Song) { - mUserQueue.add(song) - - forceUserQueueUpdate() + fun playNext(song: Song) { + mQueue.add(min(mIndex + 1, max(mQueue.lastIndex, 0)), song) + forceQueueUpdate() } /** - * Add a list of [songs] to the user queue. + * Add a list of [songs] to the top of the queue. */ - fun addToUserQueue(songs: List) { - mUserQueue.addAll(songs) - - forceUserQueueUpdate() + fun playNext(songs: List) { + mQueue.addAll(min(mIndex + 1, max(mQueue.lastIndex, 0)), songs) + forceQueueUpdate() } /** - * Remove a USER queue item at [index]. Will ignore invalid indexes. + * Add a [song] to the end of the queue. */ - fun removeUserQueueItem(index: Int) { - logD("Removing item ${mUserQueue[index].name}.") - - if (index > mUserQueue.size || index < 0) { - logE("Index is out of bounds, did not remove user queue item.") - - return - } - - mUserQueue.removeAt(index) - - forceUserQueueUpdate() + fun addToQueue(song: Song) { + mQueue.add(song) + forceQueueUpdate() } /** - * Move a USER queue item at [from] to a position at [to]. Will ignore invalid indexes. + * Add a list of [songs] to the end of the queue. */ - fun moveUserQueueItems(from: Int, to: Int) { - if (from > mUserQueue.size || from < 0 || to > mUserQueue.size || to < 0) { - logE("Indices were out of bounds, did not move user queue item") - - return - } - - val item = mUserQueue.removeAt(from) - mUserQueue.add(to, item) - - forceUserQueueUpdate() - } - - /** - * Clear the user queue. Forces a user queue update. - */ - fun clearUserQueue() { - mUserQueue.clear() - - forceUserQueueUpdate() + fun addToQueue(songs: List) { + mQueue.addAll(songs) + forceQueueUpdate() } /** @@ -414,36 +362,28 @@ class PlaybackStateManager private constructor() { mQueue = mQueue } - /** - * Force any callbacks to recieve a user queue update. - */ - private fun forceUserQueueUpdate() { - mUserQueue = mUserQueue - } - // --- SHUFFLE FUNCTIONS --- /** - * Set whether this instance is [shuffled]. Updates the queue accordingly + * Set whether this instance is [shuffled]. Updates the queue accordingly. * @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled */ fun setShuffling(shuffled: Boolean, keepSong: Boolean) { mIsShuffling = shuffled if (mIsShuffling) { - genShuffle(keepSong, mIsInUserQueue) + genShuffle(keepSong) } else { - resetShuffle(keepSong, mIsInUserQueue) + resetShuffle(keepSong) } } /** * Generate a new shuffled queue. * @param keepSong Whether the current song should be kept as the queue is shuffled - * @param useLastSong Whether to use the last song in the queue instead of the current one */ - private fun genShuffle(keepSong: Boolean, useLastSong: Boolean) { - val lastSong = if (useLastSong) mQueue[0] else mSong + private fun genShuffle(keepSong: Boolean) { + val lastSong = mSong logD("Shuffling queue") @@ -464,11 +404,10 @@ class PlaybackStateManager private constructor() { /** * Reset the queue to its normal, ordered state. * @param keepSong Whether the current song should be kept as the queue is unshuffled - * @param useLastSong Whether to use the previous song for the index calculations. */ - private fun resetShuffle(keepSong: Boolean, useLastSong: Boolean) { + private fun resetShuffle(keepSong: Boolean) { val musicStore = MusicStore.maybeGetInstance() ?: return - val lastSong = if (useLastSong) mQueue[mIndex] else mSong + val lastSong = mSong mQueue = when (mPlaybackMode) { PlaybackMode.ALL_SONGS -> @@ -585,11 +524,12 @@ class PlaybackStateManager private constructor() { database.writeState( PlaybackStateDatabase.SavedState( mSong, mPosition, mParent, mIndex, - mPlaybackMode, mIsShuffling, mLoopMode, mIsInUserQueue + mPlaybackMode, mIsShuffling, mLoopMode, ) ) - database.writeQueue(PlaybackStateDatabase.SavedQueue(mUserQueue, mQueue)) + // TODO: Re-add state saving + database.writeQueue(mQueue) this@PlaybackStateManager.logD( "Save finished in ${System.currentTimeMillis() - start}ms" @@ -608,7 +548,7 @@ class PlaybackStateManager private constructor() { val start: Long val playbackState: PlaybackStateDatabase.SavedState? - val queue: PlaybackStateDatabase.SavedQueue + val queue: MutableList withContext(Dispatchers.IO) { start = System.currentTimeMillis() @@ -622,7 +562,7 @@ class PlaybackStateManager private constructor() { // Get off the IO coroutine since it will cause LiveData updates to throw an exception if (playbackState != null) { - logD("Found playback state $playbackState with queue size ${queue.user.size + queue.queue.size}") + logD("Found playback state $playbackState") unpackFromPlaybackState(playbackState) unpackQueue(queue) @@ -649,30 +589,21 @@ class PlaybackStateManager private constructor() { mSong = playbackState.song mLoopMode = playbackState.loopMode mIsShuffling = playbackState.isShuffling - mIsInUserQueue = playbackState.isInUserQueue seekTo(playbackState.position) } - /** - * Unpack a list of queue items into a queue & user queue. - */ - private fun unpackQueue(queue: PlaybackStateDatabase.SavedQueue) { - mUserQueue = queue.user - mQueue = queue.queue + private fun unpackQueue(queue: MutableList) { + mQueue = queue - // When done, get a more accurate index to prevent issues with queue songs that were saved - // to the db but are now deleted when the restore occurred. - // Not done if in user queue because that could result in a bad index being created. - if (!mIsInUserQueue) { - mSong?.let { song -> - val index = mQueue.indexOf(song) - mIndex = if (index != -1) index else mIndex + // Sanity check: Ensure that the + mSong?.let { song -> + while (mQueue.getOrNull(mIndex) != song) { + mIndex-- } } forceQueueUpdate() - forceUserQueueUpdate() } /** @@ -702,14 +633,12 @@ class PlaybackStateManager private constructor() { fun onParentUpdate(parent: MusicParent?) {} fun onPositionUpdate(position: Long) {} fun onQueueUpdate(queue: List) {} - fun onUserQueueUpdate(userQueue: List) {} fun onModeUpdate(mode: PlaybackMode) {} fun onIndexUpdate(index: Int) {} fun onPlayingUpdate(isPlaying: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {} fun onLoopUpdate(loopMode: LoopMode) {} fun onSeek(position: Long) {} - fun onInUserQueueUpdate(isInUserQueue: Boolean) {} } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index be90d8cf2..1a95d5bf1 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -145,7 +145,7 @@ class SettingsListFragment : PreferenceFragmentCompat() { } } - SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS, -> { + SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS -> { onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt index b7251abb8..38d2867db 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -144,15 +144,31 @@ class ActionMenu( } } - R.id.action_queue_add -> { + R.id.action_play_next -> { when (data) { is Song -> { - playbackModel.addToUserQueue(data) + playbackModel.playNext(data) context.showToast(R.string.lbl_queue_added) } is Album -> { - playbackModel.addToUserQueue(data) + playbackModel.playNext(data) + context.showToast(R.string.lbl_queue_added) + } + + else -> {} + } + } + + R.id.action_queue_add -> { + when (data) { + is Song -> { + playbackModel.addToQueue(data) + context.showToast(R.string.lbl_queue_added) + } + + is Album -> { + playbackModel.addToQueue(data) context.showToast(R.string.lbl_queue_added) } diff --git a/app/src/main/res/anim/anim_stationary.xml b/app/src/main/res/anim/anim_stationary.xml deleted file mode 100644 index 5859dd2a9..000000000 --- a/app/src/main/res/anim/anim_stationary.xml +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/app/src/main/res/menu/menu_album_actions.xml b/app/src/main/res/menu/menu_album_actions.xml index 2908f7277..8fcb05935 100644 --- a/app/src/main/res/menu/menu_album_actions.xml +++ b/app/src/main/res/menu/menu_album_actions.xml @@ -6,6 +6,9 @@ + diff --git a/app/src/main/res/menu/menu_album_detail.xml b/app/src/main/res/menu/menu_album_detail.xml index e41cf7fba..e480d9191 100644 --- a/app/src/main/res/menu/menu_album_detail.xml +++ b/app/src/main/res/menu/menu_album_detail.xml @@ -1,8 +1,9 @@ - + + + android:title="@string/lbl_queue_add" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_album_song_actions.xml b/app/src/main/res/menu/menu_album_song_actions.xml index 9ee1c65e1..4c5d8d7d5 100644 --- a/app/src/main/res/menu/menu_album_song_actions.xml +++ b/app/src/main/res/menu/menu_album_song_actions.xml @@ -1,5 +1,8 @@ + diff --git a/app/src/main/res/menu/menu_artist_album_actions.xml b/app/src/main/res/menu/menu_artist_album_actions.xml index 4a06825bc..5078496da 100644 --- a/app/src/main/res/menu/menu_artist_album_actions.xml +++ b/app/src/main/res/menu/menu_artist_album_actions.xml @@ -9,8 +9,10 @@ android:id="@+id/action_shuffle" android:title="@string/lbl_shuffle" app:showAsAction="never" /> + + android:title="@string/lbl_queue_add" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_artist_song_actions.xml b/app/src/main/res/menu/menu_artist_song_actions.xml index 740462a65..e1e9c4a5d 100644 --- a/app/src/main/res/menu/menu_artist_song_actions.xml +++ b/app/src/main/res/menu/menu_artist_song_actions.xml @@ -1,5 +1,8 @@ + diff --git a/app/src/main/res/menu/menu_song_actions.xml b/app/src/main/res/menu/menu_song_actions.xml index d300e5716..1a795ddb5 100644 --- a/app/src/main/res/menu/menu_song_actions.xml +++ b/app/src/main/res/menu/menu_song_actions.xml @@ -1,5 +1,8 @@ + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 83b5f1c1f..f01725fbe 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -30,9 +30,9 @@ "Přehrát z žánru" "Nyní hraje" "Fronta" + Další skladba "Přidat do fronty" "Přidáno do fronty" - "Další ve frontě" "Jít na umělce" "Jít na album" "Stav uložen" @@ -100,7 +100,6 @@ "Přeskočit na poslední skladbu" "Změnit režim opakování" "Zapnout nebo vypnout náhodné" - "Vymazat frontu" "Přesunout skladbu ve frontě" "Vymazat vyhledávání" "Vymazat vyloučený adresář" @@ -136,7 +135,6 @@ "Šedá" - "Další z: %s" "Načtené skladby: %d" "%d skladba" diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 94f08a5b7..97077ff73 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -29,9 +29,9 @@ Aktuelle Wiedergabe Warteschlange + Spiele als nächstes Zur Warteschlange hinzufügen Der Warteschlange hinzugefügt - Nächstes in Warteschlange Zum Künstler gehen Zum Album gehen @@ -106,7 +106,6 @@ Art der Wiederholung ändern Suchanfrage löschen - Warteschlange leeren Auxio-Icon Albumcover für %s @@ -136,7 +135,6 @@ Grau - Nächsten von: %s Geladene Lieder: %d diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 6a0f370b1..389058520 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -21,9 +21,9 @@ Παίζει τώρα Ουρά αναπαραγωγής + Επόμενο Προσθήκη στην ουρά αναπ/γής Προστέθηκε ένας τίτλος στην ουρά αναπαραγωγής - Επόμενο Πήγαινε στον καλλιτέχνη Πήγαινε στο άλμπουμ @@ -55,8 +55,6 @@ Αναπαραγωγή/Παύση - Εκκαθάριση ουράς αναπαραγωγής - Αναζήτηση στη βιβλιοθήκη… diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 070efaed8..7fcebbbaa 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -30,9 +30,9 @@ Reproducción actual Cola + Siguiente Agregar a la cola Agregada a la cola - A continuación… Ir al artista Ir al álbum @@ -108,7 +108,6 @@ Saltar a la última canción Cambiar el modo de repetición - Limpiar cola Borrar historial de búsqueda Eliminar directorio excluido @@ -140,7 +139,6 @@ Gris - Siguiente de: %s Canciones encontradas: %d diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e8a75e347..6bbe18804 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -22,9 +22,9 @@ Lecture en cours File d\'attente + Jouer ensuite Ajouter à la file d\'attente Ajouté à la file d\'attente - Suivant Aller à l\'album Aller à l\'artiste @@ -62,8 +62,6 @@ Lecture/Pause - Effacer la file d\'attente - Recherche dans votre bibliothèque… diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index d72c44945..ff105e669 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -22,9 +22,9 @@ Most Játszott Lejátszási sor + Lejátszás következőnek Lejátszás sorhoz adás Sorbaállítva - Lejátszási sor Ugrás az előadóhoz Ugrás az albumhoz @@ -64,8 +64,6 @@ Lejátszás/Szünet - Lejátszási sor - Piros Rózsaszínű diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 2f13d36e7..bfcb62d45 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -22,9 +22,9 @@ Sedang Diputar Antrean + Putar selanjutnya Tambahkan ke antrean Ditambahkan ke antrean - Berikutnya Pergi ke artis Pergi ke album @@ -64,8 +64,6 @@ Putar/Jeda - Kosongkan antrean - diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 442602868..ba13e7cb5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -22,9 +22,9 @@ Schermata di riproduzione Coda + Riproduci successivo Aggiungi alla coda Aggiunta alla coda - A seguire Vai all\'artista Vai all\'album @@ -63,8 +63,6 @@ Play/Pausa - Svuota coda - Cerca nella tua libreria… diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index c1471e999..928c6a0ff 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -22,9 +22,9 @@ 지금 재생 중 대기열 + 다음 곡으로 재생 대기열에 추가 가 대기열에 추가되었습니다 - 다음 곡 아티스트로 가기 앨범으로 가기 @@ -60,8 +60,6 @@ 재생/일시 정지 - 재생 대기열 비우기 - 저장소 검색… diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4509e7dd4..725ba8f28 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -30,9 +30,9 @@ Afspeelscherm Wachtrij + Afspelen als volgende Toevoegen aan wachtrij Toegevoegd aan de wachtrij - Als volgende Ga naar artiest Ga naar album @@ -106,7 +106,6 @@ Naar het laatste nummer gaan Herhaalfunctie wijzigen - Wachtrij wissen Zoekopdracht wissen Verwijder uitgesloten map @@ -138,7 +137,6 @@ Grijis - Volgende van: %s Nummers geladen: %d diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8f162612f..851606073 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -22,9 +22,9 @@ Obecnie Grane Kolejka + Odtwarzaj następny Dodaj do kolejki Dodany do kolejki - Za chwilę zagra Przejdź do wykonawcy Przejdź do albumu @@ -61,8 +61,6 @@ Odtwarzanie/Pauza - Wyczyść kolejkę - Szukaj w bibliotece… diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3bb4e7a62..a9f059dc7 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -21,9 +21,9 @@ Tocando agora Fila + Reproduzir próxima Adicionar à fila Adicionada à fila - Próximo Ir para o artista Ir para o álbum @@ -62,8 +62,6 @@ Reproduzir/Pausar - Limpar fila - Procurar na biblioteca… diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 99bdb147f..a9701d224 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -22,9 +22,9 @@ A reproduzir Fila + Reproduzir a próxima Adicionar à fila Adicionada à fila - A seguir Ir para o artista Ir para o álbum @@ -63,8 +63,6 @@ Reproduzir/Pausar - Limpar fila - Procurar na biblioteca… diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 83d8a2024..700cff133 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -22,9 +22,9 @@ Redare Acum Fila de așteptare + Redaţi următoarea Adăugați la lista de așteptare A fost adăugat la liste de așteptare - Urmează Accesaţi artistul Accesaţi albumul @@ -65,8 +65,6 @@ Redă/Pauză - Golește lista de redare - Roșu Roz diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5efec4a92..e2c9250c6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -36,9 +36,9 @@ Воспроизвести из жанра Очередь + Воспроизвести далее Добавить в очередь Добавлено в очередь - Далее в очереди Перейти к исполнителю Перейти к альбому @@ -121,7 +121,6 @@ Включить или выключить перемешивание Перемешать все композиции - Очистить очередь Удалить композицию из очереди Переместить композицию в очереди Переместить вкладку @@ -161,7 +160,6 @@ Серый - Далее из: %s Всего композиций: %d diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0f1e85f78..d6d9f11c3 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -22,9 +22,9 @@ Şuan çalınan Kuyruk + Sonraki şarkı Kuyruğa ekle Kuyruğa eklendi - Sırada olanlar Sanatçıya git Albüme git @@ -63,8 +63,6 @@ Başlat/Durdur - Temizle Kuyruk - diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 320dc0e94..4447fe3d3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -21,9 +21,9 @@ Відтворюється Черга + Відтворити наступною Додати в чергу Додана в чергу - До наступного Перейти до виконавця Перейти до альбому @@ -59,8 +59,6 @@ Відтворити/Зупинити - Очистити черга - Пісні завантажено: %d diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index becf5c8f3..5e12ded73 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -22,9 +22,9 @@ 正在播放界面 播放队列 + 作为下一首播放 加入播放队列 此歌曲已加入播放队列 - 即将播放 查看艺术家 查看专辑 @@ -56,8 +56,6 @@ 播放/暂停 - 清空播放队列 - %d 歌曲 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 79b79f33d..2b9a9f9e1 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -22,9 +22,9 @@ 播放面板 隊列 + 下一首播放 添加到隊列 已加入隊列 - 播放佇列:即將播放 前往該歌手頁面 專輯 @@ -61,8 +61,6 @@ 播放/暫停 - 清空播放佇列 - 搜尋音樂庫… diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index dbdc4cf40..c00e52ab8 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -6,26 +6,6 @@ - - - - - - - - - - - - - - - - - - - - @string/set_theme_auto @string/set_theme_day diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4fcf5ac44..64424bd60 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,9 +36,9 @@ Play from genre Queue + Play next Add to queue Added to queue - Next in Queue Go to artist Go to album @@ -122,7 +122,6 @@ Turn shuffle on or off Shuffle all songs - Clear queue Remove this queue song Move this queue song Move this tab @@ -162,7 +161,6 @@ Grey - Next From: %s Songs loaded: %d diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 8cc356ed3..946e2dcfb 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -171,11 +171,6 @@ @style/ShapeAppearance.Auxio.FloatingActionButton.PlayPause - - diff --git a/info/ADDITIONS.md b/info/ADDITIONS.md index e66d06bf6..5a0742964 100644 --- a/info/ADDITIONS.md +++ b/info/ADDITIONS.md @@ -27,4 +27,3 @@ Feel free to fork Auxio to add your own feature set however. - Recently added list [#18] - Lyrics [#19] - Tag editing [#33] -- Specialized queue adding (ex. Play Next) [#44] \ No newline at end of file