mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-17 17:52:23 +03:00
Fix ItemTouchHelper usage in the PlayerFragment
This commit is contained in:
parent
acb25c06e3
commit
bcf56375c6
@ -11,13 +11,12 @@ import android.annotation.SuppressLint
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.drakeet.multitype.MultiTypeAdapter
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.util.BoundedTreeSet
|
||||
import org.moire.ultrasonic.util.SettableAsyncListDiffer
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@ -59,23 +58,33 @@ class BaseAdapter<T : Identifiable> : MultiTypeAdapter(), FastScrollRecyclerView
|
||||
throw IllegalAccessException("You must use submitList() to add data to the Adapter")
|
||||
}
|
||||
|
||||
private var mDiffer: AsyncListDiffer<T> = AsyncListDiffer(
|
||||
private var mDiffer: SettableAsyncListDiffer<T> = SettableAsyncListDiffer(
|
||||
AdapterListUpdateCallback(this),
|
||||
AsyncDifferConfig.Builder(diffCallback).build()
|
||||
)
|
||||
|
||||
private val mListener =
|
||||
ListListener<T> { previousList, currentList ->
|
||||
this@BaseAdapter.onCurrentListChanged(
|
||||
previousList,
|
||||
currentList
|
||||
)
|
||||
private val mListener: SettableAsyncListDiffer.ListListener<T> =
|
||||
object : SettableAsyncListDiffer.ListListener<T> {
|
||||
override fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {
|
||||
this@BaseAdapter.onCurrentListChanged(
|
||||
previousList,
|
||||
currentList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
mDiffer.addListListener(mListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the List to a new value without invoking the diffing
|
||||
*/
|
||||
fun setList(newList: List<T>) {
|
||||
Timber.v("SetList updated list in differ, size %s", newList.size)
|
||||
mDiffer.setList(newList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a new list to be diffed, and displayed.
|
||||
*
|
||||
|
@ -869,18 +869,21 @@ class PlayerFragment :
|
||||
val from = viewHolder.bindingAdapterPosition
|
||||
val to = target.bindingAdapterPosition
|
||||
|
||||
Timber.i("MOVING from %d to %d", from, to)
|
||||
val newList = viewAdapter.getCurrentList().toMutableList()
|
||||
// The item must be moved manually in the viewAdapter, because it must be
|
||||
// moved synchronously, before this function returns. AsyncListDiffer would execute
|
||||
// the move too late
|
||||
val items = viewAdapter.getCurrentList().toMutableList()
|
||||
if (from < to) {
|
||||
for (i in from until to) {
|
||||
Collections.swap(newList, i, i + 1)
|
||||
Collections.swap(items, i, i + 1)
|
||||
}
|
||||
} else {
|
||||
for (i in from downTo to + 1) {
|
||||
Collections.swap(newList, i, i - 1)
|
||||
Collections.swap(items, i, i - 1)
|
||||
}
|
||||
}
|
||||
viewAdapter.submitList(newList)
|
||||
viewAdapter.setList(items)
|
||||
viewAdapter.notifyItemMoved(from, to)
|
||||
endPosition = to
|
||||
|
||||
// When the user moves an item, onMove may be called many times quickly,
|
||||
@ -896,7 +899,12 @@ class PlayerFragment :
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val pos = viewHolder.bindingAdapterPosition
|
||||
val item = mediaPlayerController.getMediaItemAt(pos)
|
||||
mediaPlayerController.removeFromPlaylist(pos)
|
||||
|
||||
// Remove the item from the list quickly
|
||||
val items = viewAdapter.getCurrentList().toMutableList()
|
||||
items.removeAt(pos)
|
||||
viewAdapter.setList(items)
|
||||
viewAdapter.notifyItemRemoved(pos)
|
||||
|
||||
val songRemoved = String.format(
|
||||
resources.getString(R.string.download_song_removed),
|
||||
@ -904,6 +912,9 @@ class PlayerFragment :
|
||||
)
|
||||
|
||||
Util.toast(context, songRemoved)
|
||||
|
||||
// Remove the item from the playlist
|
||||
mediaPlayerController.removeFromPlaylist(pos)
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(
|
||||
|
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* SettableAsyncListDiffer.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.DiffUtil.DiffResult
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* This class is a variation of the AsyncListDiffer provided for the RecyclerView.
|
||||
* It is possible to set its List to a new value without triggering the diffing.
|
||||
* This is necessary when the changes in the List must be synchronous, and are
|
||||
* executed manually elsewhere in the code.
|
||||
* @see androidx.recyclerview.widget.AsyncListDiffer
|
||||
* More discussion about the necessity of this class is in the merge request:
|
||||
* https://gitlab.com/ultrasonic/ultrasonic/-/merge_requests/815
|
||||
* We opened an issue for Google and hope they will add an official solution, see:
|
||||
* https://issuetracker.google.com/issues/247351552
|
||||
*/
|
||||
class SettableAsyncListDiffer<T> {
|
||||
private var mUpdateCallback: ListUpdateCallback? = null
|
||||
var mConfig: AsyncDifferConfig<T>? = null
|
||||
private var mMainThreadExecutor: Executor? = null
|
||||
|
||||
private class MainThreadExecutor : Executor {
|
||||
val mHandler = Handler(Looper.getMainLooper())
|
||||
override fun execute(command: Runnable) {
|
||||
mHandler.post(command)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use MainThreadExecutor from supportlib once one exists
|
||||
private val sMainThreadExecutor: Executor = MainThreadExecutor()
|
||||
|
||||
/**
|
||||
* Listener for when the current List is updated.
|
||||
*
|
||||
* @param <T> Type of items in List
|
||||
</T> */
|
||||
interface ListListener<T> {
|
||||
/**
|
||||
* Called after the current List has been updated.
|
||||
*
|
||||
* @param previousList The previous list.
|
||||
* @param currentList The new current list.
|
||||
*/
|
||||
fun onCurrentListChanged(previousList: List<T>, currentList: List<T>)
|
||||
}
|
||||
|
||||
private val mListeners: MutableList<ListListener<T>> = CopyOnWriteArrayList()
|
||||
|
||||
/**
|
||||
* Convenience for
|
||||
* `AsyncListDiffer(new AdapterListUpdateCallback(adapter),
|
||||
* new AsyncDifferConfig.Builder().setDiffCallback(diffCallback).build());`
|
||||
*
|
||||
* @param adapter Adapter to dispatch position updates to.
|
||||
* @param diffCallback ItemCallback that compares items to dispatch appropriate animations when
|
||||
*
|
||||
* @see DiffUtil.DiffResult.dispatchUpdatesTo
|
||||
*/
|
||||
constructor(
|
||||
adapter: RecyclerView.Adapter<*>,
|
||||
diffCallback: DiffUtil.ItemCallback<T>
|
||||
) : this(
|
||||
AdapterListUpdateCallback(adapter),
|
||||
AsyncDifferConfig.Builder(diffCallback).build()
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a AsyncListDiffer with the provided config, and ListUpdateCallback to dispatch
|
||||
* updates to.
|
||||
*
|
||||
* @param listUpdateCallback Callback to dispatch updates to.
|
||||
* @param config Config to define background work Executor, and DiffUtil.ItemCallback for
|
||||
* computing List diffs.
|
||||
*
|
||||
* @see DiffUtil.DiffResult.dispatchUpdatesTo
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
constructor(
|
||||
listUpdateCallback: ListUpdateCallback,
|
||||
config: AsyncDifferConfig<T>
|
||||
) {
|
||||
mUpdateCallback = listUpdateCallback
|
||||
mConfig = config
|
||||
mMainThreadExecutor = if (config.mainThreadExecutor != null) {
|
||||
config.mainThreadExecutor
|
||||
} else {
|
||||
sMainThreadExecutor
|
||||
}
|
||||
}
|
||||
|
||||
private var mList: List<T>? = null
|
||||
|
||||
/**
|
||||
* Non-null, unmodifiable version of mList.
|
||||
*
|
||||
*
|
||||
* Collections.emptyList when mList is null, wrapped by Collections.unmodifiableList otherwise
|
||||
*/
|
||||
private var mReadOnlyList = emptyList<T>()
|
||||
|
||||
// Max generation of currently scheduled runnable
|
||||
private var mMaxScheduledGeneration/* synthetic access */ = 0
|
||||
|
||||
/**
|
||||
* Get the current List - any diffing to present this list has already been computed and
|
||||
* dispatched via the ListUpdateCallback.
|
||||
*
|
||||
*
|
||||
* If a `null` List, or no List has been submitted, an empty list will be returned.
|
||||
*
|
||||
*
|
||||
* The returned list may not be mutated - mutations to content must be done through
|
||||
* [.submitList].
|
||||
*
|
||||
* @return current List.
|
||||
*/
|
||||
|
||||
val currentList: List<T>
|
||||
get() = mReadOnlyList
|
||||
|
||||
/**
|
||||
* Sets the List to a new value without invoking the diffing
|
||||
*/
|
||||
fun setList(newList: List<T>) {
|
||||
mList = newList
|
||||
mReadOnlyList = Collections.unmodifiableList(newList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a new List to the AdapterHelper. Adapter updates will be computed on a background
|
||||
* thread.
|
||||
*
|
||||
*
|
||||
* If a List is already present, a diff will be computed asynchronously on a background thread.
|
||||
* When the diff is computed, it will be applied (dispatched to the [ListUpdateCallback]),
|
||||
* and the new List will be swapped in.
|
||||
*
|
||||
* @param newList The new List.
|
||||
*/
|
||||
fun submitList(newList: List<T>?) {
|
||||
submitList(newList, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a new List to the AdapterHelper. Adapter updates will be computed on a background
|
||||
* thread.
|
||||
*
|
||||
*
|
||||
* If a List is already present, a diff will be computed asynchronously on a background thread.
|
||||
* When the diff is computed, it will be applied (dispatched to the [ListUpdateCallback]),
|
||||
* and the new List will be swapped in.
|
||||
*
|
||||
*
|
||||
* The commit callback can be used to know when the List is committed, but note that it
|
||||
* may not be executed. If List B is submitted immediately after List A, and is
|
||||
* committed directly, the callback associated with List A will not be run.
|
||||
*
|
||||
* @param newList The new List.
|
||||
* @param commitCallback Optional runnable that is executed when the List is committed, if
|
||||
* it is committed.
|
||||
*/
|
||||
fun submitList(
|
||||
newList: List<T>?,
|
||||
commitCallback: Runnable?
|
||||
) {
|
||||
// incrementing generation means any currently-running diffs are discarded when they finish
|
||||
val runGeneration = ++mMaxScheduledGeneration
|
||||
if (newList === mList) {
|
||||
// nothing to do (Note - still had to inc generation, since may have ongoing work)
|
||||
commitCallback?.run()
|
||||
return
|
||||
}
|
||||
val previousList = mReadOnlyList
|
||||
|
||||
// fast simple remove all
|
||||
if (newList == null) {
|
||||
val countRemoved = mList!!.size
|
||||
mList = null
|
||||
mReadOnlyList = emptyList()
|
||||
// notify last, after list is updated
|
||||
mUpdateCallback!!.onRemoved(0, countRemoved)
|
||||
onCurrentListChanged(previousList, commitCallback)
|
||||
return
|
||||
}
|
||||
|
||||
// fast simple first insert
|
||||
if (mList == null) {
|
||||
mList = newList
|
||||
mReadOnlyList = Collections.unmodifiableList(newList)
|
||||
// notify last, after list is updated
|
||||
mUpdateCallback!!.onInserted(0, newList.size)
|
||||
onCurrentListChanged(previousList, commitCallback)
|
||||
return
|
||||
}
|
||||
val oldList: List<T> = mList!!
|
||||
mConfig!!.backgroundThreadExecutor.execute {
|
||||
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||
override fun getOldListSize(): Int {
|
||||
return oldList.size
|
||||
}
|
||||
|
||||
override fun getNewListSize(): Int {
|
||||
return newList.size
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItemPosition: Int,
|
||||
newItemPosition: Int
|
||||
): Boolean {
|
||||
val oldItem: T? = oldList[oldItemPosition]
|
||||
val newItem: T? = newList[newItemPosition]
|
||||
return if (oldItem != null && newItem != null) {
|
||||
mConfig!!.diffCallback.areItemsTheSame(oldItem, newItem)
|
||||
} else oldItem == null && newItem == null
|
||||
// If both items are null we consider them the same.
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItemPosition: Int,
|
||||
newItemPosition: Int
|
||||
): Boolean {
|
||||
val oldItem: T? = oldList[oldItemPosition]
|
||||
val newItem: T? = newList[newItemPosition]
|
||||
if (oldItem != null && newItem != null) {
|
||||
return mConfig!!.diffCallback.areContentsTheSame(oldItem, newItem)
|
||||
}
|
||||
if (oldItem == null && newItem == null) {
|
||||
return true
|
||||
}
|
||||
throw AssertionError()
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
|
||||
val oldItem: T? = oldList[oldItemPosition]
|
||||
val newItem: T? = newList[newItemPosition]
|
||||
if (oldItem != null && newItem != null) {
|
||||
return mConfig!!.diffCallback.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
throw AssertionError()
|
||||
}
|
||||
})
|
||||
mMainThreadExecutor!!.execute {
|
||||
if (mMaxScheduledGeneration == runGeneration) {
|
||||
latchList(newList, result, commitCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun latchList(
|
||||
newList: List<T>,
|
||||
diffResult: DiffResult,
|
||||
commitCallback: Runnable?
|
||||
) {
|
||||
val previousList = mReadOnlyList
|
||||
mList = newList
|
||||
// notify last, after list is updated
|
||||
mReadOnlyList = Collections.unmodifiableList(newList)
|
||||
diffResult.dispatchUpdatesTo(mUpdateCallback!!)
|
||||
onCurrentListChanged(previousList, commitCallback)
|
||||
}
|
||||
|
||||
private fun onCurrentListChanged(
|
||||
previousList: List<T>,
|
||||
commitCallback: Runnable?
|
||||
) {
|
||||
// current list is always mReadOnlyList
|
||||
for (listener in mListeners) {
|
||||
listener.onCurrentListChanged(previousList, mReadOnlyList)
|
||||
}
|
||||
commitCallback?.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a ListListener to receive updates when the current List changes.
|
||||
*
|
||||
* @param listener Listener to receive updates.
|
||||
*
|
||||
* @see .getCurrentList
|
||||
* @see .removeListListener
|
||||
*/
|
||||
fun addListListener(listener: ListListener<T>) {
|
||||
mListeners.add(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a previously registered ListListener.
|
||||
*
|
||||
* @param listener Previously registered listener.
|
||||
* @see .getCurrentList
|
||||
* @see .addListListener
|
||||
*/
|
||||
fun removeListListener(listener: ListListener<T>) {
|
||||
mListeners.remove(listener)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user