|
|
@@ -20,6 +20,8 @@ import android.database.DataSetObserver
|
|
|
import android.net.Uri
|
|
|
import android.os.Build
|
|
|
import android.os.Bundle
|
|
|
+import android.os.Looper
|
|
|
+import android.os.Handler
|
|
|
import androidx.annotation.RequiresApi
|
|
|
import androidx.compose.runtime.Composable
|
|
|
import androidx.compose.runtime.DisposableEffect
|
|
|
@@ -28,59 +30,68 @@ import androidx.compose.runtime.mutableStateOf
|
|
|
import androidx.compose.runtime.remember
|
|
|
import androidx.compose.runtime.setValue
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
+import kotlinx.coroutines.CoroutineScope
|
|
|
+import kotlinx.coroutines.CoroutineStart
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
+import kotlinx.coroutines.launch
|
|
|
import kotlinx.coroutines.runBlocking
|
|
|
import org.mjdev.tvlib.extensions.CursorExt.isNotEmpty
|
|
|
import timber.log.Timber
|
|
|
+import kotlin.coroutines.CoroutineContext
|
|
|
|
|
|
@Suppress("FunctionName")
|
|
|
-open class CachingCursor(
|
|
|
+open class CachingCursor<T>(
|
|
|
context: Context,
|
|
|
uri: Uri?,
|
|
|
private val projection: Array<String>,
|
|
|
selection: String? = null,
|
|
|
selectionArgs: Array<String>? = null,
|
|
|
sortOrder: String? = null,
|
|
|
- private val transform: (Cursor) -> Any? = { it },
|
|
|
-) : Cursor, List<Any?> {
|
|
|
+ private val transform: (Cursor) -> T? = { null },
|
|
|
+) : Cursor, List<T?> {
|
|
|
|
|
|
- private val resolver: ContentResolver by lazy {
|
|
|
+ private val contentResolver by lazy {
|
|
|
context.contentResolver
|
|
|
}
|
|
|
|
|
|
- private val observer = object : ContentObserver(null) {
|
|
|
+ private val handler by lazy {
|
|
|
+ Looper.myLooper()?.let { looper ->
|
|
|
+ Handler(looper)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private val observer = object : ContentObserver(handler) {
|
|
|
+ // todo, refresh
|
|
|
@Suppress("DEPRECATION")
|
|
|
override fun onChange(selfChange: Boolean) {
|
|
|
requery()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private val cache: MutableList<CachedCursorItem> = mutableListOf()
|
|
|
+ private val cache: MutableList<CachedCursorItem<T>> = mutableListOf()
|
|
|
|
|
|
private val cursor: Cursor? by lazy {
|
|
|
- runBlocking(Dispatchers.IO) {
|
|
|
- if (uri != null) {
|
|
|
- try {
|
|
|
- resolver.query(
|
|
|
- uri, projection, selection, selectionArgs, sortOrder
|
|
|
- )?.apply {
|
|
|
- registerContentObserver(observer)
|
|
|
- if (this.isNotEmpty) {
|
|
|
- moveToFirst()
|
|
|
- }
|
|
|
+ if (uri != null) {
|
|
|
+ try {
|
|
|
+ contentResolver.query(
|
|
|
+ uri, projection, selection, selectionArgs, sortOrder
|
|
|
+ )?.apply {
|
|
|
+ registerContentObserver(observer)
|
|
|
+ if (this.isNotEmpty) {
|
|
|
+ moveToFirst()
|
|
|
}
|
|
|
- } catch (e: Exception) {
|
|
|
- Timber.e(e)
|
|
|
- null
|
|
|
}
|
|
|
- } else null
|
|
|
- }
|
|
|
+ } catch (e: Exception) {
|
|
|
+ Timber.e(e)
|
|
|
+ null
|
|
|
+ }
|
|
|
+ } else null
|
|
|
}
|
|
|
|
|
|
val isNotEmpty: Boolean get() = count > 0
|
|
|
|
|
|
- private fun _get(idx: Int): Any? {
|
|
|
- var result: Any? = null
|
|
|
+ private fun _get(idx: Int): T? {
|
|
|
+ var result: T? = null
|
|
|
if (isCached(idx)) {
|
|
|
result = getFromCache(idx)
|
|
|
} else {
|
|
|
@@ -99,15 +110,15 @@ open class CachingCursor(
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
- fun getData(idx: Int): Any? {
|
|
|
+ fun getData(idx: Int): T? {
|
|
|
return _get(idx)
|
|
|
}
|
|
|
|
|
|
- private fun insertToCache(idx: Int, value: Any) {
|
|
|
+ private fun insertToCache(idx: Int, value: T) {
|
|
|
cache.add(CachedCursorItem(idx, System.currentTimeMillis(), value))
|
|
|
}
|
|
|
|
|
|
- private fun getFromCache(idx: Int): Any? {
|
|
|
+ private fun getFromCache(idx: Int): T? {
|
|
|
return cache.firstOrNull { item ->
|
|
|
item.idx == idx
|
|
|
}?.data
|
|
|
@@ -225,12 +236,24 @@ open class CachingCursor(
|
|
|
}
|
|
|
|
|
|
@Deprecated("Deprecated in Java")
|
|
|
- override fun requery(): Boolean = runBlocking(Dispatchers.IO) { _requery() }
|
|
|
+ override fun requery(): Boolean {
|
|
|
+ launch {
|
|
|
+ _requery()
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun launch(
|
|
|
+ context: CoroutineContext = Dispatchers.IO,
|
|
|
+ start: CoroutineStart = CoroutineStart.DEFAULT,
|
|
|
+ block: suspend CoroutineScope.() -> Unit
|
|
|
+ ) = CoroutineScope(context).launch(context, start, block)
|
|
|
|
|
|
@Suppress("SpellCheckingInspection", "DEPRECATION")
|
|
|
private fun _requery(): Boolean {
|
|
|
cache.clear()
|
|
|
- return cursor?.requery() ?: false
|
|
|
+ val ret = cursor?.requery() ?: false
|
|
|
+ return ret
|
|
|
}
|
|
|
|
|
|
override fun isClosed(): Boolean =
|
|
|
@@ -275,22 +298,23 @@ open class CachingCursor(
|
|
|
|
|
|
override val size: Int get() = count
|
|
|
|
|
|
- override fun get(index: Int): Any? = _get(index)
|
|
|
+ override fun get(index: Int): T? = _get(index)
|
|
|
+
|
|
|
override fun isEmpty(): Boolean = (size == 0)
|
|
|
|
|
|
- override fun iterator(): Iterator<Any?> =
|
|
|
+ override fun iterator(): Iterator<T?> =
|
|
|
CachingCursorListIterator(this)
|
|
|
|
|
|
- override fun listIterator(): ListIterator<Any?> =
|
|
|
+ override fun listIterator(): ListIterator<T?> =
|
|
|
CachingCursorListIterator(this)
|
|
|
|
|
|
- override fun listIterator(index: Int): ListIterator<Any?> =
|
|
|
+ override fun listIterator(index: Int): ListIterator<T?> =
|
|
|
CachingCursorListIterator(this, index)
|
|
|
|
|
|
- override fun subList(fromIndex: Int, toIndex: Int): List<Any?> =
|
|
|
+ override fun subList(fromIndex: Int, toIndex: Int): List<T?> =
|
|
|
(fromIndex..toIndex).map { idx -> get(idx) }
|
|
|
|
|
|
- override fun lastIndexOf(element: Any?): Int {
|
|
|
+ override fun lastIndexOf(element: T?): Int {
|
|
|
var curr: Int = size - 1
|
|
|
while ((this[curr] != element) && (curr > 0)) {
|
|
|
curr--
|
|
|
@@ -298,7 +322,7 @@ open class CachingCursor(
|
|
|
return curr
|
|
|
}
|
|
|
|
|
|
- override fun indexOf(element: Any?): Int {
|
|
|
+ override fun indexOf(element: T?): Int {
|
|
|
var curr = 0
|
|
|
while ((this[curr] != element) && (curr < size)) {
|
|
|
curr++
|
|
|
@@ -306,18 +330,18 @@ open class CachingCursor(
|
|
|
return if (curr == size) -1 else curr
|
|
|
}
|
|
|
|
|
|
- override fun containsAll(elements: Collection<Any?>): Boolean {
|
|
|
+ override fun containsAll(elements: Collection<T?>): Boolean {
|
|
|
TODO("Not yet implemented")
|
|
|
}
|
|
|
|
|
|
- override fun contains(element: Any?): Boolean {
|
|
|
+ override fun contains(element: T?): Boolean {
|
|
|
TODO("Not yet implemented")
|
|
|
}
|
|
|
|
|
|
- data class CachedCursorItem(
|
|
|
+ data class CachedCursorItem<CT>(
|
|
|
val idx: Int,
|
|
|
val cached: Long,
|
|
|
- val data: Any?
|
|
|
+ val data: CT?
|
|
|
)
|
|
|
|
|
|
companion object {
|
|
|
@@ -325,22 +349,22 @@ open class CachingCursor(
|
|
|
@Deprecated("Please for better performance use view model with cursors.")
|
|
|
@SuppressLint("Recycle")
|
|
|
@Composable
|
|
|
- fun rememberCursor(
|
|
|
+ fun <RT> rememberCursor(
|
|
|
uri: Uri?,
|
|
|
projection: Array<String>,
|
|
|
selection: String? = null,
|
|
|
selectionArgs: Array<String>? = null,
|
|
|
sortOrder: String? = null,
|
|
|
- transform: (Cursor) -> Any?,
|
|
|
- ): CachingCursor? {
|
|
|
+ transform: (Cursor) -> RT?,
|
|
|
+ ): CachingCursor<RT>? {
|
|
|
if (uri == null) return null
|
|
|
else {
|
|
|
val context = LocalContext.current
|
|
|
var cursor by remember {
|
|
|
- mutableStateOf<CachingCursor?>(null)
|
|
|
+ mutableStateOf<CachingCursor<RT>?>(null)
|
|
|
}
|
|
|
DisposableEffect(uri) {
|
|
|
- cursor = CachingCursor(
|
|
|
+ cursor = CachingCursor<RT>(
|
|
|
context,
|
|
|
uri,
|
|
|
projection,
|