Skip to content

Commit

Permalink
list fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
nift4 committed Dec 5, 2024
1 parent d396b9b commit 90bba56
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
Expand All @@ -71,7 +72,7 @@ import uk.akane.libphonograph.items.Item

abstract class BaseAdapter<T>(
protected val fragment: Fragment,
liveData: Flow<List<T>>?,
liveData: Flow<List<T>>,
sortHelper: Sorter.Helper<T>,
naturalOrderHelper: Sorter.NaturalOrderHelper<T>?,
initialSortType: Sorter.Type,
Expand All @@ -86,8 +87,7 @@ abstract class BaseAdapter<T>(
) : AdapterFragment.BaseInterface<BaseAdapter<T>.ViewHolder>(), PopupTextProvider, ItemHeightHelper {

val context = fragment.requireContext()
protected val listAgent = if (liveData == null) MutableStateFlow(listOf<T>()) else null
protected val liveDataAgent = MutableStateFlow((liveData ?: listAgent)!!)
protected val liveDataAgent = MutableStateFlow(liveData)
@OptIn(ExperimentalCoroutinesApi::class)
private val flow = liveDataAgent.flatMapLatest { it }
protected inline val mainActivity
Expand All @@ -100,15 +100,13 @@ abstract class BaseAdapter<T>(
context.resources.getDimensionPixelSize(R.dimen.larger_list_height)
private var gridHeight: Int? = null
private var lockedInGridSize = false
private var lastList: List<T>? = null
private val sorter = Sorter(sortHelper, naturalOrderHelper, rawOrderExposed)
val decorAdapter by lazy { createDecorAdapter() }
override val concatAdapter by lazy { ConcatAdapter(decorAdapter, this) }
override val itemHeightHelper by lazy {
DefaultItemHeightHelper.concatItemHeightHelper(decorAdapter, { 1 }, this)
}
private val handler = Handler(Looper.getMainLooper())
private val rawList = ArrayList<T>((flow as? SharedFlow<List<T>>)?.replayCache?.lastOrNull()?.size ?: 0)
private var lastList: List<T>? = null
protected val list = ArrayList<T>((flow as? SharedFlow<List<T>>)?.replayCache?.lastOrNull()?.size ?: 0)
private var comparator: Sorter.HintedComparator<T>? = null
private var layoutManager: RecyclerView.LayoutManager? = null
Expand Down Expand Up @@ -186,18 +184,7 @@ abstract class BaseAdapter<T>(
prefSortType
else
initialSortType
/*
if (flow is SharedFlow<List<T>>) { // TODO this can never succeed
flow.replayCache.lastOrNull()?.let {
updateListInternal(it, now = true, canDiff = false)
}
} else {
// TODO if we don't have a SharedFlow we'd be missing the above fast path and waste
// cycles reloading the list after view was layout-ed. should use viewmodel in
// callers to keep permanent SharedFlow and remove this code path entirely
Log.w("BaseAdapter", "liveData is non-null but not SharedFlow")
}
*/
updateListInternal(runBlocking { flow.first() }, now = true, canDiff = false)
layoutType =
if (prefLayoutType != LayoutType.NONE && prefLayoutType != defaultLayoutType && !isSubFragment)
prefLayoutType
Expand Down Expand Up @@ -279,16 +266,13 @@ abstract class BaseAdapter<T>(
}

@SuppressLint("NotifyDataSetChanged")
private fun sort(srcList: List<T>? = null, canDiff: Boolean): () -> () -> Unit {
// Ensure rawList is only accessed on UI thread
// and ensure calls to this method go in order
// to prevent funny IndexOutOfBoundsException crashes
val newList = ArrayList(srcList ?: rawList)
private suspend fun sort(srcList: List<T>, canDiff: Boolean) {
val newList = ArrayList(srcList)
if (!listLock.tryAcquire()) {
throw IllegalStateException("listLock already held, add now = true to the caller (I am ${javaClass.name})")
}
return {
try {
try {
val diff = withContext(Dispatchers.Default) {
if (sortType == Sorter.Type.NativeOrderDescending) {
newList.reverse()
} else if (sortType != Sorter.Type.NativeOrder) {
Expand All @@ -298,54 +282,39 @@ abstract class BaseAdapter<T>(
else comparator?.compare(o1, o2) ?: 0
}
}
val diff =
if (((list.isNotEmpty() && newList.isNotEmpty()) || allowDiffUtils) && canDiff)
DiffUtil.calculateDiff(SongDiffCallback(list, newList)) else null
val oldCount = list.size
val newCount = newList.size
{
try {
if (srcList != null) {
rawList.clear()
rawList.addAll(srcList)
}
list.clear()
list.addAll(newList)
if (diff != null)
diff.dispatchUpdatesTo(this)
else
notifyDataSetChanged()
if (oldCount != newCount) decorAdapter.updateSongCounter()
onListUpdated()
} finally {
listLock.release()
}
}
} catch (e: Exception) {
listLock.release()
throw e
if (((list.isNotEmpty() && newList.isNotEmpty()) || allowDiffUtils) && canDiff)
DiffUtil.calculateDiff(SongDiffCallback(list, newList)) else null
}
}
}

fun updateList(newList: List<T>, canDiff: Boolean) { // now is true for all callees
// TODO what about now / canDiff
runBlocking {
listAgent!!.emit(newList)
list.clear()
list.addAll(newList)
if (diff != null)
diff.dispatchUpdatesTo(this@BaseAdapter)
else
notifyDataSetChanged()
if (list.size != newList.size) decorAdapter.updateSongCounter()
onListUpdated()
} finally {
listLock.release()
}
}

fun updateListInternal(newList: List<T>? = null, now: Boolean, canDiff: Boolean) {
// The replay cache may cause us seeing the same list more than one.
if (lastList === newList) return
lastList = newList
val doSort = sort(newList, canDiff)
if (now || scope == null) doSort()()
else {
if (newList != null) {
if (lastList === newList) return
lastList = newList
}
val list = lastList
if (list == null)
throw IllegalArgumentException("updateListInternal called with null value but no value is cached")
if (now || scope == null) {
runBlocking {
sort(list, canDiff)
}
} else {
scope!!.launch {
val apply = doSort()
handler.post {
apply()
withContext(Dispatchers.Main) {
sort(list, canDiff)
}
}
}
Expand Down Expand Up @@ -499,7 +468,7 @@ abstract class BaseAdapter<T>(
}

protected fun toRawPos(item: T): Int {
return rawList.indexOf(item)
return lastList!!.indexOf(item)
}

final override fun getPopupText(view: View, position: Int): CharSequence {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ import android.view.animation.AnimationUtils
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.media3.common.MediaItem
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.zhanghai.android.fastscroll.PopupTextProvider
Expand All @@ -51,8 +53,9 @@ class DetailedFolderAdapter(
private val folderPopAdapter: FolderPopAdapter = FolderPopAdapter(this)
private val folderAdapter: FolderListAdapter =
FolderListAdapter(listOf(), mainActivity, this)
private val songList = MutableStateFlow(listOf<MediaItem>())
private val songAdapter: SongAdapter =
SongAdapter(fragment, listOf(), false, null, false)
SongAdapter(fragment, songList, false, null, false)
override val concatAdapter: ConcatAdapter =
ConcatAdapter(this, folderPopAdapter, folderAdapter, songAdapter)
override val itemHeightHelper: ItemHeightHelper? = null
Expand Down Expand Up @@ -116,7 +119,7 @@ class DetailedFolderAdapter(
val doUpdate = { canDiff: Boolean ->
folderPopAdapter.enabled = fileNodePath.isNotEmpty()
folderAdapter.updateList(item?.folderList?.values ?: listOf(), canDiff)
songAdapter.updateList(item?.songList ?: listOf(), false)
songList.value = item?.songList ?: listOf()
}
recyclerView.let {
if (it == null || invertedDirection == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ import android.view.animation.AnimationUtils
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.media3.common.MediaItem
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.zhanghai.android.fastscroll.PopupTextProvider
Expand All @@ -51,8 +53,9 @@ class FolderAdapter(
private val folderPopAdapter: FolderPopAdapter = FolderPopAdapter(this)
private val folderAdapter: FolderListAdapter =
FolderListAdapter(listOf(), mainActivity, this)
private val songList = MutableStateFlow(listOf<MediaItem>())
private val songAdapter: SongAdapter =
SongAdapter(fragment, listOf(), false, null, false)
SongAdapter(fragment, songList, false, null, false)
override val concatAdapter: ConcatAdapter =
ConcatAdapter(this, folderPopAdapter, folderAdapter, songAdapter)
override val itemHeightHelper: ItemHeightHelper? = null
Expand Down Expand Up @@ -126,7 +129,7 @@ class FolderAdapter(
val doUpdate = { canDiff: Boolean ->
folderPopAdapter.enabled = fileNodePath.isNotEmpty()
folderAdapter.updateList(item?.folderList?.values ?: listOf(), canDiff)
songAdapter.updateList(item?.songList ?: listOf(), false)
songList.value = item?.songList ?: listOf()
}
recyclerView.let {
if (it == null || invertedDirection == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import uk.akane.libphonograph.manipulator.ItemManipulator
*/
class SongAdapter(
fragment: Fragment,
songList: Flow<List<MediaItem>>? = (fragment.requireActivity() as MainActivity).reader.songListFlow,
songList: Flow<List<MediaItem>> = (fragment.requireActivity() as MainActivity).reader.songListFlow,
canSort: Boolean,
helper: Sorter.NaturalOrderHelper<MediaItem>?,
ownsView: Boolean,
Expand Down Expand Up @@ -82,30 +82,6 @@ class SongAdapter(
fallbackSpans = fallbackSpans
) {

constructor(
fragment: Fragment,
songList: List<MediaItem>,
canSort: Boolean,
helper: Sorter.NaturalOrderHelper<MediaItem>?,
ownsView: Boolean,
isSubFragment: Boolean = false,
allowDiffUtils: Boolean = false,
rawOrderExposed: Boolean = !isSubFragment,
fallbackSpans: Int = 1
) : this(
fragment,
null,
canSort,
helper,
ownsView,
isSubFragment,
allowDiffUtils,
rawOrderExposed,
fallbackSpans
) {
updateList(songList, false)
}

fun getSongList() = list

fun getActivity() = mainActivity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.appbar.AppBarLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.akanework.gramophone.R
import org.akanework.gramophone.logic.closeKeyboard
Expand Down Expand Up @@ -64,9 +65,10 @@ class SearchFragment : BaseFragment(false) {
appBarLayout.enableEdgeToEdgePaddingListener()
editText = rootView.findViewById(R.id.edit_text)
val recyclerView = rootView.findViewById<MyRecyclerView>(R.id.recyclerview)
val songList = MutableStateFlow(listOf<MediaItem>())
val songAdapter =
SongAdapter(
this, listOf(),
this, songList,
true, null, false, isSubFragment = true,
allowDiffUtils = true, rawOrderExposed = true
)
Expand All @@ -83,7 +85,7 @@ class SearchFragment : BaseFragment(false) {
editText.addTextChangedListener { rawText ->
// TODO sort results by match quality? (using NaturalOrderHelper)
if (rawText.isNullOrBlank()) {
songAdapter.updateList(listOf(), true)
songList.value = listOf()
} else {
// make sure the user doesn't edit away our text while we are filtering
val text = rawText.toString()
Expand All @@ -104,9 +106,7 @@ class SearchFragment : BaseFragment(false) {
it
)
}
handler.post {
songAdapter.updateList(filteredList, true)
}
songList.value = filteredList.toList()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
val agpVersion = "8.7.2"
val agpVersion = "8.8.0-beta01"
id("com.android.application") version agpVersion apply false
id("com.android.library") version agpVersion apply false
id("com.android.test") version agpVersion apply false
Expand Down
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
2 changes: 1 addition & 1 deletion libphonograph

0 comments on commit 90bba56

Please sign in to comment.