diff --git a/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt index 7391c17..f2ecb66 100644 --- a/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt +++ b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewView.kt @@ -231,6 +231,8 @@ fun XWebViewView( var showImageSourceDialog by remember { mutableStateOf(false) } var showStoragePermissionDialog by remember { mutableStateOf(false) } val pendingUrlDownload = remember { mutableStateOf?>(null) } + // blobdownload pending: (url, filename, base64Data) + val pendingBlobDownload = remember { mutableStateOf?>(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("取消") } }, )