Add parent restoration

Add a failsafe to the persistence system that allows the parent of a playback state to be restored from the queue.
This commit is contained in:
OxygenCobalt 2020-11-26 08:50:48 -07:00
parent 1af17a6df1
commit 7e0ee3d04f
8 changed files with 90 additions and 16 deletions

View file

@ -26,6 +26,7 @@ import org.oxycblt.auxio.ui.getTransparentAccent
import org.oxycblt.auxio.ui.toColor
import kotlin.IllegalArgumentException
// TODO: Dedicated Search Tab?
class MainFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
@ -98,7 +99,7 @@ class MainFragment : Fragment() {
playbackModel.navToItem.observe(viewLifecycleOwner) {
if (it != null) {
// If the current destination isnt even LibraryFragment, then navigate there first
// If the current destination isn't even LibraryFragment, then navigate there first
if (binding.navBar.selectedItemId != R.id.library_fragment) {
binding.navBar.selectedItemId = R.id.library_fragment
} else {

View file

@ -235,17 +235,25 @@ class PlaybackStateManager private constructor() {
// --- QUEUE FUNCTIONS ---
/**
* Go to the next song, along with doing all the checks that entails.
*/
fun next() {
resetLoopMode()
// 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()
} else {
// If not in the user queue, then increment the current index
// If it cant be incremented anymore, end playback or loop depending on the setting.
if (mIndex < mQueue.lastIndex) {
mIndex = mIndex.inc()
} else {
@ -261,6 +269,9 @@ class PlaybackStateManager private constructor() {
}
}
/**
* Go to the previous song, doing any checks that are needed.
*/
fun prev() {
if (mIndex > 0 && !mIsInUserQueue) {
mIndex = mIndex.dec()
@ -457,6 +468,8 @@ class PlaybackStateManager private constructor() {
database.writeQueue(queueItems)
}
getCommonGenre()
val time = System.currentTimeMillis() - start
Log.d(this::class.simpleName, "Save finished in ${time}ms")
@ -486,6 +499,7 @@ class PlaybackStateManager private constructor() {
unpackFromPlaybackState(it)
unpackQueue(queueItems)
doParentSanityCheck()
}
val time = System.currentTimeMillis() - start
@ -568,7 +582,8 @@ class PlaybackStateManager private constructor() {
}
}
// Get a more accurate index [At least if were not in the user 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.
if (!mIsInUserQueue) {
mSong?.let {
val index = mQueue.indexOf(it)
@ -580,6 +595,62 @@ class PlaybackStateManager private constructor() {
forceUserQueueUpdate()
}
private fun doParentSanityCheck() {
// Check if the parent was lost while in the DB.
if (mSong != null && mParent == null && mMode != PlaybackMode.ALL_SONGS) {
Log.d(this::class.simpleName, "Parent lost, attempting restore.")
mParent = when (mMode) {
PlaybackMode.IN_ALBUM -> mQueue.firstOrNull()?.album
PlaybackMode.IN_ARTIST -> mQueue.firstOrNull()?.album?.artist
PlaybackMode.IN_GENRE -> getCommonGenre()
PlaybackMode.ALL_SONGS -> null
}
}
}
/**
* Search for the common genre out of a queue of songs that **should have a common genre**.
* @return The **single** common genre, null if there isn't any or if there's multiple.
*/
private fun getCommonGenre(): Genre? {
// Pool of "Possible" genres, these get narrowed down until the list is only
// the actual genre(s) that all songs in the queue have in common.
var genres = mutableListOf<Genre>()
var otherGenres: MutableList<Genre>
for (queueSong in mQueue) {
// If there's still songs to check despite the pool of genres being empty, re-add them.
if (genres.size == 0) {
genres.addAll(queueSong.album.artist.genres)
continue
}
otherGenres = genres.toMutableList()
// Iterate through the current genres and remove the ones that don't exist in this song,
// narrowing down the pool of possible genres.
for (genre in genres) {
if (queueSong.album.artist.genres.find { it.id == genre.id } == null) {
otherGenres.remove(genre)
}
}
genres = otherGenres.toMutableList()
}
Log.d(this::class.simpleName, "Found genre $genres")
// There should not be more than one common genre, so return null if that's the case
if (genres.size > 1) {
return null
}
// Sometimes the narrowing process will lead to a zero-size list, so return null if that
// is the case.
return genres.firstOrNull()
}
// --- ORDERING FUNCTIONS ---
private fun setupOrderedQueue() {

View file

@ -82,19 +82,20 @@ class SongsFragment : Fragment() {
val item = musicStore.songs[pos]
iters++
var char = item.name[0].toUpperCase()
// If the item starts with "the"/"a", then actually use the character after that
// as its initial. Yes, this is stupidly anglo-centric but the code [hopefully]
// as its initial. Yes, this is stupidly western-centric but the code [hopefully]
// shouldn't run with other languages.
if (item.name.length > 5 &&
val char: Char = if (item.name.length > 5 &&
item.name.startsWith("the ", ignoreCase = true)
) {
char = item.name[4].toUpperCase()
item.name[4].toUpperCase()
} else if (item.name.length > 3 &&
item.name.startsWith("a ", ignoreCase = true)
) {
char = item.name[2].toUpperCase()
item.name[2].toUpperCase()
} else {
// If it doesn't begin with that word, then just use the first character.
item.name[0].toUpperCase()
}
// Check if this song starts with a number, if so, then concat it with a single

View file

@ -129,8 +129,8 @@
android:paddingEnd="@dimen/margin_medium"
android:paddingBottom="@dimen/padding_small"
app:layout_constraintBottom_toBottomOf="@+id/genre_artist_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/genre_artist_header"
tools:layout_editor_absoluteX="355dp"
tools:src="@drawable/ic_sort_alpha_down" />
<androidx.recyclerview.widget.RecyclerView

View file

@ -10,7 +10,7 @@
android:id="@+id/action_shuffle"
android:icon="@drawable/ic_shuffle"
android:title="@string/label_shuffle"
app:showAsAction="ifRoom" />
app:showAsAction="never" />
<item
android:id="@+id/action_queue_add"
android:icon="@drawable/ic_queue_add"

View file

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_play"
android:title="@string/label_play"
android:icon="@drawable/ic_play"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_shuffle"
android:icon="@drawable/ic_shuffle"
android:title="@string/label_shuffle"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_play"
android:title="@string/label_play"
android:icon="@drawable/ic_play"
app:showAsAction="never" />
</menu>

View file

@ -42,7 +42,7 @@
<action
android:id="@+id/action_show_album"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@animator/nav_default_exit_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:destination="@id/album_detail_fragment"

View file

@ -49,6 +49,7 @@
<style name="TextAppearance.FastScroll" parent="TextAppearance.AppCompat.Body2">
<item name="android:fontFamily">@font/inter_semibold</item>
<item name="android:verticalSpacing">1dp</item>
</style>
<style name="TextAppearance.ThumbIndicator" parent="TextAppearance.FastScroll">