添加输入密码查询pdf

This commit is contained in:
ocean 2025-09-08 15:57:47 +08:00
parent 86750a4429
commit 687ac17419
12 changed files with 324 additions and 150 deletions

View File

@ -3,7 +3,6 @@ package com.all.pdfreader.pro.app.room.entity
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordDialog
import kotlinx.parcelize.Parcelize
@Parcelize

View File

@ -42,7 +42,7 @@ class PdfRepository private constructor(context: Context) {
pdfDao.searchDocuments(query)
suspend fun updateFavoriteStatus(fileHash: String, isFavorite: Boolean) {
val document = pdfDao.getByHash(fileHash)?.copy(
val document = pdfDao.getByPath(fileHash)?.copy(
isFavorite = isFavorite,
addedToFavoriteTime = if (isFavorite) System.currentTimeMillis() else null
)
@ -50,7 +50,7 @@ class PdfRepository private constructor(context: Context) {
}
suspend fun updateReadingProgress(fileHash: String, page: Int, progress: Float) {
val document = pdfDao.getByHash(fileHash)?.copy(
val document = pdfDao.getByPath(fileHash)?.copy(
lastOpenedTime = System.currentTimeMillis(),
lastReadPage = page,
readingProgress = progress
@ -59,14 +59,14 @@ class PdfRepository private constructor(context: Context) {
}
suspend fun updatePasswordStatus(fileHash: String, isPassword: Boolean) {
val document = pdfDao.getByHash(fileHash)?.copy(
val document = pdfDao.getByPath(fileHash)?.copy(
isPassword = isPassword
)
document?.let { pdfDao.update(it) }
}
suspend fun updatePassword(fileHash: String, password: String?) {
val document = pdfDao.getByHash(fileHash)?.copy(
val document = pdfDao.getByPath(fileHash)?.copy(
password = password
)
document?.let { pdfDao.update(it) }
@ -113,7 +113,7 @@ class PdfRepository private constructor(context: Context) {
// 组合查询
suspend fun getPdfWithDetails(pdfHash: String): Flow<PdfDetails> {
return combine(
pdfDao.getByHash(pdfHash)?.let { kotlinx.coroutines.flow.flowOf(it) }
pdfDao.getByPath(pdfHash)?.let { kotlinx.coroutines.flow.flowOf(it) }
?: kotlinx.coroutines.flow.flowOf(null),
bookmarkDao.getBookmarksByPdf(pdfHash),
noteDao.getNotesByPdf(pdfHash)

View File

@ -8,6 +8,7 @@ import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfViewBinding
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment
import com.all.pdfreader.pro.app.ui.view.CustomScrollHandle
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import com.github.barteksc.pdfviewer.listener.OnErrorListener
@ -63,32 +64,13 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
// 使用传递的文件路径加载PDF
val file = File(pdfDocument.filePath)
if (file.exists()) {
val pdfView = binding.pdfview
// 如果有密码,先尝试使用密码加载
if (!pdfDocument.password.isNullOrEmpty()) {
try {
pdfView.fromFile(file)
.password(pdfDocument.password) // 使用密码加载
.defaultPage(pdfDocument.lastReadPage)
.enableDoubletap(true)
.onLoad(this)
.enableAnnotationRendering(true)
.onError(this)
.onPageChange(this)
.scrollHandle(CustomScrollHandle(this))
.load()
return
} catch (e: Exception) {
logDebug("Password protected PDF failed: ${e.message}")
// 密码错误,显示密码输入对话框
//需要密码展示对话框。
if (pdfDocument.isPassword) {
showPasswordDialog(file)
return
}
}
// 无密码PDF正常加载
pdfView.fromFile(file)
binding.pdfview.fromFile(file)
.defaultPage(pdfDocument.lastReadPage) // 从上次阅读页码开始
.enableDoubletap(true)// 是否允许双击缩放
.onLoad(this)
@ -113,22 +95,13 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
logDebug("PDF loading error: ${t?.message}")
t?.let {
val errorMessage = it.message ?: "未知错误"
// 检查是否是密码相关的错误
if (errorMessage.contains("Password") || errorMessage.contains("password")) {
// 如果当前没有设置密码,显示密码输入对话框
if (pdfDocument.password.isNullOrEmpty()) {
val file = File(pdfDocument.filePath)
showPasswordDialog(file)
} else {
// 密码错误,显示密码输入对话框
showToast("PDF密码错误")
val file = File(pdfDocument.filePath)
showPasswordDialog(file)
}
} else {
// 其他错误
showToast("PDF加载失败: $errorMessage")
showToast(getString(R.string.pdf_loading_failed))
finish()
}
}
@ -156,57 +129,23 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
}
private fun showPasswordDialog(file: File) {
val builder = android.app.AlertDialog.Builder(this)
builder.setTitle("PDF密码保护")
builder.setMessage("请输入PDF文件密码")
val input = android.widget.EditText(this)
input.inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
builder.setView(input)
builder.setPositiveButton("确定") { dialog, _ ->
val password = input.text.toString()
if (password.isNotEmpty()) {
// 尝试使用输入的密码重新加载PDF
PdfPasswordProtectionDialogFragment(file, onOkClick = { password ->
tryLoadPdfWithPassword(file, password)
} else {
showToast("密码不能为空")
}
dialog.dismiss()
}
builder.setNegativeButton("取消") { dialog, _ ->
dialog.cancel()
}, onCancelClick = {
finish()
}
builder.setOnCancelListener {
finish()
}
builder.show()
}).show(supportFragmentManager, TAG)
}
private fun tryLoadPdfWithPassword(file: File, password: String) {
try {
binding.pdfview.fromFile(file)
.password(password) // 使用输入的密码
.defaultPage(pdfDocument.lastReadPage)
.enableDoubletap(true)
.defaultPage(pdfDocument.lastReadPage) // 从上次阅读页码开始
.enableDoubletap(true)// 是否允许双击缩放
.onLoad(this)
.enableAnnotationRendering(true)
.onError { error ->
logDebug("Password still incorrect: ${error?.message}")
showToast("密码错误,请重试")
showPasswordDialog(file) // 重新显示密码对话框
}
.enableAnnotationRendering(true) // 是否渲染注释(如评论、颜色、表单等)
.onError(this)
.onPageChange(this)
.scrollHandle(CustomScrollHandle(this))
.load()
} catch (e: Exception) {
logDebug("Failed to load PDF with provided password: ${e.message}")
showToast("密码错误,请重试")
showPasswordDialog(file) // 重新显示密码对话框
}
}
}

View File

@ -0,0 +1,82 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.fragment.app.DialogFragment
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogPdfPasswordProtectionBinding
import com.all.pdfreader.pro.app.databinding.DialogPermissionBinding
import com.all.pdfreader.pro.app.databinding.DialogSortBinding
import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.model.SortDirection
import com.all.pdfreader.pro.app.model.SortField
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.sp.AppStore
import com.all.pdfreader.pro.app.ui.act.MainActivity.SortableFragment
import com.all.pdfreader.pro.app.ui.view.CustomPasswordTransformation
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import androidx.core.graphics.drawable.toDrawable
import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.all.pdfreader.pro.app.util.FileUtils.isPdfPasswordCorrect
import java.io.File
class PdfPasswordProtectionDialogFragment(
private val file: File,
private val onOkClick: (String) -> Unit,
private val onCancelClick: () -> Unit
) : DialogFragment() {
private lateinit var binding: DialogPdfPasswordProtectionBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = DialogPdfPasswordProtectionBinding.inflate(layoutInflater)
return binding.root
}
override fun onStart() {
super.onStart()
isCancelable = false
dialog?.window?.apply {
// 去掉系统默认的背景 padding
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
// 设置宽度为全屏减去 16dp
val margin = resources.getDimensionPixelSize(R.dimen.dialog_margin) // 16dp
val width = resources.displayMetrics.widthPixels - margin * 2
setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.etPassword.showKeyboard()
binding.tvCancel.setOnClickListener {
onCancelClick()
dismiss()
}
binding.tvConfirm.setOnClickListener {
val password = binding.etPassword.text.toString()
if(password.isEmpty()){
binding.tilPassword.error = getString(R.string.password_not_empty)
return@setOnClickListener
}
if(isPdfPasswordCorrect(requireActivity(),file,password)){
onOkClick(password)
dismiss()
}else{
binding.tilPassword.error = getString(R.string.incorrect_password)
}
}
}
}

View File

@ -1,6 +1,5 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
@ -9,11 +8,11 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.all.pdfreader.pro.app.databinding.DialogPdfPasswordBinding
import com.all.pdfreader.pro.app.databinding.DialogPdfSetPasswordBinding
class PdfPasswordDialog : DialogFragment() {
class PdfSetPasswordDialog : DialogFragment() {
private var _binding: DialogPdfPasswordBinding? = null
private var _binding: DialogPdfSetPasswordBinding? = null
private val binding get() = _binding!!
private var listener: PasswordDialogListener? = null
@ -23,13 +22,17 @@ class PdfPasswordDialog : DialogFragment() {
}
companion object {
fun newInstance(): PdfPasswordDialog {
return PdfPasswordDialog()
fun newInstance(): PdfSetPasswordDialog {
return PdfSetPasswordDialog()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = DialogPdfPasswordBinding.inflate(inflater, container, false)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = DialogPdfSetPasswordBinding.inflate(inflater, container, false)
return binding.root
}

View File

@ -0,0 +1,24 @@
package com.all.pdfreader.pro.app.ui.view
import android.text.method.PasswordTransformationMethod
import android.view.View
class CustomPasswordTransformation : PasswordTransformationMethod() {
override fun getTransformation(source: CharSequence, view: View): CharSequence {
return PasswordCharSequence(source)
}
private class PasswordCharSequence(private val mSource: CharSequence) : CharSequence {
override val length: Int
get() = mSource.length
override fun get(index: Int): Char {
// 最后一位显示原字符,其余显示●
return if (index == mSource.length - 1) mSource[index] else '●'
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return mSource.subSequence(startIndex, endIndex)
}
}
}

View File

@ -1,7 +1,11 @@
package com.all.pdfreader.pro.app.util
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
object AppUtils {
@ -42,4 +46,16 @@ object AppUtils {
return (this * context.resources.displayMetrics.density).toInt()
}
/**
* 软键盘弹出
*/
fun EditText.showKeyboard() {
requestFocus()
(context as? Activity)?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
postDelayed({
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}, 200)
}
}

View File

@ -8,6 +8,7 @@ import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import com.shockwave.pdfium.PdfiumCore
import java.io.File
import java.io.IOException
import java.io.InputStream
@ -345,6 +346,22 @@ object FileUtils {
}
}
/**
* 判断pdf密码是否正确
*/
fun isPdfPasswordCorrect(context: Context, file: File, password: String): Boolean {
return try {
val descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfiumCore = PdfiumCore(context)
val doc = pdfiumCore.newDocument(descriptor, password)
pdfiumCore.closeDocument(doc)
true // 没抛异常说明密码正确
} catch (e: Exception) {
false // 抛异常说明密码错误
}
}
fun getFileFromUri(context: Context, uri: Uri): File? {
// 先尝试通过 DATA 字段获取
val projection = arrayOf(MediaStore.Files.FileColumns.DATA)

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/dr_rounded_corner_12_bg_white"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="@font/poppins_semibold"
android:text="@string/password_protection"
android:textColor="@color/black"
android:textSize="20sp" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/password_protection_dialog_desc"
android:textColor="@color/black_80"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="@string/enter_password"
app:endIconMode="password_toggle"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:textSize="16sp"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_regular"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:fontFamily="@font/poppins_regular"
android:padding="12dp"
android:text="@string/cancel"
android:textColor="@color/black_80" />
<TextView
android:id="@+id/tvConfirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_medium"
android:padding="12dp"
android:text="@string/ok"
android:textColor="@color/black" />
</LinearLayout>
</LinearLayout>

View File

@ -3,43 +3,46 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/dr_rounded_corner_12_bg_white"
android:orientation="vertical"
android:padding="24dp"
android:background="@android:color/white">
android:padding="24dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置PDF密码"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_marginBottom="16dp" />
android:layout_marginBottom="16dp"
android:fontFamily="@font/poppins_semibold"
android:text="@string/set_pdf_password"
android:textColor="@color/black"
android:textSize="20sp" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="为PDF文件设置保护密码留空表示不设置密码"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="16dp" />
android:layout_marginBottom="16dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/set_pdf_password_dialog_desc"
android:textColor="@color/black_60"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入密码"
app:passwordToggleEnabled="true"
android:layout_marginBottom="8dp"
android:hint="@string/enter_password"
app:endIconMode="password_toggle"
android:layout_marginBottom="8dp">
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
android:fontFamily="@font/poppins_regular"
android:inputType="textPassword"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
@ -47,43 +50,45 @@
android:id="@+id/tilConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="确认密码"
app:passwordToggleEnabled="true"
android:layout_marginBottom="24dp"
android:hint="@string/confirm_password"
app:endIconMode="password_toggle"
android:layout_marginBottom="24dp">
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
android:fontFamily="@font/poppins_regular"
android:inputType="textPassword"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
android:gravity="end"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"
android:textColor="@color/black"
android:padding="12dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless" />
android:fontFamily="@font/poppins_regular"
android:padding="12dp"
android:text="@string/cancel"
android:textColor="@color/black_80" />
<TextView
android:id="@+id/tvConfirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确认"
android:textColor="@color/black"
android:fontFamily="@font/poppins_medium"
android:padding="12dp"
android:background="?attr/selectableItemBackgroundBorderless" />
android:text="@string/ok"
android:textColor="@color/black" />
</LinearLayout>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dialog_margin">16dp</dimen>
</resources>

View File

@ -28,4 +28,13 @@
<string name="unlock">Unlock</string>
<string name="eye_protect">Eye Protect</string>
<string name="bookmarks">Bookmarks</string>
<string name="set_pdf_password">Set PDF Password</string>
<string name="set_pdf_password_dialog_desc">Set a protection password for the PDF file,Leaving it blank means no password is set.</string>
<string name="password_protection">PDF Password Protection</string>
<string name="enter_password">Enter password</string>
<string name="confirm_password">Confirm password</string>
<string name="password_protection_dialog_desc">Please enter the PDF file password</string>
<string name="password_not_empty">Password cannot be empty</string>
<string name="incorrect_password">Incorrect password</string>
<string name="pdf_loading_failed">PDF loading failed</string>
</resources>