feat(webview): 添加blob下载功能并完善存储权限处理

- 添加pendingBlobDownload状态管理blob下载请求
- 实现API 30+系统上MANAGE_EXTERNAL_STORAGE权限检查
- 添加blob数据解码和文件保存功能
- 集成下载完成事件分发机制
- 完善存储权限对话框中的状态清理逻辑
- 实现blob下载成功后的相册保存或文件打开操作
这个提交包含在:
XuqmGroup 2026-06-18 11:52:02 +08:00
父节点 aca0d009a8
当前提交 f0276bb440

查看文件

@ -231,6 +231,8 @@ fun XWebViewView(
var showImageSourceDialog by remember { mutableStateOf(false) }
var showStoragePermissionDialog by remember { mutableStateOf(false) }
val pendingUrlDownload = remember { mutableStateOf<Pair<String, String?>?>(null) }
// blobdownload pending: (url, filename, base64Data)
val pendingBlobDownload = remember { mutableStateOf<Triple<String, String, String>?>(null) }
val notificationPermissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
@ -304,6 +306,15 @@ fun XWebViewView(
android.util.Log.e("XWV", "blobdownload: empty data")
return@handler
}
// On API 30+, writing to public Downloads requires MANAGE_EXTERNAL_STORAGE.
if (config.downloadDestination == FileDownloadDestination.PublicDownloads &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
!FileSDK.isManageStorageGranted(context)
) {
pendingBlobDownload.value = Triple(url, filename, b64)
showStoragePermissionDialog = true
return@handler
}
android.util.Log.d("XWV", "blobdownload: filename=$filename, b64Len=${b64.length}, dest=${config.downloadDestination}")
coroutineScope.launch(Dispatchers.IO) {
runCatching {
@ -402,6 +413,7 @@ fun XWebViewView(
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
val pd = pendingUrlDownload.value
val pbd = pendingBlobDownload.value
if (pd != null && FileSDK.isManageStorageGranted(context)) {
val (url, filename) = pd
pendingUrlDownload.value = null
@ -436,6 +448,27 @@ fun XWebViewView(
}
}
}
if (pbd != null && FileSDK.isManageStorageGranted(context)) {
val (url, filename, b64) = pbd
pendingBlobDownload.value = null
coroutineScope.launch(Dispatchers.IO) {
runCatching {
FileSDK.saveBlobDownload(context, b64, filename, config.downloadDestination)
}.onSuccess { file ->
android.util.Log.d("XWV", "blobdownload saved: ${file.absolutePath}, size=${file.length()}")
val savedToGallery = runCatching { FileSDK.saveImageToGallery(context, file) }.getOrDefault(false)
withContext(Dispatchers.Main) {
dispatchDownloadEvent("__xwvDownloadDone", url, ",success:true")
if (!savedToGallery) FileSDK.openFile(context, file)
}
}.onFailure { e ->
android.util.Log.e("XWV", "blobdownload FAILED", e)
withContext(Dispatchers.Main) {
dispatchDownloadEvent("__xwvDownloadDone", url, ",success:false,error:'${e.message?.escapeJs()}'")
}
}
}
}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
@ -564,6 +597,7 @@ fun XWebViewView(
onDismissRequest = {
showStoragePermissionDialog = false
pendingUrlDownload.value = null
pendingBlobDownload.value = null
},
title = { Text("需要存储权限") },
text = { Text("下载文件需要「所有文件访问权限」,请在设置中授权后将自动继续下载。") },
@ -577,6 +611,7 @@ fun XWebViewView(
TextButton(onClick = {
showStoragePermissionDialog = false
pendingUrlDownload.value = null
pendingBlobDownload.value = null
}) { Text("取消") }
},
)