agent-Specialization/android-webview-app/app/src/main/java/com/cyjai/agent/PdfPreviewActivity.kt
JOJO e4a663594d feat: Android App 原生 PDF 预览与前端 show_file 卡片
- 集成 AndroidPdfViewer (mhiew fork) 实现不依赖浏览器的 PDF 预览
- 新增 PdfPreviewActivity 与 JS Bridge (AndroidPdfBridge)
- ShowFileCard 在 Android App 内对 PDF 显示「预览」按钮,不内嵌 iframe
- 新增 /api/file/content 后端接口用于文件内容 inline 预览
- 前端支持 <show_file> 标签与 download:// 链接渲染
- 更新 Android 版本号至 1.0.35
2026-06-24 00:06:10 +08:00

151 lines
4.5 KiB
Kotlin

package com.cyjai.agent
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.github.barteksc.pdfviewer.PDFView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL
class PdfPreviewActivity : AppCompatActivity() {
companion object {
const val EXTRA_PDF_URL = "pdf_url"
private const val HOME_URL = "https://agent.cyjai.com"
}
private lateinit var pdfView: PDFView
private lateinit var loadingContainer: LinearLayout
private lateinit var progressBar: ProgressBar
private lateinit var statusText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pdf_preview)
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
title = "PDF 预览"
}
pdfView = findViewById(R.id.pdfView)
loadingContainer = findViewById(R.id.loadingContainer)
progressBar = findViewById(R.id.progressBar)
statusText = findViewById(R.id.statusText)
val rawUrl = intent.getStringExtra(EXTRA_PDF_URL) ?: ""
if (rawUrl.isBlank()) {
showError("PDF 地址为空")
return
}
val url = if (rawUrl.startsWith("http://") || rawUrl.startsWith("https://")) {
rawUrl
} else {
HOME_URL.trimEnd('/') + (if (rawUrl.startsWith("/")) rawUrl else "/$rawUrl")
}
loadPdf(url)
}
private fun showLoading(msg: String) {
loadingContainer.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
statusText.visibility = View.VISIBLE
statusText.text = msg
pdfView.visibility = View.INVISIBLE
}
private fun hideLoading() {
loadingContainer.visibility = View.GONE
progressBar.visibility = View.GONE
statusText.visibility = View.GONE
pdfView.visibility = View.VISIBLE
}
private fun showError(msg: String) {
loadingContainer.visibility = View.VISIBLE
progressBar.visibility = View.GONE
statusText.visibility = View.VISIBLE
statusText.text = msg
pdfView.visibility = View.INVISIBLE
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
private fun loadPdf(url: String) {
showLoading("加载中...")
lifecycleScope.launch(Dispatchers.IO) {
try {
val file = downloadToCache(url)
withContext(Dispatchers.Main) {
showPdf(file)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
showError("PDF 加载失败: ${e.message}")
}
}
}
}
private fun downloadToCache(url: String): File {
val conn = URL(url).openConnection() as HttpURLConnection
conn.connectTimeout = 30_000
conn.readTimeout = 60_000
conn.setRequestProperty("Accept", "application/pdf,*/*")
conn.connect()
if (conn.responseCode !in 200..299) {
throw RuntimeException("HTTP ${conn.responseCode}")
}
val pathParam = Uri.parse(url).getQueryParameter("path")
val fileName = if (!pathParam.isNullOrBlank()) {
"preview_" + pathParam.replace("/", "_")
} else {
"preview_${System.currentTimeMillis()}.pdf"
}
val file = File(cacheDir, fileName)
FileOutputStream(file).use { out ->
conn.inputStream.use { input ->
input.copyTo(out)
}
}
conn.disconnect()
return file
}
private fun showPdf(file: File) {
pdfView.fromFile(file)
.enableSwipe(true)
.swipeHorizontal(false)
.enableDoubletap(true)
.defaultPage(0)
.onError { showError("PDF 渲染失败") }
.onLoad { hideLoading() }
.load()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
finish()
true
} else {
super.onOptionsItemSelected(item)
}
}
}