1.设置密码

2.复制文件
3.优化耗时操作进度条展示
This commit is contained in:
ocean 2025-09-11 18:31:55 +08:00
parent 5323ad1324
commit aead309db7
17 changed files with 386 additions and 72 deletions

View File

@ -1,7 +1,15 @@
package com.all.pdfreader.pro.app.model package com.all.pdfreader.pro.app.model
import java.io.File
sealed class FileActionEvent { sealed class FileActionEvent {
data class Rename(val renameResult: RenameResult) : FileActionEvent() data class Rename(val renameResult: RenameResult) : FileActionEvent()
data class Delete(val deleteResult: DeleteResult) : FileActionEvent() data class Delete(val deleteResult: DeleteResult) : FileActionEvent()
data class Favorite(val isFavorite: Boolean) : FileActionEvent() data class Favorite(val isFavorite: Boolean) : FileActionEvent()
data class Duplicate(val file: File?) : FileActionEvent()
// 成功或失败结果,只在完成事件时有值
data class SetPassword(val status: Status, val success: Boolean? = null) : FileActionEvent() {
enum class Status { START, COMPLETE }
}
} }

View File

@ -9,6 +9,7 @@ interface PdfDocumentDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(document: PdfDocumentEntity) suspend fun insertOrUpdate(document: PdfDocumentEntity)
//@Update 不会响应flow //@Update 不会响应flow
@Update @Update
suspend fun update(document: PdfDocumentEntity) suspend fun update(document: PdfDocumentEntity)
@ -41,4 +42,6 @@ interface PdfDocumentDao {
@Query("UPDATE pdf_documents SET filePath = :newFilePath, fileName = :newName WHERE filePath = :oldFilePath") @Query("UPDATE pdf_documents SET filePath = :newFilePath, fileName = :newName WHERE filePath = :oldFilePath")
suspend fun updateFilePathAndFileName(oldFilePath: String, newFilePath: String, newName: String) suspend fun updateFilePathAndFileName(oldFilePath: String, newFilePath: String, newName: String)
@Query("UPDATE pdf_documents SET isPassword = :hasPassword WHERE filePath = :filePath")
suspend fun updateIsPassword(filePath: String, hasPassword: Boolean)
} }

View File

@ -43,6 +43,11 @@ class PdfRepository private constructor(context: Context) {
pdfDao.updateFilePathAndFileName(oldFilePath, newFilePath, newName) pdfDao.updateFilePathAndFileName(oldFilePath, newFilePath, newName)
} }
//语句更新可响应flow
suspend fun updateIsPassword(filePath: String, hasPassword: Boolean){
pdfDao.updateIsPassword(filePath, hasPassword)
}
suspend fun updateFavoriteStatus(filePath: String, isFavorite: Boolean) { suspend fun updateFavoriteStatus(filePath: String, isFavorite: Boolean) {
val document = pdfDao.getByPath(filePath)?.copy( val document = pdfDao.getByPath(filePath)?.copy(
isFavorite = isFavorite, isFavorite = isFavorite,
@ -60,6 +65,7 @@ class PdfRepository private constructor(context: Context) {
document?.let { pdfDao.update(it) } document?.let { pdfDao.update(it) }
} }
//复制更新不会更新flow
suspend fun updatePasswordStatus(filePath: String, isPassword: Boolean) { suspend fun updatePasswordStatus(filePath: String, isPassword: Boolean) {
val document = pdfDao.getByPath(filePath)?.copy( val document = pdfDao.getByPath(filePath)?.copy(
isPassword = isPassword isPassword = isPassword

View File

@ -12,6 +12,7 @@ import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityMainBinding import com.all.pdfreader.pro.app.databinding.ActivityMainBinding
import com.all.pdfreader.pro.app.model.FileActionEvent import com.all.pdfreader.pro.app.model.FileActionEvent
import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.ProgressDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment
import com.all.pdfreader.pro.app.ui.fragment.FavoriteFrag import com.all.pdfreader.pro.app.ui.fragment.FavoriteFrag
import com.all.pdfreader.pro.app.ui.fragment.HomeFrag import com.all.pdfreader.pro.app.ui.fragment.HomeFrag
@ -43,6 +44,8 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
private val viewModel by lazy { ViewModelProvider(this)[PdfViewModel::class.java] } private val viewModel by lazy { ViewModelProvider(this)[PdfViewModel::class.java] }
private var progressDialog: ProgressDialogFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
@ -93,6 +96,33 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
showToast(getString(R.string.removed_from_favorites)) showToast(getString(R.string.removed_from_favorites))
} }
} }
is FileActionEvent.Duplicate -> {
if (event.file != null) {
showToast(getString(R.string.duplicate_created_successfully))
} else {
showToast(getString(R.string.duplicate_created_failed))
}
}
is FileActionEvent.SetPassword -> {
when (event.status) {
FileActionEvent.SetPassword.Status.START -> {
progressDialog = ProgressDialogFragment()
progressDialog?.show(supportFragmentManager, "progressDialog")
}
FileActionEvent.SetPassword.Status.COMPLETE -> {
progressDialog?.dismiss()
progressDialog = null
if (event.success == true) {
showToast(getString(R.string.set_password_successfully))
} else {
showToast(getString(R.string.set_password_failed))
}
}
}
}
} }
} }
} }

View File

@ -74,6 +74,7 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
.into(binding.tvFileImg) .into(binding.tvFileImg)
} }
updateCollectUi(isFavorite) updateCollectUi(isFavorite)
updatePasswordUi(pdfDocument.isPassword)
} }
private fun setupOnClick() { private fun setupOnClick() {
@ -103,6 +104,18 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
printPdfFile(requireActivity(), Uri.fromFile(File(pdfDocument.filePath))) printPdfFile(requireActivity(), Uri.fromFile(File(pdfDocument.filePath)))
dismiss() dismiss()
} }
binding.duplicateFileBtn.setOnClickListener {
viewModel.duplicateFile(requireActivity(), pdfDocument.filePath)
dismiss()
}
binding.setPasswordBtn.setOnClickListener {
if (pdfDocument.isPassword) {
} else {
PdfSetPasswordDialog().show(parentFragmentManager, "PdfSetPasswordDialog")
}
dismiss()
}
} }
private fun updateCollectUi(b: Boolean) { private fun updateCollectUi(b: Boolean) {
@ -113,6 +126,16 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
} }
} }
private fun updatePasswordUi(b: Boolean) {
if (b) {
binding.passwordIv.setImageResource(R.drawable.unlock)
binding.passwordTv.text = getString(R.string.remove_password)
} else {
binding.passwordIv.setImageResource(R.drawable.lock)
binding.passwordTv.text = getString(R.string.set_password)
}
}
private fun showToast(message: String) { private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show() Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
} }

View File

@ -7,18 +7,23 @@ 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.viewmodel.PdfViewModel
import kotlin.getValue
class PdfSetPasswordDialog( class PdfSetPasswordDialog() : DialogFragment(
private val onCancelled: () -> Unit, private val onPasswordSet: (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?
@ -42,14 +47,19 @@ class PdfSetPasswordDialog(
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() {
binding.tvCancel.setOnClickListener { binding.tvCancel.setOnClickListener {
onCancelled()
dismiss() dismiss()
} }
@ -57,7 +67,7 @@ class PdfSetPasswordDialog(
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)) {
onPasswordSet(password) viewModel.setPassword(pdfDocument.filePath, password)
dismiss() dismiss()
} }
} }
@ -118,4 +128,7 @@ class PdfSetPasswordDialog(
return true return true
} }
private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
}
} }

View File

@ -0,0 +1,40 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogProgressBinding
class ProgressDialogFragment : DialogFragment() {
private lateinit var binding: DialogProgressBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = DialogProgressBinding.inflate(layoutInflater)
return binding.root
}
override fun onStart() {
super.onStart()
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)
isCancelable = false
}
}

View File

@ -128,7 +128,6 @@ class RenameDialogFragment() : DialogFragment() {
return true return true
} }
private fun showToast(message: String) { private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show() Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
} }

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.pdf.PdfRenderer import android.graphics.pdf.PdfRenderer
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -30,6 +31,7 @@ import java.io.FileOutputStream
import androidx.core.graphics.createBitmap import androidx.core.graphics.createBitmap
import androidx.print.PrintHelper import androidx.print.PrintHelper
import com.all.pdfreader.pro.app.ui.adapter.PrintPdfAdapter import com.all.pdfreader.pro.app.ui.adapter.PrintPdfAdapter
import com.shockwave.pdfium.PdfDocument
import com.shockwave.pdfium.PdfPasswordException import com.shockwave.pdfium.PdfPasswordException
import com.shockwave.pdfium.PdfiumCore import com.shockwave.pdfium.PdfiumCore
import com.tom_roush.pdfbox.pdmodel.common.PDPageLabelRange import com.tom_roush.pdfbox.pdmodel.common.PDPageLabelRange
@ -167,4 +169,52 @@ object AppUtils {
e3.printStackTrace() e3.printStackTrace()
} }
} }
fun generateFastThumbnail(
context: Context,
pdfFile: File,
maxWidth: Int = 200,
maxHeight: Int = 300
): String? {
return try {
val pdfiumCore = PdfiumCore(context)
val fd = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfDocument: PdfDocument = pdfiumCore.newDocument(fd)
// 打开第一页
pdfiumCore.openPage(pdfDocument, 0)
// 获取原始页宽高
val width = pdfiumCore.getPageWidthPoint(pdfDocument, 0)
val height = pdfiumCore.getPageHeightPoint(pdfDocument, 0)
// 计算缩略图尺寸
val scale = minOf(maxWidth.toFloat() / width, maxHeight.toFloat() / height)
val thumbWidth = (width * scale).toInt()
val thumbHeight = (height * scale).toInt()
val bitmap = createBitmap(thumbWidth, thumbHeight)
bitmap.eraseColor(Color.WHITE) // 白底
// 渲染第一页到 Bitmap
pdfiumCore.renderPageBitmap(pdfDocument, bitmap, 0, 0, 0, thumbWidth, thumbHeight)
// 保存到缓存
val cacheDir = File(context.cacheDir, "thumbnails")
if (!cacheDir.exists()) cacheDir.mkdirs()
val thumbFile = File(cacheDir, pdfFile.nameWithoutExtension + ".jpg")
FileOutputStream(thumbFile).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
}
pdfiumCore.closeDocument(pdfDocument)
fd.close()
thumbFile.absolutePath
} catch (e: Exception) {
e.printStackTrace()
null
}
}
} }

View File

@ -529,4 +529,29 @@ object FileUtils {
} }
} }
fun duplicateFile(originalFile: File): File? {
if (!originalFile.exists()) return null
val parentDir = originalFile.parentFile ?: return null
val fileName = originalFile.nameWithoutExtension
val extension = originalFile.extension
// 自动生成不重复的新文件名
var newFile = File(parentDir, "$fileName (copy).$extension")
var index = 1
while (newFile.exists()) {
newFile = File(parentDir, "$fileName ($index).$extension")
index++
}
// 执行复制
originalFile.inputStream().use { input ->
newFile.outputStream().use { output ->
input.copyTo(output)
}
}
return newFile
}
} }

View File

@ -9,6 +9,7 @@ import androidx.core.graphics.createBitmap
import com.all.pdfreader.pro.app.PRApp import com.all.pdfreader.pro.app.PRApp
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.util.AppUtils.generateFastThumbnail
import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted
import com.shockwave.pdfium.PdfDocument import com.shockwave.pdfium.PdfDocument
import com.shockwave.pdfium.PdfiumCore import com.shockwave.pdfium.PdfiumCore
@ -273,51 +274,4 @@ class PdfScanner(
val lastScan = getLastScanTime() val lastScan = getLastScanTime()
return TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - lastScan) return TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - lastScan)
} }
private fun generateFastThumbnail(
context: Context,
pdfFile: File,
maxWidth: Int = 200,
maxHeight: Int = 300
): String? {
return try {
val pdfiumCore = PdfiumCore(context)
val fd = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfDocument: PdfDocument = pdfiumCore.newDocument(fd)
// 打开第一页
pdfiumCore.openPage(pdfDocument, 0)
// 获取原始页宽高
val width = pdfiumCore.getPageWidthPoint(pdfDocument, 0)
val height = pdfiumCore.getPageHeightPoint(pdfDocument, 0)
// 计算缩略图尺寸
val scale = minOf(maxWidth.toFloat() / width, maxHeight.toFloat() / height)
val thumbWidth = (width * scale).toInt()
val thumbHeight = (height * scale).toInt()
val bitmap = createBitmap(thumbWidth, thumbHeight)
bitmap.eraseColor(Color.WHITE) // 白底
// 渲染第一页到 Bitmap
pdfiumCore.renderPageBitmap(pdfDocument, bitmap, 0, 0, 0, thumbWidth, thumbHeight)
// 保存到缓存
val cacheDir = File(context.cacheDir, "thumbnails")
if (!cacheDir.exists()) cacheDir.mkdirs()
val thumbFile = File(cacheDir, pdfFile.nameWithoutExtension + ".jpg")
FileOutputStream(thumbFile).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
}
pdfiumCore.closeDocument(pdfDocument)
fd.close()
thumbFile.absolutePath
} catch (e: Exception) {
e.printStackTrace()
null
}
}
} }

View File

@ -0,0 +1,44 @@
package com.all.pdfreader.pro.app.util
import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.encryption.AccessPermission
import com.tom_roush.pdfbox.pdmodel.encryption.StandardProtectionPolicy
import java.io.File
object PdfSecurityUtils {
fun setPasswordToPdf(
inputFilePath: String,
userPassword: String,
ownerPassword: String
): Boolean {
return try {
PDDocument.load(File(inputFilePath)).use { document ->
val accessPermission = AccessPermission()
val protectionPolicy =
StandardProtectionPolicy(ownerPassword, userPassword, accessPermission)
protectionPolicy.encryptionKeyLength = 256
protectionPolicy.permissions = accessPermission
document.protect(protectionPolicy)
document.save(inputFilePath)
true
}
} catch (e: Exception) {
e.printStackTrace()
false
}
}
fun removePasswordFromPdf(inputFilePath: String, password: String): Boolean {
return try {
PDDocument.load(File(inputFilePath), password).use { document ->
document.isAllSecurityToBeRemoved = true // 移除所有安全设置,包括密码
document.save(inputFilePath)
}
true // 返回成功解密的标志
} catch (e: Exception) {
e.printStackTrace()
false // 返回解密失败的标志
}
}
}

View File

@ -1,5 +1,6 @@
package com.all.pdfreader.pro.app.viewmodel package com.all.pdfreader.pro.app.viewmodel
import android.content.Context
import android.util.Log import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -8,9 +9,12 @@ import androidx.lifecycle.viewModelScope
import com.all.pdfreader.pro.app.model.FileActionEvent import com.all.pdfreader.pro.app.model.FileActionEvent
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.util.AppUtils.generateFastThumbnail
import com.all.pdfreader.pro.app.util.FileDeleteUtil import com.all.pdfreader.pro.app.util.FileDeleteUtil
import com.all.pdfreader.pro.app.util.FileUtils import com.all.pdfreader.pro.app.util.FileUtils
import com.all.pdfreader.pro.app.util.LogUtil import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted
import com.all.pdfreader.pro.app.util.PdfMetadataExtractor
import com.all.pdfreader.pro.app.util.PdfSecurityUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -62,11 +66,9 @@ class PdfViewModel : ViewModel() {
pdfRepository.updateFilePathAndFileName(filePath, newFilePath, finalName) pdfRepository.updateFilePathAndFileName(filePath, newFilePath, finalName)
} }
withContext(Dispatchers.Main) {
_fileActionEvent.postValue(FileActionEvent.Rename(renameResult)) _fileActionEvent.postValue(FileActionEvent.Rename(renameResult))
} }
} }
}
fun deleteFile(filePath: String) { fun deleteFile(filePath: String) {
viewModelScope.launch { viewModelScope.launch {
@ -77,18 +79,76 @@ class PdfViewModel : ViewModel() {
Log.d("ocean", "文件已删除,清除数据库数据") Log.d("ocean", "文件已删除,清除数据库数据")
pdfRepository.deleteDocument(filePath) pdfRepository.deleteDocument(filePath)
} }
withContext(Dispatchers.Main) {
_fileActionEvent.postValue(FileActionEvent.Delete(deleteResult)) _fileActionEvent.postValue(FileActionEvent.Delete(deleteResult))
} }
} }
}
fun saveCollectState(filePath: String, isFavorite: Boolean) { fun saveCollectState(filePath: String, isFavorite: Boolean) {
viewModelScope.launch { viewModelScope.launch {
pdfRepository.updateFavoriteStatus(filePath, isFavorite) pdfRepository.updateFavoriteStatus(filePath, isFavorite)
withContext(Dispatchers.Main) {
_fileActionEvent.postValue(FileActionEvent.Favorite(isFavorite)) _fileActionEvent.postValue(FileActionEvent.Favorite(isFavorite))
} }
} }
fun duplicateFile(context: Context, filePath: String) {
viewModelScope.launch {
val file = FileUtils.duplicateFile(File(filePath))
Log.d("ocean", "duplicateFile->$file")
if (file != null) {
val isPassword = isPdfEncrypted(file)
val metadata = PdfMetadataExtractor.extractMetadata(file.absolutePath)
val document = PdfDocumentEntity(
filePath = file.absolutePath,
fileName = file.name,
fileSize = file.length(),
lastModified = file.lastModified(),
pageCount = metadata?.pageCount ?: 0,
thumbnailPath = null,
metadataTitle = metadata?.title,
metadataAuthor = metadata?.author,
metadataSubject = metadata?.subject,
metadataKeywords = metadata?.keywords,
metadataCreationDate = metadata?.creationDate?.time,
metadataModificationDate = metadata?.modificationDate?.time,
isPassword = isPassword
)
pdfRepository.insertOrUpdateDocument(document)
Log.d("ocean", "✅ 已保存到数据库: ${file.name}")
if (!isPassword) {//没有密码的情况下才去获取缩略图
launch(Dispatchers.IO) {
val newThumbnail = generateFastThumbnail(context, file)
if (newThumbnail != null && document.thumbnailPath != newThumbnail) {
pdfRepository.updateThumbnailPath(
document.filePath,
newThumbnail
)
Log.d("ocean", "✅ 缩略图已更新")
}
}
}
}
_fileActionEvent.postValue(FileActionEvent.Duplicate(file))
}
}
fun setPassword(filePath: String, password: String) {
viewModelScope.launch {
// 发送 START 事件UI 显示进度框
_fileActionEvent.postValue(FileActionEvent.SetPassword(FileActionEvent.SetPassword.Status.START))
val success = withContext(Dispatchers.IO) {
PdfSecurityUtils.setPasswordToPdf(filePath, password, password).also {
if (it) {
pdfRepository.updateIsPassword(filePath, true)
}
}
}
_fileActionEvent.postValue(
FileActionEvent.SetPassword(
FileActionEvent.SetPassword.Status.COMPLETE,
success
)
)
}
} }
} }

View File

@ -0,0 +1,20 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 背景轨道 -->
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<corners android:radius="6dp" />
<solid android:color="#E0E0E0"/> <!-- 背景色 -->
</shape>
</item>
<!-- 动画前景 -->
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="6dp"/>
<solid android:color="#3F51B5"/> <!-- 动画颜色 -->
</shape>
</clip>
</item>
</layer-list>

View File

@ -230,6 +230,7 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/duplicateFileBtn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -251,17 +252,20 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/setPasswordBtn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/passwordIv"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:src="@drawable/lock" /> android:src="@drawable/lock" />
<TextView <TextView
android:id="@+id/passwordTv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"

View File

@ -0,0 +1,28 @@
<?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="16dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_medium"
android:gravity="center"
android:text="@string/processing"
android:textColor="@color/black"
android:textSize="18sp" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:indeterminate="true"/>
</LinearLayout>

View File

@ -49,7 +49,11 @@
<string name="delete_file">Delete File</string> <string name="delete_file">Delete File</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="set_password">Set Password</string> <string name="set_password">Set Password</string>
<string name="set_password_successfully">Set Password successfully</string>
<string name="set_password_failed">Set Password failed</string>
<string name="remove_password">Remove Password</string> <string name="remove_password">Remove Password</string>
<string name="remove_password_successfully">Remove Password successfully</string>
<string name="remove_password_failed">Remove Password failed</string>
<string name="duplicate_file">Duplicate File</string> <string name="duplicate_file">Duplicate File</string>
<string name="enter_name">Enter a name</string> <string name="enter_name">Enter a name</string>
<string name="name_not_empty">File name cannot be empty</string> <string name="name_not_empty">File name cannot be empty</string>
@ -88,4 +92,7 @@
<string name="pdf_cant_print_password_protected">Cannot print a password protected PDF file</string> <string name="pdf_cant_print_password_protected">Cannot print a password protected PDF file</string>
<string name="cannot_print_malformed_pdf">Cannot print a malformed PDF file</string> <string name="cannot_print_malformed_pdf">Cannot print a malformed PDF file</string>
<string name="pdf_cannot_print_error">Cannot print PDF file, Unknown error has occurred</string> <string name="pdf_cannot_print_error">Cannot print PDF file, Unknown error has occurred</string>
<string name="duplicate_created_successfully">Duplicate file created successfully</string>
<string name="duplicate_created_failed">Duplicate file created failed</string>
<string name="processing">Processing…</string>
</resources> </resources>