优化搜索界面,添加工具加密等操作。

This commit is contained in:
ocean 2025-10-10 18:47:32 +08:00
parent 6ffb08dc4f
commit 5bf2952c03
20 changed files with 217 additions and 77 deletions

View File

@ -74,7 +74,7 @@
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.act.SplitPdfResultActivity" android:name=".ui.act.PdfResultActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />

View File

@ -4,7 +4,8 @@ enum class PdfPickerSource {
NONE, // 没有任何 NONE, // 没有任何
MERGE, // PDF合并 MERGE, // PDF合并
SPLIT, // PDF拆分 SPLIT, // PDF拆分
LOCK, // PDF加密/解密 LOCK, // PDF加密
UNLOCK, // PDF解密
TO_IMAGES, // PDF转图片 TO_IMAGES, // PDF转图片
TO_LONG_IMAGE // PDF转长图 TO_LONG_IMAGE // PDF转长图
} }

View File

@ -3,5 +3,6 @@ package com.all.pdfreader.pro.app.model
data class PdfSplitResultItem( data class PdfSplitResultItem(
val filePath: String, val filePath: String,
val thumbnailPath: String? = null, val thumbnailPath: String? = null,
var isSelected: Boolean var isSelected: Boolean = false,
var isPassword: Boolean = false
) )

View File

@ -62,7 +62,7 @@ class MergePdfActivity : BaseActivity() {
} }
binding.continueNowBtn.setOnSingleClickListener { binding.continueNowBtn.setOnSingleClickListener {
val list = selectedList.map { it.filePath } val list = selectedList.map { it.filePath }
val intent = SplitPdfResultActivity.createIntentInputFile( val intent = PdfResultActivity.createIntentInputFile(
this, ArrayList(list), PdfPickerSource.MERGE this, ArrayList(list), PdfPickerSource.MERGE
) )
startActivity(intent) startActivity(intent)

View File

@ -15,6 +15,7 @@ import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.PdfSetPasswordDialog
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
@ -80,7 +81,21 @@ class PdfPickerActivity : BaseActivity() {
finish() finish()
} }
PdfPickerSource.LOCK -> {} PdfPickerSource.LOCK -> {
PdfSetPasswordDialog(onOkClick = { password ->
val intent = PdfResultActivity.createIntentLock(
this, pdf.filePath, password,
PdfPickerSource.LOCK
)
startActivity(intent)
finish()
}).show(supportFragmentManager, "PdfSetPasswordDialog")
}
PdfPickerSource.UNLOCK -> {
}
PdfPickerSource.TO_IMAGES -> {} PdfPickerSource.TO_IMAGES -> {}
PdfPickerSource.TO_LONG_IMAGE -> {} PdfPickerSource.TO_LONG_IMAGE -> {}
PdfPickerSource.NONE -> {} PdfPickerSource.NONE -> {}
@ -109,17 +124,25 @@ class PdfPickerActivity : BaseActivity() {
private fun initData() { private fun initData() {
lifecycleScope.launch { lifecycleScope.launch {
PdfRepository.getInstance().getAllDocuments().collect { list -> PdfRepository.getInstance().getAllDocuments().collect { list ->
if (list.isEmpty()) {
binding.noFilesLayout.visibility = View.VISIBLE
return@collect
}
val sortedList = sortDocuments(list) val sortedList = sortDocuments(list)
// 标记已选择项 // 标记已选择项
if (fromActivityResult == MergePdfActivity.TAG) { if (fromActivityResult == MergePdfActivity.TAG) {
markSelectedItems(sortedList, historyList) markSelectedItems(sortedList, historyList)
} }
if (list.isNotEmpty()) { // 再根据来源过滤
adapter.updateData(sortedList) val displayList = when (source) {
binding.noFilesLayout.visibility = View.GONE PdfPickerSource.LOCK -> sortedList.filter { !it.isPassword }//只展示没有被加密的
} else { PdfPickerSource.UNLOCK -> sortedList.filter { it.isPassword }//只展示已经被加密的
binding.noFilesLayout.visibility = View.VISIBLE else -> sortedList
} }
adapter.updateData(displayList)
binding.noFilesLayout.visibility =
if (displayList.isEmpty()) View.VISIBLE else View.GONE
} }
} }
} }

View File

@ -1,6 +1,5 @@
package com.all.pdfreader.pro.app.ui.act package com.all.pdfreader.pro.app.ui.act
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
@ -16,11 +15,13 @@ import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitResultBinding
import com.all.pdfreader.pro.app.model.PdfPickerSource import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem
import com.all.pdfreader.pro.app.model.PdfSplitResultItem import com.all.pdfreader.pro.app.model.PdfSplitResultItem
import com.all.pdfreader.pro.app.ui.adapter.SplitPdfResultAdapter import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.adapter.PdfResultAdapter
import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment
import com.all.pdfreader.pro.app.util.AppUtils import com.all.pdfreader.pro.app.util.AppUtils
import com.all.pdfreader.pro.app.util.FileUtils.toUnderscoreDateTime import com.all.pdfreader.pro.app.util.FileUtils.toUnderscoreDateTime
import com.all.pdfreader.pro.app.util.PdfScanner import com.all.pdfreader.pro.app.util.PdfScanner
import com.all.pdfreader.pro.app.util.PdfSecurityUtils
import com.all.pdfreader.pro.app.util.PdfUtils import com.all.pdfreader.pro.app.util.PdfUtils
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -29,18 +30,20 @@ import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.Serializable import java.io.Serializable
class SplitPdfResultActivity : BaseActivity() { class PdfResultActivity : BaseActivity() {
override val TAG: String = "SplitPdfResultActivity" override val TAG: String = "PdfResultActivity"
companion object { companion object {
private const val EXTRA_SELECTED_LIST = "extra_selected_list" private const val EXTRA_SELECTED_LIST = "extra_selected_list"
private const val EXTRA_FILE_LIST = "extra_file_list" private const val EXTRA_FILE_LIST = "extra_file_list"
private const val EXTRA_SOURCE = "extra_source" private const val EXTRA_SOURCE = "extra_source"
private const val EXTRA_PASSWORD = "extra_password"
private const val EXTRA_FILE_PATH = "extra_file_path"
fun createIntentPdfSelectedPagesItem( fun createIntentPdfSelectedPagesItem(
context: Context, list: ArrayList<PdfSelectedPagesItem>, source: PdfPickerSource context: Context, list: ArrayList<PdfSelectedPagesItem>, source: PdfPickerSource
): Intent { ): Intent {
return Intent(context, SplitPdfResultActivity::class.java).apply { return Intent(context, PdfResultActivity::class.java).apply {
putParcelableArrayListExtra(EXTRA_SELECTED_LIST, list) putParcelableArrayListExtra(EXTRA_SELECTED_LIST, list)
putExtra(EXTRA_SOURCE, source) putExtra(EXTRA_SOURCE, source)
} }
@ -49,23 +52,35 @@ class SplitPdfResultActivity : BaseActivity() {
fun createIntentInputFile( fun createIntentInputFile(
context: Context, list: ArrayList<String>, source: PdfPickerSource context: Context, list: ArrayList<String>, source: PdfPickerSource
): Intent { ): Intent {
return Intent(context, SplitPdfResultActivity::class.java).apply { return Intent(context, PdfResultActivity::class.java).apply {
putStringArrayListExtra(EXTRA_FILE_LIST, list) putStringArrayListExtra(EXTRA_FILE_LIST, list)
putExtra(EXTRA_SOURCE, source) putExtra(EXTRA_SOURCE, source)
} }
} }
fun createIntentLock(
context: Context, filepath: String, password: String, source: PdfPickerSource
): Intent {
return Intent(context, PdfResultActivity::class.java).apply {
putExtra(EXTRA_FILE_PATH, filepath)
putExtra(EXTRA_PASSWORD, password)
putExtra(EXTRA_SOURCE, source)
}
}
} }
private lateinit var binding: ActivityPdfSplitResultBinding private lateinit var binding: ActivityPdfSplitResultBinding
private lateinit var adapter: SplitPdfResultAdapter private lateinit var adapter: PdfResultAdapter
private var resultList: MutableList<PdfSplitResultItem> = mutableListOf() private var resultList: MutableList<PdfSplitResultItem> = mutableListOf()
private lateinit var selectedList: ArrayList<PdfSelectedPagesItem> private lateinit var selectedList: ArrayList<PdfSelectedPagesItem>
private lateinit var inputFile: ArrayList<String> private lateinit var inputFile: ArrayList<String>
private lateinit var source: PdfPickerSource private lateinit var source: PdfPickerSource
private var isSplitting = false private var isProcessing = false
private var exitDialog: PromptDialogFragment? = null private var exitDialog: PromptDialogFragment? = null
private val pdfRepository = getRepository() private val pdfRepository = getRepository()
private lateinit var pdfScanner: PdfScanner private lateinit var pdfScanner: PdfScanner
private lateinit var filepath: String
private lateinit var password: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -76,6 +91,8 @@ class SplitPdfResultActivity : BaseActivity() {
.navigationBarColor(R.color.bg_color).init() .navigationBarColor(R.color.bg_color).init()
selectedList = requireParcelableArrayList(EXTRA_SELECTED_LIST) selectedList = requireParcelableArrayList(EXTRA_SELECTED_LIST)
inputFile = requireStringArrayList(EXTRA_FILE_LIST) inputFile = requireStringArrayList(EXTRA_FILE_LIST)
filepath = intent.getStringExtra(EXTRA_FILE_PATH) ?: ""
password = intent.getStringExtra(EXTRA_PASSWORD) ?: ""
source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE) source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE)
if (source == PdfPickerSource.NONE) { if (source == PdfPickerSource.NONE) {
showToast(getString(R.string.pdf_loading_failed)) showToast(getString(R.string.pdf_loading_failed))
@ -94,6 +111,12 @@ class SplitPdfResultActivity : BaseActivity() {
finish() finish()
return return
} }
} else if (source == PdfPickerSource.LOCK) {
if (filepath.isEmpty() || password.isEmpty()) {
showToast(getString(R.string.pdf_loading_failed))
finish()
return
}
} }
} }
pdfScanner = PdfScanner(this, pdfRepository) pdfScanner = PdfScanner(this, pdfRepository)
@ -103,7 +126,7 @@ class SplitPdfResultActivity : BaseActivity() {
} }
private fun initView() { private fun initView() {
adapter = SplitPdfResultAdapter(resultList) adapter = PdfResultAdapter(resultList)
binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
} }
@ -111,7 +134,7 @@ class SplitPdfResultActivity : BaseActivity() {
private fun initData() { private fun initData() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
isSplitting = true isProcessing = true
binding.processingLayout.visibility = View.VISIBLE binding.processingLayout.visibility = View.VISIBLE
binding.progressBar.isIndeterminate = false binding.progressBar.isIndeterminate = false
binding.progressBar.progress = 0 binding.progressBar.progress = 0
@ -143,7 +166,7 @@ class SplitPdfResultActivity : BaseActivity() {
} }
})?.let { resultFile -> })?.let { resultFile ->
val thumbnails = val thumbnails =
AppUtils.generateFastThumbnail(this@SplitPdfResultActivity, resultFile) AppUtils.generateFastThumbnail(this@PdfResultActivity, resultFile)
val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false) val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false)
pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) { pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) {
resultList.add(result) resultList.add(result)
@ -172,13 +195,27 @@ class SplitPdfResultActivity : BaseActivity() {
} }
})?.let { resultFile -> })?.let { resultFile ->
val thumbnails = val thumbnails =
AppUtils.generateFastThumbnail(this@SplitPdfResultActivity, resultFile) AppUtils.generateFastThumbnail(this@PdfResultActivity, resultFile)
val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false) val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false)
pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) { pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) {
resultList.add(result) resultList.add(result)
} }
} }
} }
} else if (source == PdfPickerSource.LOCK) {
binding.congratulationsDesc.text = getString(R.string.set_password_successfully)
PdfSecurityUtils.setPasswordToPdfWithProgress(
filepath, password, password
) { progress ->
binding.progressBar.progress = progress
binding.progressTv.text = "$progress"
}.let { it ->
if (it) {
PdfRepository.getInstance().updateIsPassword(filepath, true)
}
val result = PdfSplitResultItem(filePath = filepath, isPassword = true)
resultList.add(result)
}
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.processingLayout.visibility = View.GONE binding.processingLayout.visibility = View.GONE
@ -187,7 +224,7 @@ class SplitPdfResultActivity : BaseActivity() {
resultList[0].isSelected = true resultList[0].isSelected = true
} }
adapter.updateAdapter() adapter.updateAdapter()
isSplitting = false//拆分结束 isProcessing = false//拆分结束
exitDialog?.dismissAllowingStateLoss() exitDialog?.dismissAllowingStateLoss()
exitDialog = null exitDialog = null
} }
@ -216,12 +253,15 @@ class SplitPdfResultActivity : BaseActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
isProcessing = false
exitDialog?.dismissAllowingStateLoss()
exitDialog = null
} }
private fun setupBackPressedCallback() { private fun setupBackPressedCallback() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
if (isSplitting) { if (isProcessing) {
exitDialog = PromptDialogFragment( exitDialog = PromptDialogFragment(
getString(R.string.exit_split), getString(R.string.exit_split),
getString(R.string.confirm_discard_changes), getString(R.string.confirm_discard_changes),

View File

@ -25,6 +25,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.json.JSONArray import org.json.JSONArray
import kotlin.math.log
class SearchActivity : BaseActivity() { class SearchActivity : BaseActivity() {
override val TAG: String = "SearchActivity" override val TAG: String = "SearchActivity"
@ -87,8 +88,9 @@ class SearchActivity : BaseActivity() {
delay(150) delay(150)
pdfRepository.searchDocuments(query).collectLatest { list -> pdfRepository.searchDocuments(query).collectLatest { list ->
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
adapter.updateData(list) adapter.updateData(list) {
adapter.highlightItems(query.trim()) adapter.highlightItems(query.trim())
}
binding.noFilesLayout.visibility = View.GONE binding.noFilesLayout.visibility = View.GONE
} else { } else {
adapter.updateData(emptyList()) adapter.updateData(emptyList())

View File

@ -5,9 +5,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.R
@ -140,7 +138,7 @@ class SplitPdfActivity : BaseActivity() {
binding.splitBtn.setOnSingleClickListener { binding.splitBtn.setOnSingleClickListener {
logDebug("${selectedList.size}") logDebug("${selectedList.size}")
//因为图片做的路径缓存方式所以这里直接传入整个集合到result页处理 //因为图片做的路径缓存方式所以这里直接传入整个集合到result页处理
val intent = SplitPdfResultActivity.createIntentPdfSelectedPagesItem( val intent = PdfResultActivity.createIntentPdfSelectedPagesItem(
this, this,
ArrayList(selectedList), ArrayList(selectedList),
PdfPickerSource.SPLIT PdfPickerSource.SPLIT

View File

@ -6,7 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.AdapterSplitSelectedResultItemBinding import com.all.pdfreader.pro.app.databinding.AdapterSelectedResultItemBinding
import com.all.pdfreader.pro.app.model.PdfSplitResultItem import com.all.pdfreader.pro.app.model.PdfSplitResultItem
import com.all.pdfreader.pro.app.util.AppUtils.dpToPx import com.all.pdfreader.pro.app.util.AppUtils.dpToPx
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -14,15 +14,15 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import java.io.File import java.io.File
class SplitPdfResultAdapter( class PdfResultAdapter(
private val list: MutableList<PdfSplitResultItem> private val list: MutableList<PdfSplitResultItem>
) : RecyclerView.Adapter<SplitPdfResultAdapter.PdfViewHolder>() { ) : RecyclerView.Adapter<PdfResultAdapter.PdfViewHolder>() {
inner class PdfViewHolder(val binding: AdapterSplitSelectedResultItemBinding) : inner class PdfViewHolder(val binding: AdapterSelectedResultItemBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PdfViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PdfViewHolder(
AdapterSplitSelectedResultItemBinding.inflate( AdapterSelectedResultItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false LayoutInflater.from(parent.context), parent, false
) )
) )
@ -35,6 +35,11 @@ class SplitPdfResultAdapter(
.transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context))) .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context)))
.into(holder.binding.image) .into(holder.binding.image)
} }
if (item.isPassword) {
holder.binding.lockLayout.visibility = View.VISIBLE
} else {
holder.binding.lockLayout.visibility = View.GONE
}
holder.binding.nameTv.text = File(item.filePath).name holder.binding.nameTv.text = File(item.filePath).name
holder.binding.pathTv.text = holder.binding.pathTv.text =
holder.binding.root.context.getString(R.string.path_details, item.filePath) holder.binding.root.context.getString(R.string.path_details, item.filePath)

View File

@ -1,6 +1,7 @@
package com.all.pdfreader.pro.app.ui.adapter package com.all.pdfreader.pro.app.ui.adapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -24,7 +25,7 @@ class SearchPdfAdapter(
private var showMoreButton: Boolean = true, // 是否显示更多按钮 private var showMoreButton: Boolean = true, // 是否显示更多按钮
private var showCheckButton: Boolean = false // 是否显示选择框按钮 private var showCheckButton: Boolean = false // 是否显示选择框按钮
) : ListAdapter<PdfDocumentEntity, SearchPdfAdapter.PdfViewHolder>(PdfDiffCallback()) { ) : ListAdapter<PdfDocumentEntity, SearchPdfAdapter.PdfViewHolder>(PdfDiffCallback()) {
private var searchKeyword: String? = null // 保存当前搜索关键字
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) : inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
@ -54,12 +55,13 @@ class SearchPdfAdapter(
holder.binding.checkBtn.visibility = if (showCheckButton) View.VISIBLE else View.GONE holder.binding.checkBtn.visibility = if (showCheckButton) View.VISIBLE else View.GONE
holder.binding.moreBtn.visibility = if (showMoreButton) View.VISIBLE else View.GONE holder.binding.moreBtn.visibility = if (showMoreButton) View.VISIBLE else View.GONE
holder.binding.deleteBtn.visibility = View.GONE
// 文件名高亮,如果 highlightKeyword 为 null 或空字符串,就显示普通文本 val keyword = highlightKeyword ?: searchKeyword
holder.binding.tvFileName.text = if (highlightKeyword.isNullOrBlank()) { holder.binding.tvFileName.text = if (keyword.isNullOrBlank()) {
item.fileName item.fileName
} else { } else {
item.fileName.toHighlightedSpannable(highlightKeyword, holder.binding.root.context.getColor(R.color.icon_sel_on_color)) item.fileName.toHighlightedSpannable(keyword, holder.binding.root.context.getColor(R.color.icon_sel_on_color))
} }
holder.binding.tvFileSize.text = item.fileSize.toFormatFileSize() holder.binding.tvFileSize.text = item.fileSize.toFormatFileSize()
@ -85,14 +87,17 @@ class SearchPdfAdapter(
holder.binding.moreBtn.setOnClickListener { onMoreClick(item) } holder.binding.moreBtn.setOnClickListener { onMoreClick(item) }
} }
fun updateData(newList: List<PdfDocumentEntity>) { fun updateData(newList: List<PdfDocumentEntity>, onComplete: (() -> Unit)? = null) {
submitList(newList.toList()) submitList(newList.toList()) {
onComplete?.invoke()
}
} }
/** /**
* 搜索场景使用只刷新高亮不刷新整个列表 * 搜索场景使用只刷新高亮不刷新整个列表
*/ */
fun highlightItems(keyword: String) { fun highlightItems(keyword: String) {
searchKeyword = keyword
for (i in 0 until itemCount) { for (i in 0 until itemCount) {
notifyItemChanged(i, keyword) notifyItemChanged(i, keyword)
} }

View File

@ -31,7 +31,7 @@ class FileDetailsDialogFragment() : BottomSheetDialogFragment() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
dialog?.window?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) dialog?.window?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
?.setBackgroundResource(R.drawable.dr_rounded_corner_12_bg_white) ?.setBackgroundResource(R.drawable.dr_rc_top_12_bg_white)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -197,7 +197,9 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
if (pdfDocument.isPassword) { if (pdfDocument.isPassword) {
PdfRemovePasswordDialog().show(parentFragmentManager, "PdfRemovePasswordDialog") PdfRemovePasswordDialog().show(parentFragmentManager, "PdfRemovePasswordDialog")
} else { } else {
PdfSetPasswordDialog().show(parentFragmentManager, "PdfSetPasswordDialog") PdfSetPasswordDialog(onOkClick = { password ->
viewModel.setPassword(pdfDocument.filePath, password)
}).show(parentFragmentManager, "PdfSetPasswordDialog")
} }
dismiss() dismiss()
} }

View File

@ -7,24 +7,17 @@ import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogPdfSetPasswordBinding import com.all.pdfreader.pro.app.databinding.DialogPdfSetPasswordBinding
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.all.pdfreader.pro.app.util.ToastUtils import com.all.pdfreader.pro.app.util.ToastUtils
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import kotlin.getValue
class PdfSetPasswordDialog() : DialogFragment( class PdfSetPasswordDialog(
private val onOkClick: (password: String) -> Unit = {}
) { ) : DialogFragment() {
private lateinit var binding: DialogPdfSetPasswordBinding private lateinit var binding: DialogPdfSetPasswordBinding
private val viewModel: PdfViewModel by activityViewModels()
private lateinit var pdfDocument: PdfDocumentEntity
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@ -48,15 +41,9 @@ class PdfSetPasswordDialog() : DialogFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.pdfDocument.value?.let {
pdfDocument = it
binding.etPassword.showKeyboard() binding.etPassword.showKeyboard()
setupListeners() setupListeners()
setupTextWatchers() setupTextWatchers()
} ?: run {
showToast(getString(R.string.file_not))
dismiss()
}
} }
private fun setupListeners() { private fun setupListeners() {
@ -68,7 +55,7 @@ class PdfSetPasswordDialog() : DialogFragment(
val password = binding.etPassword.text.toString() val password = binding.etPassword.text.toString()
val confirmPassword = binding.etConfirmPassword.text.toString() val confirmPassword = binding.etConfirmPassword.text.toString()
if (validatePassword(password, confirmPassword)) { if (validatePassword(password, confirmPassword)) {
viewModel.setPassword(pdfDocument.filePath, password) onOkClick(password)
dismiss() dismiss()
} }
} }

View File

@ -33,5 +33,9 @@ class ToolsFrag : Fragment() {
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.SPLIT) val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.SPLIT)
startActivity(intent) startActivity(intent)
} }
binding.lockBtn.setOnClickListener {
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.LOCK)
startActivity(intent)
}
} }
} }

View File

@ -1,23 +1,27 @@
package com.all.pdfreader.pro.app.util package com.all.pdfreader.pro.app.util
import android.graphics.pdf.PdfDocument
import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.encryption.AccessPermission import com.tom_roush.pdfbox.pdmodel.encryption.AccessPermission
import com.tom_roush.pdfbox.pdmodel.encryption.StandardProtectionPolicy import com.tom_roush.pdfbox.pdmodel.encryption.StandardProtectionPolicy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
object PdfSecurityUtils { object PdfSecurityUtils {
fun setPasswordToPdf( fun setPasswordToPdf(
inputFilePath: String, inputFilePath: String, userPassword: String, ownerPassword: String
userPassword: String,
ownerPassword: String
): Boolean { ): Boolean {
return try { return try {
PDDocument.load(File(inputFilePath)).use { document -> PDDocument.load(File(inputFilePath)).use { document ->
val accessPermission = AccessPermission() val accessPermission = AccessPermission()
val protectionPolicy = val protectionPolicy =
StandardProtectionPolicy(ownerPassword, userPassword, accessPermission) StandardProtectionPolicy(ownerPassword, userPassword, accessPermission)
protectionPolicy.encryptionKeyLength = 256 protectionPolicy.encryptionKeyLength = 128
protectionPolicy.permissions = accessPermission protectionPolicy.permissions = accessPermission
document.protect(protectionPolicy) document.protect(protectionPolicy)
document.save(inputFilePath) document.save(inputFilePath)
@ -29,6 +33,44 @@ object PdfSecurityUtils {
} }
} }
suspend fun setPasswordToPdfWithProgress(
inputFilePath: String,
userPassword: String,
ownerPassword: String,
onProgress: (Int) -> Unit
): Boolean = withContext(Dispatchers.IO) {
var success = false
// 假进度变量
var progress = 0
val job = launch {
while (isActive && progress < 98) {
delay(50)
// 专业曲线:前 80%慢,后 20%快
progress += when {
progress < 80 -> 1 // 前 80% 每次 +1
progress < 90 -> 2 // 80~90% 每次 +2
else -> 3 // 90~98% 每次 +3
}
if (progress > 98) progress = 98
withContext(Dispatchers.Main) {
onProgress(progress)
}
}
}
success = setPasswordToPdf(inputFilePath, userPassword, ownerPassword)
// 停止假进度
job.cancel()
// 最终完成直接 100%
withContext(Dispatchers.Main) {
onProgress(100)
}
success
}
fun removePasswordFromPdf(inputFilePath: String, password: String): Boolean { fun removePasswordFromPdf(inputFilePath: String, password: String): Boolean {
return try { return try {
PDDocument.load(File(inputFilePath), password).use { document -> PDDocument.load(File(inputFilePath), password).use { document ->

View File

@ -109,6 +109,7 @@
android:textSize="20sp" /> android:textSize="20sp" />
<TextView <TextView
android:id="@+id/congratulationsDesc"
style="@style/TextViewFont_PopMedium" style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -134,7 +135,7 @@
android:layout_height="150dp" android:layout_height="150dp"
android:layout_margin="16dp" android:layout_margin="16dp"
tools:itemCount="3" tools:itemCount="3"
tools:listitem="@layout/adapter_split_selected_result_item" /> tools:listitem="@layout/adapter_selected_result_item" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -49,16 +49,6 @@
android:layout_margin="0.5dp" /> android:layout_margin="0.5dp" />
</RelativeLayout> </RelativeLayout>
<ImageView
android:id="@+id/collectState"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:background="@drawable/dr_collect_state_bg"
android:padding="2dp"
android:src="@drawable/collected_white" />
<LinearLayout <LinearLayout
android:id="@+id/lock_layout" android:id="@+id/lock_layout"
android:layout_width="56dp" android:layout_width="56dp"
@ -73,6 +63,15 @@
android:src="@drawable/lock" /> android:src="@drawable/lock" />
</LinearLayout> </LinearLayout>
<ImageView
android:id="@+id/collectState"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:background="@drawable/dr_collect_state_bg"
android:padding="2dp"
android:src="@drawable/collected_white" />
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout

View File

@ -37,6 +37,21 @@
android:layout_width="56dp" android:layout_width="56dp"
android:layout_height="56dp" /> android:layout_height="56dp" />
<LinearLayout
android:id="@+id/lock_layout"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/dr_item_lock_bg"
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/lock" />
</LinearLayout>
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout

View File

@ -22,4 +22,17 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/split_pdf" /> android:text="@string/split_pdf" />
<Button
android:id="@+id/lockBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lock_pdf" />
<Button
android:id="@+id/unLockBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unlock_pdf" />
</LinearLayout> </LinearLayout>

View File

@ -29,7 +29,9 @@
<string name="pd_content_notice">Unable to access PDF file, please grant storage permission in settings</string> <string name="pd_content_notice">Unable to access PDF file, please grant storage permission in settings</string>
<string name="file_not">File does not exist</string> <string name="file_not">File does not exist</string>
<string name="lock">Lock</string> <string name="lock">Lock</string>
<string name="lock_pdf">Lock PDF</string>
<string name="unlock">Unlock</string> <string name="unlock">Unlock</string>
<string name="unlock_pdf">Unlock PDF</string>
<string name="eye_protect">Eye Protect</string> <string name="eye_protect">Eye Protect</string>
<string name="bookmarks">Bookmarks</string> <string name="bookmarks">Bookmarks</string>
<string name="no_bookmarks_page">No Bookmarks Page</string> <string name="no_bookmarks_page">No Bookmarks Page</string>