1.设置密码
2.复制文件 3.优化耗时操作进度条展示
This commit is contained in:
parent
5323ad1324
commit
aead309db7
@ -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 }
|
||||
}
|
||||
}
|
||||
@ -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<List<PdfDocumentEntity>>
|
||||
|
||||
|
||||
@Query("SELECT * FROM pdf_documents WHERE fileName LIKE '%' || :query || '%' OR metadataTitle LIKE '%' || :query || '%'")
|
||||
fun searchDocuments(query: String): Flow<List<PdfDocumentEntity>>
|
||||
|
||||
|
||||
@Query("SELECT * FROM pdf_documents ORDER BY lastOpenedTime DESC")
|
||||
fun getAllDocuments(): Flow<List<PdfDocumentEntity>>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -128,7 +128,6 @@ class RenameDialogFragment() : DialogFragment() {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private fun showToast(message: String) {
|
||||
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 // 返回解密失败的标志
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -230,6 +230,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/duplicateFileBtn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center_vertical"
|
||||
@ -251,17 +252,20 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/setPasswordBtn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/passwordIv"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/lock" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/passwordTv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
|
||||
28
app/src/main/res/layout/dialog_progress.xml
Normal file
28
app/src/main/res/layout/dialog_progress.xml
Normal 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>
|
||||
@ -49,7 +49,11 @@
|
||||
<string name="delete_file">Delete File</string>
|
||||
<string name="delete">Delete</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_successfully">Remove Password successfully</string>
|
||||
<string name="remove_password_failed">Remove Password failed</string>
|
||||
<string name="duplicate_file">Duplicate File</string>
|
||||
<string name="enter_name">Enter a name</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="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="duplicate_created_successfully">Duplicate file created successfully</string>
|
||||
<string name="duplicate_created_failed">Duplicate file created failed</string>
|
||||
<string name="processing">Processing…</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue
Block a user