diff --git a/app/src/main/java/com/all/pdfreader/pro/app/model/FileActionEvent.kt b/app/src/main/java/com/all/pdfreader/pro/app/model/FileActionEvent.kt index 9f32b10..17b5377 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/model/FileActionEvent.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/model/FileActionEvent.kt @@ -1,7 +1,15 @@ package com.all.pdfreader.pro.app.model +import java.io.File + sealed class FileActionEvent { data class Rename(val renameResult: RenameResult) : FileActionEvent() data class Delete(val deleteResult: DeleteResult) : 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 } + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/PdfDocumentDao.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/PdfDocumentDao.kt index 531a524..c416d77 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/PdfDocumentDao.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/PdfDocumentDao.kt @@ -6,25 +6,26 @@ import kotlinx.coroutines.flow.Flow @Dao interface PdfDocumentDao { - + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertOrUpdate(document: PdfDocumentEntity) + //@Update 不会响应flow @Update suspend fun update(document: PdfDocumentEntity) - + @Query("SELECT * FROM pdf_documents WHERE fileHash = :fileHash") suspend fun getByHash(fileHash: String): PdfDocumentEntity? - + @Query("SELECT * FROM pdf_documents WHERE filePath = :filePath") suspend fun getByPath(filePath: String): PdfDocumentEntity? - + @Query("SELECT * FROM pdf_documents WHERE isFavorite = 1 ORDER BY addedToFavoriteTime DESC") fun getFavoriteDocuments(): Flow> - + @Query("SELECT * FROM pdf_documents WHERE fileName LIKE '%' || :query || '%' OR metadataTitle LIKE '%' || :query || '%'") fun searchDocuments(query: String): Flow> - + @Query("SELECT * FROM pdf_documents ORDER BY lastOpenedTime DESC") fun getAllDocuments(): Flow> @@ -33,7 +34,7 @@ interface PdfDocumentDao { @Delete suspend fun delete(document: PdfDocumentEntity) - + @Query("DELETE FROM pdf_documents WHERE filePath = :filePath") suspend fun deleteByPath(filePath: String) @@ -41,4 +42,6 @@ interface PdfDocumentDao { @Query("UPDATE pdf_documents SET filePath = :newFilePath, fileName = :newName WHERE filePath = :oldFilePath") 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) } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt index 53edc6a..1ba32f0 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt @@ -43,6 +43,11 @@ class PdfRepository private constructor(context: Context) { pdfDao.updateFilePathAndFileName(oldFilePath, newFilePath, newName) } + //语句更新可响应flow + suspend fun updateIsPassword(filePath: String, hasPassword: Boolean){ + pdfDao.updateIsPassword(filePath, hasPassword) + } + suspend fun updateFavoriteStatus(filePath: String, isFavorite: Boolean) { val document = pdfDao.getByPath(filePath)?.copy( isFavorite = isFavorite, @@ -60,6 +65,7 @@ class PdfRepository private constructor(context: Context) { document?.let { pdfDao.update(it) } } + //复制更新不会更新flow suspend fun updatePasswordStatus(filePath: String, isPassword: Boolean) { val document = pdfDao.getByPath(filePath)?.copy( isPassword = isPassword diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt index 954aee0..05a7c55 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt @@ -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.model.FileActionEvent 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.fragment.FavoriteFrag 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 var progressDialog: ProgressDialogFragment? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) @@ -93,6 +96,33 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback 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)) + } + } + } + } } } } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt index 03bdafc..7b8e091 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt @@ -74,6 +74,7 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() .into(binding.tvFileImg) } updateCollectUi(isFavorite) + updatePasswordUi(pdfDocument.isPassword) } private fun setupOnClick() { @@ -103,6 +104,18 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() printPdfFile(requireActivity(), Uri.fromFile(File(pdfDocument.filePath))) 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) { @@ -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) { Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfSetPasswordDialog.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfSetPasswordDialog.kt index cd051f6..a67acff 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfSetPasswordDialog.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfSetPasswordDialog.kt @@ -7,18 +7,23 @@ import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.core.graphics.drawable.toDrawable import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels import com.all.pdfreader.pro.app.R 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.viewmodel.PdfViewModel +import kotlin.getValue -class PdfSetPasswordDialog( - private val onCancelled: () -> Unit, private val onPasswordSet: (String) -> Unit -) : DialogFragment( +class PdfSetPasswordDialog() : DialogFragment( ) { private lateinit var binding: DialogPdfSetPasswordBinding + private val viewModel: PdfViewModel by activityViewModels() + private lateinit var pdfDocument: PdfDocumentEntity override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -42,14 +47,19 @@ class PdfSetPasswordDialog( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.etPassword.showKeyboard() - setupListeners() - setupTextWatchers() + viewModel.pdfDocument.value?.let { + pdfDocument = it + binding.etPassword.showKeyboard() + setupListeners() + setupTextWatchers() + } ?: run { + showToast(getString(R.string.file_not)) + dismiss() + } } private fun setupListeners() { binding.tvCancel.setOnClickListener { - onCancelled() dismiss() } @@ -57,7 +67,7 @@ class PdfSetPasswordDialog( val password = binding.etPassword.text.toString() val confirmPassword = binding.etConfirmPassword.text.toString() if (validatePassword(password, confirmPassword)) { - onPasswordSet(password) + viewModel.setPassword(pdfDocument.filePath, password) dismiss() } } @@ -118,4 +128,7 @@ class PdfSetPasswordDialog( return true } + private fun showToast(message: String) { + Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show() + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ProgressDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ProgressDialogFragment.kt new file mode 100644 index 0000000..bfd4644 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ProgressDialogFragment.kt @@ -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 + } +} diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt index 8bb3f1a..28be373 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt @@ -128,7 +128,6 @@ class RenameDialogFragment() : DialogFragment() { return true } - private fun showToast(message: String) { Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt index 1fe355c..f9a669d 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.Color import android.graphics.pdf.PdfRenderer import android.net.Uri import android.os.Bundle @@ -30,6 +31,7 @@ import java.io.FileOutputStream import androidx.core.graphics.createBitmap import androidx.print.PrintHelper import com.all.pdfreader.pro.app.ui.adapter.PrintPdfAdapter +import com.shockwave.pdfium.PdfDocument import com.shockwave.pdfium.PdfPasswordException import com.shockwave.pdfium.PdfiumCore import com.tom_roush.pdfbox.pdmodel.common.PDPageLabelRange @@ -167,4 +169,52 @@ object AppUtils { 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 + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt index 70240ea..8055f31 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/FileUtils.kt @@ -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 + } + } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt index fbc4a76..fa781d4 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfScanner.kt @@ -9,6 +9,7 @@ import androidx.core.graphics.createBitmap import com.all.pdfreader.pro.app.PRApp import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity 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.shockwave.pdfium.PdfDocument import com.shockwave.pdfium.PdfiumCore @@ -273,51 +274,4 @@ class PdfScanner( val lastScan = getLastScanTime() 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 - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfSecurityUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfSecurityUtils.kt new file mode 100644 index 0000000..9c22b6c --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfSecurityUtils.kt @@ -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 // 返回解密失败的标志 + } + } +} diff --git a/app/src/main/java/com/all/pdfreader/pro/app/viewmodel/PdfViewModel.kt b/app/src/main/java/com/all/pdfreader/pro/app/viewmodel/PdfViewModel.kt index d82dba9..e9fc96b 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/viewmodel/PdfViewModel.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/viewmodel/PdfViewModel.kt @@ -1,5 +1,6 @@ package com.all.pdfreader.pro.app.viewmodel +import android.content.Context import android.util.Log import androidx.lifecycle.LiveData 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.room.entity.PdfDocumentEntity 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.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.launch import kotlinx.coroutines.withContext @@ -62,9 +66,7 @@ class PdfViewModel : ViewModel() { pdfRepository.updateFilePathAndFileName(filePath, newFilePath, finalName) } - withContext(Dispatchers.Main) { - _fileActionEvent.postValue(FileActionEvent.Rename(renameResult)) - } + _fileActionEvent.postValue(FileActionEvent.Rename(renameResult)) } } @@ -77,18 +79,76 @@ class PdfViewModel : ViewModel() { Log.d("ocean", "文件已删除,清除数据库数据") pdfRepository.deleteDocument(filePath) } - withContext(Dispatchers.Main) { - _fileActionEvent.postValue(FileActionEvent.Delete(deleteResult)) - } + _fileActionEvent.postValue(FileActionEvent.Delete(deleteResult)) } } fun saveCollectState(filePath: String, isFavorite: Boolean) { viewModelScope.launch { 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 + ) + ) } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/indeterminate_progress_drawable.xml b/app/src/main/res/drawable/indeterminate_progress_drawable.xml new file mode 100644 index 0000000..d417f49 --- /dev/null +++ b/app/src/main/res/drawable/indeterminate_progress_drawable.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_list_more.xml b/app/src/main/res/layout/dialog_list_more.xml index 3f5de4d..5bd6b70 100644 --- a/app/src/main/res/layout/dialog_list_more.xml +++ b/app/src/main/res/layout/dialog_list_more.xml @@ -230,6 +230,7 @@ android:orientation="vertical"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9ece7e3..014801e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,7 +49,11 @@ Delete File Delete Set Password + Set Password successfully + Set Password failed Remove Password + Remove Password successfully + Remove Password failed Duplicate File Enter a name File name cannot be empty @@ -88,4 +92,7 @@ Cannot print a password protected PDF file Cannot print a malformed PDF file Cannot print PDF file, Unknown error has occurred + Duplicate file created successfully + Duplicate file created failed + Processing… \ No newline at end of file