فهرست منبع

添加打印功能

cc12458 1 ماه پیش
والد
کامیت
a0bed5100c

+ 14 - 0
library/browser/src/main/java/com/hzliuzhi/applet/browser/print/Print.kt

@@ -0,0 +1,14 @@
+package com.hzliuzhi.applet.browser.print
+
+import com.google.gson.Gson
+import com.google.gson.JsonElement
+
+data class Print(
+  val url: String?,
+) {
+  companion object {
+    fun formJson(element: JsonElement): Print {
+      return Gson().fromJson(element, Print::class.java)
+    }
+  }
+}

+ 151 - 0
library/browser/src/main/java/com/hzliuzhi/applet/browser/print/PrintEventHandler.kt

@@ -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}") }
+        }
+      })
+    }
+  }
+}
+
+

+ 3 - 1
library/browser/src/main/java/com/hzliuzhi/applet/browser/webview/WebViewBridge.kt

@@ -6,6 +6,7 @@ import android.webkit.JavascriptInterface
 import android.webkit.WebView
 import com.google.gson.Gson
 import com.google.gson.JsonElement
+import com.hzliuzhi.applet.browser.print.PrintEventHandle
 import com.hzliuzhi.applet.core.shared.Event
 import com.hzliuzhi.applet.core.shared.SharedFlowHub
 import com.hzliuzhi.applet.core.shared.SharedFlowHub.cast
@@ -59,6 +60,8 @@ class WebViewBridge(private val coroutineScope: CoroutineScope) {
         webview.evaluateJavascript("Bridge.getInstance().dispatch(${JSONObject.quote(payload)})", it.callback)
       }
     }.launchIn(coroutineScope)
+
+    PrintEventHandle(webview, coroutineScope)
   }
 
 
@@ -92,4 +95,3 @@ class WebViewBridge(private val coroutineScope: CoroutineScope) {
     }
   }
 }
-