修复搜索的时候,需要密码来加载。

This commit is contained in:
ocean 2025-10-27 10:37:35 +08:00
parent ed7430e7d2
commit e93d08b56f
7 changed files with 24 additions and 352 deletions

View File

@ -1,226 +0,0 @@
package com.all.pdfreader.pro.app.ui.act
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.databinding.ActivityPdfViewerTestBinding
import com.all.pdfreader.pro.app.ui.adapter.SearchResultsAdapter
import com.all.pdfreader.pro.app.util.PDFHighlighter
import com.all.pdfreader.pro.app.util.PDFSearchManager
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener
import com.tom_roush.pdfbox.pdmodel.PDDocument
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class PdfTextSearchTestActivity : BaseActivity() {
override val TAG: String = "PdfTextSearchTestActivity"
private lateinit var binding: ActivityPdfViewerTestBinding
private lateinit var searchManager: PDFSearchManager
private lateinit var highlighter: PDFHighlighter
private var pdfFile: File? = null
private val searchResults = mutableListOf<PDFSearchManager.TextSearchResult>()
private lateinit var adapter: SearchResultsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPdfViewerTestBinding.inflate(layoutInflater)
setContentView(binding.root)
val pdfPath = intent.getStringExtra("pdf_path") ?: ""
if (pdfPath.isEmpty()) {
finish()
return
}
pdfFile = File(pdfPath)
initViews()
setupPDFViewer()
loadPDF()
}
private fun initViews() {
adapter = SearchResultsAdapter(searchResults) { result ->
onSearchResultClicked(result, true)
}
binding.searchResultsRecyclerView.adapter = adapter
binding.searchResultsRecyclerView.layoutManager = LinearLayoutManager(this)
binding.searchButton.alpha = 0.5f
binding.searchButton.isEnabled = false
binding.searchButton.setOnClickListener {
performSearch()
}
binding.clearButton.setOnClickListener {
clearHighlights()
}
}
private fun setupPDFViewer() {
searchManager = PDFSearchManager(binding.pdfView)
highlighter = PDFHighlighter(binding.pdfView, searchManager)
initSearchDocument()
}
private fun initSearchDocument() {
lifecycleScope.launch(Dispatchers.IO) {
searchManager.getDocument(
pdfFile!!,
onLoaded = { doc ->
lifecycleScope.launch(Dispatchers.IO) {
checkPageHasText(doc, binding.pdfView.currentPage)
}
},
onError = { e ->
runOnUiThread {
binding.searchButton.alpha = 0.5f
binding.searchButton.isEnabled = false
}
}
)
}
}
/**
* 异步检查指定页是否有文字并更新按钮
*/
private suspend fun checkPageHasText(doc: PDDocument, pageIndex: Int): Boolean {
val hasText = searchManager.pageHasText(doc, pageIndex, minChars = 8) // 可调整阈值
withContext(Dispatchers.Main) {
if (hasText) {
binding.searchButton.alpha = 1f
binding.searchButton.isEnabled = true
} else {
binding.searchButton.alpha = 0.5f
binding.searchButton.isEnabled = false
}
}
return hasText
}
private var pageCheckJob: Job? = null
private fun loadPDF() {
binding.pdfView.fromFile(pdfFile)
.enableSwipe(true)
.swipeHorizontal(true)
.enableDoubletap(true)
.defaultPage(0)
.onDraw(highlighter) // 设置绘制监听器
.enableAnnotationRendering(true)
.password(null)
.scrollHandle(null)
.swipeHorizontal(false)
.enableAntialiasing(true)
.onPageChange(object : OnPageChangeListener {
override fun onPageChanged(page: Int, pageCount: Int) {
logDebug("page->$page")
logDebug("pageCount->$pageCount")
val doc = searchManager.getCachedDoc()
if (doc != null) {
lifecycleScope.launch(Dispatchers.IO) {
pageCheckJob?.cancel()
pageCheckJob = lifecycleScope.launch(Dispatchers.IO) {
delay(50) // 防抖
val hasText = checkPageHasText(doc, page)
//确定这页有文字,是否进行搜索过,当前搜索的文字
if (hasText && searchManager.hasSearched && searchManager.currentSearchText != null) {
searchTextIng(
pdfFile!!,
searchManager.currentSearchText ?: "",
binding.pdfView.currentPage
)
}
}
}
}
}
})
.spacing(0)
.load()
}
private fun performSearch() {
val searchText = binding.searchEditText.text.toString().trim()
if (searchText.isEmpty()) {
Toast.makeText(this, "请输入搜索关键词", Toast.LENGTH_SHORT).show()
return
}
val file = pdfFile ?: return
binding.progressBar.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
searchTextIng(file, searchText, binding.pdfView.currentPage)
}
}
private fun searchTextIng(pdfFile: File, key: String, targetPage: Int? = null) {
lifecycleScope.launch(Dispatchers.IO) {
try {
val results = searchManager.searchText(pdfFile, key, targetPage)
withContext(Dispatchers.Main) {
binding.progressBar.visibility = View.GONE
searchResults.clear()
searchResults.addAll(results)
adapter.notifyDataSetChanged()
if (!results.isEmpty()) {
results.firstOrNull()?.let { firstResult ->
onSearchResultClicked(firstResult)
}
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
binding.progressBar.visibility = View.GONE
}
}
}
}
private fun onSearchResultClicked(
result: PDFSearchManager.TextSearchResult,
isJumpTo: Boolean = false
) {
if (isJumpTo) {
// 跳转到对应页面 (PDFView 页面索引从0开始)
binding.pdfView.jumpTo(result.pageNumber - 1)
}
// 设置高亮
highlighter.setCurrentPageHighlights(result.pageNumber)
// 标记已搜索高亮
searchManager.currentSearchText = result.text
searchManager.hasSearched = true
// 可选:滚动到搜索结果列表中的对应项
val index = searchResults.indexOf(result)
if (index != -1) {
binding.searchResultsRecyclerView.scrollToPosition(index)
}
}
private fun clearHighlights() {
searchManager.clearHighlights()
highlighter.clearCurrentHighlights()
searchResults.clear()
adapter.notifyDataSetChanged()
}
override fun onDestroy() {
super.onDestroy()
searchManager.closeCachedDocument()
}
}

View File

@ -71,6 +71,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
private lateinit var searchManager: PDFSearchManager
private lateinit var highlighter: PDFHighlighter
private var pageCheckJob: Job? = null
private var currentPassword: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -269,7 +270,6 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
finish()
return
}
setupPDFSearchManager()//初始化搜索文本需要的类
if (pdfDocument.isPassword) {
showPasswordDialog(file)
} else {
@ -316,6 +316,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
pageCheckJob?.cancel()
// 启动新的防抖任务
pageCheckJob = lifecycleScope.launch(Dispatchers.IO) {
highlighter.clearCurrentHighlights()//新的之前先清除
delay(120) // 防抖 120ms
val hasText = checkPageHasText(doc, page)
if (hasText && searchManager.hasSearched && !searchManager.currentSearchText.isNullOrEmpty()) {
@ -339,6 +340,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
private fun showPasswordDialog(file: File) {
PdfPasswordProtectionDialogFragment(file, onOkClick = { password ->
tryLoadPdfWithPassword(file, password)
currentPassword = password
}, onCancelClick = {
finish()
}).show(supportFragmentManager, TAG)
@ -349,6 +351,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
}
private fun loadPdfInternal(file: File, password: String?) {
setupPDFSearchManager(password)//初始化搜索文本需要的类
binding.pdfview.fromFile(file).apply {
password?.let { password(it) } // 只有在有密码时才调用
defaultPage(pdfDocument.lastReadPage) // 从上次阅读页码开始
@ -454,17 +457,17 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
}
}
private fun setupPDFSearchManager() {
private fun setupPDFSearchManager(password: String?) {
searchManager = PDFSearchManager(binding.pdfview)
highlighter = PDFHighlighter(binding.pdfview, searchManager)
initSearchDocument()
initSearchDocument(password)
}
private fun initSearchDocument() {
private fun initSearchDocument(password: String?) {
val file = File(pdfDocument.filePath)
lifecycleScope.launch(Dispatchers.IO) {
searchManager.getDocument(file, onLoaded = { doc ->
searchManager.getDocument(file, password, onLoaded = { doc ->
lifecycleScope.launch(Dispatchers.IO) {
checkPageHasText(doc, binding.pdfview.currentPage)
}
@ -504,7 +507,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
private fun searchTextIng(pdfFile: File, key: String, targetPage: Int? = null) {
lifecycleScope.launch(Dispatchers.IO) {
try {
val results = searchManager.searchText(pdfFile, key, targetPage)
val results = searchManager.searchText(pdfFile, key, targetPage, currentPassword)
withContext(Dispatchers.Main) {
if (!results.isEmpty()) {

View File

@ -1,41 +0,0 @@
package com.all.pdfreader.pro.app.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.util.PDFSearchManager
class SearchResultsAdapter(
private val results: List<PDFSearchManager.TextSearchResult>,
private val onItemClick: (PDFSearchManager.TextSearchResult) -> Unit
) : RecyclerView.Adapter<SearchResultsAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val pageText: TextView = view.findViewById(R.id.pageText)
val matchCount: TextView = view.findViewById(R.id.matchCount)
val previewText: TextView = view.findViewById(R.id.previewText)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_search_result, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val result = results[position]
holder.pageText.text = "${result.pageNumber}"
holder.matchCount.text = "${result.positions.size} 处匹配"
holder.previewText.text = "搜索: \"${result.text}\""
holder.itemView.setOnClickListener {
onItemClick(result)
}
}
override fun getItemCount() = results.size
}

View File

@ -14,7 +14,6 @@ import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.act.MainActivity
import com.all.pdfreader.pro.app.ui.act.PdfTextSearchTestActivity
import com.all.pdfreader.pro.app.ui.act.PdfViewActivity
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment

View File

@ -67,6 +67,7 @@ class PDFSearchManager(private val pdfView: PDFView) {
*/
fun getDocument(
file: File,
password: String?,
onLoaded: ((PDDocument) -> Unit)? = null,
onError: ((Exception) -> Unit)? = null
): PDDocument {
@ -74,7 +75,11 @@ class PDFSearchManager(private val pdfView: PDFView) {
try {
// 如果是新的文件,则关闭旧文档
cachedDoc?.close()
cachedDoc = PDDocument.load(file)
cachedDoc = if (!password.isNullOrEmpty()) {
PDDocument.load(file, password)
} else {
PDDocument.load(file)
}
cachedPath = file.absolutePath
// 通知加载成功
@ -114,14 +119,19 @@ class PDFSearchManager(private val pdfView: PDFView) {
* @param key 搜索关键词
* @param targetPage 可选参数指定页码 0 开始如果为 null 则搜索整本
*/
fun searchText(pdfFile: File, key: String, targetPage: Int? = null): List<TextSearchResult> {
fun searchText(pdfFile: File, key: String, targetPage: Int? = null,password: String?): List<TextSearchResult> {
Log.d("ocean", "searchText pdfFile->$pdfFile")
Log.d("ocean", "searchText key->$key")
Log.d("ocean", "searchText targetPage->$targetPage")
val results = mutableListOf<TextSearchResult>()
try {
val doc = getDocument(pdfFile) // 这里不再每次重新 load
// 这里不再每次重新 load
val doc = if (!password.isNullOrEmpty()) {
PDDocument.load(pdfFile, password)
} else {
PDDocument.load(pdfFile)
}
val startPage = targetPage ?: 0
val endPage = targetPage ?: (doc.numberOfPages - 1)

View File

@ -21,6 +21,7 @@
android:id="@+id/sidebarBtn"
android:layout_width="44dp"
android:layout_height="44dp"
android:visibility="gone"
android:layout_marginStart="6dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
@ -36,7 +37,7 @@
style="@style/TextViewFont_PopMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="6dp"
android:layout_weight="1"
android:text="@string/app_name"

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 搜索栏 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f5f5f5"
android:orientation="horizontal"
android:padding="8dp">
<EditText
android:id="@+id/searchEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="输入搜索关键词"
android:imeOptions="actionSearch"
android:singleLine="true" />
<Button
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="搜索" />
<Button
android:id="@+id/clearButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="清除" />
</LinearLayout>
<TextView
android:id="@+id/debugTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- PDF显示区域 -->
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
<!-- 搜索结果列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchResultsRecyclerView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff" />
</LinearLayout>
</LinearLayout>