Rewrite LauncherUtils

This commit is contained in:
tarsin committed 2023-05-23 13:11:57 +08:00
1 parent aa1e2102fc
commit f419189749
3 files changed
+45 -23

No files matched your search

@@ -73,7 +73,6 @@ import com.hippo.ehviewer.util.addTextToClipboard
import com.hippo.ehviewer.util.getClipboardManager
import com.hippo.ehviewer.util.getParcelableExtraCompat
import com.hippo.ehviewer.util.getUrlFromClipboard
import com.hippo.ehviewer.util.initLauncher
import com.hippo.ehviewer.yorozuya.IOUtils
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.lang.launchUI
@@ -91,10 +90,6 @@ class MainActivity : EhActivity() {
private val connectivityManager by lazy { getSystemService<ConnectivityManager>()!! }
private val availableNetworks = mutableListOf<Network>()
init {
initLauncher()
}
private fun saveImageToTempFile(uri: Uri): File? {
val src = ImageDecoder.createSource(contentResolver, uri)
val bitmap = runCatching {
@@ -54,7 +54,7 @@ class ImageSearchLayout @JvmOverloads constructor(
Mdc3Theme {
val coroutineScope = rememberCoroutineScope()
fun selectImage() = coroutineScope.launch {
pickVisualMedia(ImageOnly)?.let {
context.pickVisualMedia(ImageOnly)?.let {
path = it.toString()
}
}
@@ -1,41 +1,68 @@
package com.hippo.ehviewer.util
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType
import androidx.core.content.ContextCompat
import arrow.atomic.Atomic
import arrow.atomic.update
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import arrow.atomic.AtomicInt
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.suspendCancellableCoroutine
import rikka.core.util.ContextUtils.requireActivity
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
// Fuck off the silly Android launcher and callback :)
private typealias LauncherAndCallback<K, V> = Atomic<Pair<ActivityResultLauncher<K>, (V) -> Unit>>
private fun <K, V> LauncherAndCallback<K, V>.cleanup() = update { it.copy {} }
private lateinit var requestPermission: LauncherAndCallback<String, Boolean>
private lateinit var pickVisualMedia: LauncherAndCallback<PickVisualMediaRequest, Uri?>
private suspend fun <K, V> LauncherAndCallback<K, V>.await(key: K) = withUIContext {
suspendCancellableCoroutine { cont ->
updateAndGet { prev -> prev.copy { cont.resume(it) } }.first.launch(key)
cont.invokeOnCancellation { cleanup() } // Drop continuation when cancelled
}.apply { cleanup() } // Drop continuation when completed
private val atomicInteger = AtomicInt()
private fun Context.getLifecycle(): Lifecycle {
var context: Context? = this
while (true) {
when (context) {
is LifecycleOwner -> return context.lifecycle
!is ContextWrapper -> TODO()
else -> context = context.baseContext
}
}
}
fun ComponentActivity.initLauncher() {
requestPermission = Atomic(registerForActivityResult(ActivityResultContracts.RequestPermission()) { requestPermission.get().second(it) } to {})
pickVisualMedia = Atomic(registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { pickVisualMedia.get().second(it) } to {})
private suspend fun <I, O> Context.registerForActivityResultAndLaunchAndAwaitResult(contract: ActivityResultContract<I, O>, input: I): O {
val lifecycle = getLifecycle()
val key = "activity_rq#${atomicInteger.getAndIncrement()}"
var launcher: ActivityResultLauncher<I>? = null
var observer: LifecycleEventObserver? = null
observer = LifecycleEventObserver { _, event ->
if (Lifecycle.Event.ON_DESTROY == event) {
launcher?.unregister()
if (observer != null) {
lifecycle.removeObserver(observer!!)
}
}
}
lifecycle.addObserver(observer)
return withUIContext {
suspendCoroutine { cont -> // No cancellation support here since we cannot cancel a launched Intent
launcher = requireActivity<ComponentActivity>(this@registerForActivityResultAndLaunchAndAwaitResult).activityResultRegistry.register(key, contract) {
launcher?.unregister()
lifecycle.removeObserver(observer)
cont.resume(it)
}.apply { launch(input) }
}
}
}
suspend fun Context.requestPermission(key: String): Boolean {
if (ContextCompat.checkSelfPermission(this, key) == PackageManager.PERMISSION_GRANTED) return true
return requestPermission.await(key)
return registerForActivityResultAndLaunchAndAwaitResult(ActivityResultContracts.RequestPermission(), key)
}
suspend fun pickVisualMedia(type: VisualMediaType): Uri? = pickVisualMedia.await(PickVisualMediaRequest.Builder().setMediaType(type).build())
suspend fun Context.pickVisualMedia(type: VisualMediaType): Uri? = registerForActivityResultAndLaunchAndAwaitResult(ActivityResultContracts.PickVisualMedia(), PickVisualMediaRequest.Builder().setMediaType(type).build())