修复搜索的时候,需要密码来加载。
This commit is contained in:
parent
ed7430e7d2
commit
e93d08b56f
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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()) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue
Block a user