|
@@ -0,0 +1,151 @@
|
|
|
+package com.hzliuzhi.applet.browser.print
|
|
|
+
|
|
|
+import android.content.Context
|
|
|
+import android.os.Bundle
|
|
|
+import android.os.CancellationSignal
|
|
|
+import android.os.ParcelFileDescriptor
|
|
|
+import android.print.PageRange
|
|
|
+import android.print.PrintAttributes
|
|
|
+import android.print.PrintDocumentAdapter
|
|
|
+import android.print.PrintDocumentInfo
|
|
|
+import android.print.PrintManager
|
|
|
+import android.webkit.WebView
|
|
|
+import com.google.gson.JsonElement
|
|
|
+import com.hzliuzhi.applet.core.shared.Payload
|
|
|
+import com.hzliuzhi.applet.core.shared.SharedFlowHub
|
|
|
+import com.hzliuzhi.applet.core.shared.SharedFlowHub.callbackAs
|
|
|
+import com.hzliuzhi.applet.core.shared.SharedFlowHub.cast
|
|
|
+import kotlinx.coroutines.CoroutineScope
|
|
|
+import kotlinx.coroutines.flow.filter
|
|
|
+import kotlinx.coroutines.flow.launchIn
|
|
|
+import kotlinx.coroutines.flow.onEach
|
|
|
+import okhttp3.Call
|
|
|
+import okhttp3.Callback
|
|
|
+import okhttp3.OkHttpClient
|
|
|
+import okhttp3.Request
|
|
|
+import okhttp3.Response
|
|
|
+import okio.buffer
|
|
|
+import okio.sink
|
|
|
+import java.io.File
|
|
|
+import java.io.FileInputStream
|
|
|
+import java.io.FileOutputStream
|
|
|
+import java.io.IOException
|
|
|
+
|
|
|
+class PrintEventHandle(webView: WebView, scope: CoroutineScope) {
|
|
|
+ private val printManager: PrintManager = webView.context.getSystemService(Context.PRINT_SERVICE) as PrintManager
|
|
|
+
|
|
|
+ init {
|
|
|
+ android.util.Log.d("log:bridge", "PrintEventHandle 被注入")
|
|
|
+ SharedFlowHub.events
|
|
|
+ .filter { it.type.contains("print") }
|
|
|
+ .onEach { _event ->
|
|
|
+ when (_event.type) {
|
|
|
+
|
|
|
+ "${SharedFlowHub.WEBVIEW_BRIDGE_EVENT}:print" -> {
|
|
|
+ _event.cast<JsonElement, JsonElement>()?.also { event ->
|
|
|
+ val print = event.payload?.let { it -> Print.formJson(it) }
|
|
|
+ val callback = event.callback ?: {}
|
|
|
+ handlePrint(webView, print) { payload ->
|
|
|
+ payload.toEvent()?.also { callback(it) }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ else -> _event.callbackAs<Payload<Unit>>()?.invoke(Payload.error(message = "[print] 未实现 ${_event.type}"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .launchIn(scope)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun handlePrint(webView: WebView, print: Print?, callback: ((Payload<Boolean?>) -> Unit)) {
|
|
|
+ val attributes = PrintAttributes.Builder().build()
|
|
|
+
|
|
|
+ print?.url.takeUnless { it.isNullOrEmpty() }?.also {
|
|
|
+ val context = webView.context.applicationContext
|
|
|
+ PdfPrintAdapter.download(
|
|
|
+ context = context, print = print!!,
|
|
|
+ onFinished = { file ->
|
|
|
+ val jobName = "${context.getString(android.R.string.untitled)}_${file.name}"
|
|
|
+ val adapter = PdfPrintAdapter(file, { callback(Payload.data(true));true }, { callback(Payload.error(message = it)); true })
|
|
|
+ printManager.print(jobName, adapter, attributes)
|
|
|
+ },
|
|
|
+ onError = { callback(Payload.error(message = it)) }
|
|
|
+ )
|
|
|
+ } ?: webView.post {
|
|
|
+ val jobName = "${webView.context.getString(android.R.string.untitled)}_page"
|
|
|
+ val adapter = webView.createPrintDocumentAdapter("WebViewDocument")
|
|
|
+ printManager.print(jobName, adapter, attributes)
|
|
|
+ callback(Payload.data(true))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private class PdfPrintAdapter(
|
|
|
+ private val file: File,
|
|
|
+ private val onFinished: () -> Boolean = { true },
|
|
|
+ private val onError: (String) -> Boolean = { true },
|
|
|
+) : PrintDocumentAdapter() {
|
|
|
+ override fun onLayout(
|
|
|
+ oldAttributes: PrintAttributes?,
|
|
|
+ newAttributes: PrintAttributes?,
|
|
|
+ cancellationSignal: CancellationSignal?,
|
|
|
+ callback: LayoutResultCallback?,
|
|
|
+ extras: Bundle?,
|
|
|
+ ) {
|
|
|
+ cancellationSignal?.takeIf { it.isCanceled }?.also { callback?.onLayoutCancelled(); return }
|
|
|
+
|
|
|
+ PrintDocumentInfo.Builder(file.name).apply {
|
|
|
+ setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
|
|
|
+ setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
|
|
|
+ }.build().also { callback?.onLayoutFinished(it, true) }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onWrite(
|
|
|
+ pages: Array<out PageRange>?,
|
|
|
+ destination: ParcelFileDescriptor,
|
|
|
+ cancellationSignal: CancellationSignal?,
|
|
|
+ callback: WriteResultCallback?,
|
|
|
+ ) {
|
|
|
+ runCatching {
|
|
|
+ FileInputStream(file).use { input ->
|
|
|
+ FileOutputStream(destination.fileDescriptor).use { output ->
|
|
|
+ input.copyTo(output)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.onSuccess {
|
|
|
+ callback?.onWriteFinished(arrayOf(PageRange.ALL_PAGES))
|
|
|
+ }.onFailure {
|
|
|
+ callback?.onWriteFailed(it.message)
|
|
|
+ onError("打印错误: ${it.message ?: "失败"}").takeIf { remove -> remove }?.also { file.delete() }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onFinish() {
|
|
|
+ super.onFinish()
|
|
|
+ onFinished().takeIf { remove -> remove }?.also { file.delete() }
|
|
|
+ }
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ fun download(context: Context, print: Print, onFinished: (File) -> Unit, onError: (String) -> Unit) {
|
|
|
+ val request = Request.Builder().url(print.url!!).build()
|
|
|
+ OkHttpClient().newCall(request).enqueue(object : Callback {
|
|
|
+ override fun onFailure(call: Call, e: IOException) {
|
|
|
+ onError("下载失败: IO 错误")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onResponse(call: Call, response: Response) {
|
|
|
+ if (!response.isSuccessful) onError("下载失败: ${response.message}").also { return }
|
|
|
+ runCatching {
|
|
|
+ File(context.cacheDir, "temp_${System.currentTimeMillis()}.pdf").apply {
|
|
|
+ sink().buffer().use { sikp ->
|
|
|
+ response.body?.source()?.let { sikp.writeAll(it) }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.onSuccess { onFinished(it) }.onFailure { onError("下载失败: ${it.message}") }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|