Generate subscription QR codes off the UI thread (#6194)
Move subscription QR bitmap generation from the synchronous share callback into the existing ViewModel, running CPU-bound encoding on Dispatchers.Default and exposing the completed bitmap through StateFlow. Cancel pending result delivery when the dialog is dismissed, a newer share request replaces it, or the ViewModel is cleared. Keep dialog rendering lifecycle-aware and reuse the existing localized failure message when generation returns no bitmap. Extract only QR generation and dialog-state wiring from local/talkback-all-10. Leave subscription row accessibility, custom actions, update handling, and clipboard sharing unchanged.
This commit is contained in:
1 parent
6400d33b87
commit
7d9fa970dc
2 files changed
+30
-9
No files matched your search
@@ -1,7 +1,6 @@
|
|||||||
package com.v2ray.ang.ui.subscription
|
package com.v2ray.ang.ui.subscription
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@@ -53,7 +52,6 @@ import com.v2ray.ang.ui.compose.ReorderableListItem
|
|||||||
import com.v2ray.ang.ui.compose.SelectListDialog
|
import com.v2ray.ang.ui.compose.SelectListDialog
|
||||||
import com.v2ray.ang.ui.compose.SettingsSwitchItem
|
import com.v2ray.ang.ui.compose.SettingsSwitchItem
|
||||||
import com.v2ray.ang.ui.compose.verticalScrollbar
|
import com.v2ray.ang.ui.compose.verticalScrollbar
|
||||||
import com.v2ray.ang.util.QRCodeDecoder
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import sh.calvin.reorderable.ReorderableItem
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
@@ -83,7 +81,7 @@ class SubSettingActivity : BaseComponentActivity() {
|
|||||||
startActivity(Intent(this, SubEditActivity::class.java).putExtra("subId", subId))
|
startActivity(Intent(this, SubEditActivity::class.java).putExtra("subId", subId))
|
||||||
},
|
},
|
||||||
onRemoveSub = { subId -> removeSub(subId) },
|
onRemoveSub = { subId -> removeSub(subId) },
|
||||||
onShareQRCode = { url -> QRCodeDecoder.createQRCode(url) },
|
onShareQRCode = viewModel::shareQRCode,
|
||||||
onShareClipboard = { url ->
|
onShareClipboard = { url ->
|
||||||
Utils.setClipboard(this, url)
|
Utils.setClipboard(this, url)
|
||||||
toast(getString(R.string.toast_success))
|
toast(getString(R.string.toast_success))
|
||||||
@@ -110,7 +108,7 @@ fun SubSettingScreen(
|
|||||||
onSubUpdate: () -> Unit,
|
onSubUpdate: () -> Unit,
|
||||||
onEditSub: (String) -> Unit,
|
onEditSub: (String) -> Unit,
|
||||||
onRemoveSub: (String) -> Unit,
|
onRemoveSub: (String) -> Unit,
|
||||||
onShareQRCode: (String) -> Bitmap?,
|
onShareQRCode: (String) -> Unit,
|
||||||
onShareClipboard: (String) -> Unit
|
onShareClipboard: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val subscriptions by viewModel.subsFlow.collectAsStateWithLifecycle()
|
val subscriptions by viewModel.subsFlow.collectAsStateWithLifecycle()
|
||||||
@@ -119,7 +117,7 @@ fun SubSettingScreen(
|
|||||||
val confirmRemove = MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE, false)
|
val confirmRemove = MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE, false)
|
||||||
|
|
||||||
var shareTarget by remember { mutableStateOf<Pair<String, String>?>(null) }
|
var shareTarget by remember { mutableStateOf<Pair<String, String>?>(null) }
|
||||||
var showQRCodeBitmap by remember { mutableStateOf<Bitmap?>(null) }
|
val qrCodeBitmap by viewModel.qrCode.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
val reorderableState = rememberReorderableLazyListState(lazyListState) { from, to ->
|
val reorderableState = rememberReorderableLazyListState(lazyListState) { from, to ->
|
||||||
@@ -254,7 +252,7 @@ fun SubSettingScreen(
|
|||||||
onSelected = { action ->
|
onSelected = { action ->
|
||||||
shareTarget = null
|
shareTarget = null
|
||||||
when (action) {
|
when (action) {
|
||||||
SubscriptionShareAction.QRCode -> showQRCodeBitmap = onShareQRCode(url)
|
SubscriptionShareAction.QRCode -> onShareQRCode(url)
|
||||||
SubscriptionShareAction.Clipboard -> onShareClipboard(url)
|
SubscriptionShareAction.Clipboard -> onShareClipboard(url)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -263,10 +261,10 @@ fun SubSettingScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// QR Code Dialog
|
// QR Code Dialog
|
||||||
if (showQRCodeBitmap != null) {
|
if (qrCodeBitmap != null) {
|
||||||
QRCodeDialog(
|
QRCodeDialog(
|
||||||
bitmap = showQRCodeBitmap,
|
bitmap = qrCodeBitmap,
|
||||||
onDismiss = { showQRCodeBitmap = null }
|
onDismiss = viewModel::dismissQRCode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.v2ray.ang.ui.subscription
|
package com.v2ray.ang.ui.subscription
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.SubscriptionUpdateMessage
|
import com.v2ray.ang.dto.SubscriptionUpdateMessage
|
||||||
@@ -14,14 +16,20 @@ import com.v2ray.ang.handler.SettingsManager
|
|||||||
import com.v2ray.ang.helper.MessageHelper
|
import com.v2ray.ang.helper.MessageHelper
|
||||||
import com.v2ray.ang.ui.base.BaseViewModel
|
import com.v2ray.ang.ui.base.BaseViewModel
|
||||||
import com.v2ray.ang.util.LogUtil
|
import com.v2ray.ang.util.LogUtil
|
||||||
|
import com.v2ray.ang.util.QRCodeDecoder
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class SubscriptionsViewModel(application: Application) : BaseViewModel(application) {
|
class SubscriptionsViewModel(application: Application) : BaseViewModel(application) {
|
||||||
|
private var qrCodeJob: Job? = null
|
||||||
|
private val _qrCode = MutableStateFlow<Bitmap?>(null)
|
||||||
|
internal val qrCode = _qrCode.asStateFlow()
|
||||||
private val subscriptions: MutableList<SubscriptionCache> =
|
private val subscriptions: MutableList<SubscriptionCache> =
|
||||||
MmkvManager.decodeSubscriptions().toMutableList()
|
MmkvManager.decodeSubscriptions().toMutableList()
|
||||||
|
|
||||||
@@ -55,6 +63,21 @@ class SubscriptionsViewModel(application: Application) : BaseViewModel(applicati
|
|||||||
_subsFlow.value = subscriptions.toList()
|
_subsFlow.value = subscriptions.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun shareQRCode(url: String) {
|
||||||
|
dismissQRCode()
|
||||||
|
qrCodeJob = viewModelScope.launch {
|
||||||
|
val bitmap = withContext(Dispatchers.Default) { QRCodeDecoder.createQRCode(url) }
|
||||||
|
if (bitmap == null) toastError(R.string.toast_failure)
|
||||||
|
_qrCode.value = bitmap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dismissQRCode() {
|
||||||
|
qrCodeJob?.cancel()
|
||||||
|
qrCodeJob = null
|
||||||
|
_qrCode.value = null
|
||||||
|
}
|
||||||
|
|
||||||
fun move(fromPosition: Int, toPosition: Int) {
|
fun move(fromPosition: Int, toPosition: Int) {
|
||||||
if (subscriptions.moveItem(fromPosition, toPosition)) {
|
if (subscriptions.moveItem(fromPosition, toPosition)) {
|
||||||
MmkvManager.encodeSubsList(subscriptions.mapTo(mutableListOf()) { it.guid })
|
MmkvManager.encodeSubsList(subscriptions.mapTo(mutableListOf()) { it.guid })
|
||||||
|
|||||||
Reference in new issue
Block a user