Feat(ToolView): Support upload and download file
This commit is contained in:
@@ -2,14 +2,26 @@ package top.fatweb.oxygen.toolbox.util
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.annotation.RequiresApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
class NativeWebApi(
|
||||
private val context: Context,
|
||||
private val webView: WebView
|
||||
private val webView: WebView,
|
||||
private val permissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
||||
) {
|
||||
@JavascriptInterface
|
||||
fun copyToClipboard(text: String): Boolean {
|
||||
@@ -26,6 +38,65 @@ class NativeWebApi(
|
||||
return clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString() ?: ""
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun saveToDownloads(data: ByteArray, fileName: String): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
saveFileToDownloads(data = data, fileName = fileName)
|
||||
} else {
|
||||
saveFileToExternalDownloads(data = data, fileName = fileName)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun saveFileToDownloads(data: ByteArray, fileName: String): Boolean {
|
||||
val resolver = context.contentResolver
|
||||
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Downloads.MIME_TYPE, "application/octet-stream")
|
||||
put(MediaStore.Downloads.IS_PENDING, 1)
|
||||
}
|
||||
|
||||
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val fileUri = resolver.insert(collection, contentValues)
|
||||
|
||||
return fileUri?.let { uri ->
|
||||
resolver.openOutputStream(uri)?.use { outputStream ->
|
||||
outputStream.write(data)
|
||||
outputStream.flush()
|
||||
}
|
||||
|
||||
contentValues.clear()
|
||||
contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
|
||||
resolver.update(uri, contentValues, null, null)
|
||||
true
|
||||
} ?: let {
|
||||
Timber.e("Could not save file $fileName to Downloads")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveFileToExternalDownloads(data: ByteArray, fileName: String): Boolean {
|
||||
if (!runBlocking { Permissions.requestWriteExternalStoragePermission(context, permissionLauncher) }) {
|
||||
return false
|
||||
}
|
||||
|
||||
val downloadsDir =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
val file = File(downloadsDir, fileName)
|
||||
|
||||
return try {
|
||||
FileOutputStream(file).apply {
|
||||
write(data)
|
||||
close()
|
||||
}
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
Timber.e("Could not save file $fileName to ${file.absolutePath}", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun callback(callback: String, vararg args: Any) {
|
||||
val jsCode =
|
||||
"$callback(${args.map { if (it is String) "'$it'" else it }.joinToString(", ")})"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package top.fatweb.oxygen.toolbox.util
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
object Permissions {
|
||||
var continuation: Continuation<Boolean>? = null
|
||||
|
||||
suspend fun requestWriteExternalStoragePermission(
|
||||
context: Context,
|
||||
permissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
||||
): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
return true
|
||||
}
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
continuation.resume(true)
|
||||
} else {
|
||||
this.continuation = continuation
|
||||
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user