diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/BookmarkDao.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/BookmarkDao.kt index 4c07d34..e956641 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/BookmarkDao.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/BookmarkDao.kt @@ -19,18 +19,18 @@ interface BookmarkDao { @Query("SELECT * FROM bookmarks WHERE id = :bookmarkId") suspend fun getById(bookmarkId: Long): BookmarkEntity? - @Query("SELECT * FROM bookmarks WHERE pdfHash = :pdfHash ORDER BY pageNumber ASC, createTime ASC") - fun getBookmarksByPdf(pdfHash: String): Flow> + @Query("SELECT * FROM bookmarks WHERE filePath = :filePath ORDER BY pageNumber ASC, createTime ASC") + fun getBookmarksByPdf(filePath: String): Flow> - @Query("SELECT * FROM bookmarks WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber") - suspend fun getBookmarksByPage(pdfHash: String, pageNumber: Int): List + @Query("SELECT * FROM bookmarks WHERE filePath = :filePath AND pageNumber = :pageNumber") + suspend fun getBookmarksByPage(filePath: String, pageNumber: Int): List - @Query("SELECT COUNT(*) FROM bookmarks WHERE pdfHash = :pdfHash") - suspend fun getBookmarkCount(pdfHash: String): Int + @Query("SELECT COUNT(*) FROM bookmarks WHERE filePath = :filePath") + suspend fun getBookmarkCount(filePath: String): Int - @Query("DELETE FROM bookmarks WHERE pdfHash = :pdfHash") - suspend fun deleteAllByPdf(pdfHash: String) + @Query("DELETE FROM bookmarks WHERE filePath = :filePath") + suspend fun deleteAllByPdf(filePath: String) - @Query("DELETE FROM bookmarks WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber") - suspend fun deleteByPage(pdfHash: String, pageNumber: Int) + @Query("DELETE FROM bookmarks WHERE filePath = :filePath AND pageNumber = :pageNumber") + suspend fun deleteByPage(filePath: String, pageNumber: Int) } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/NoteDao.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/NoteDao.kt index 3333fd4..9dda2cc 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/NoteDao.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/NoteDao.kt @@ -19,21 +19,21 @@ interface NoteDao { @Query("SELECT * FROM notes WHERE id = :noteId") suspend fun getById(noteId: Long): NoteEntity? - @Query("SELECT * FROM notes WHERE pdfHash = :pdfHash ORDER BY pageNumber ASC, createTime ASC") - fun getNotesByPdf(pdfHash: String): Flow> + @Query("SELECT * FROM notes WHERE filePath = :filePath ORDER BY pageNumber ASC, createTime ASC") + fun getNotesByPdf(filePath: String): Flow> - @Query("SELECT * FROM notes WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber") - suspend fun getNotesByPage(pdfHash: String, pageNumber: Int): List + @Query("SELECT * FROM notes WHERE filePath = :filePath AND pageNumber = :pageNumber") + suspend fun getNotesByPage(filePath: String, pageNumber: Int): List - @Query("SELECT * FROM notes WHERE pdfHash = :pdfHash AND noteType = :noteType") - fun getNotesByType(pdfHash: String, noteType: String): Flow> + @Query("SELECT * FROM notes WHERE filePath = :filePath AND noteType = :noteType") + fun getNotesByType(filePath: String, noteType: String): Flow> - @Query("SELECT COUNT(*) FROM notes WHERE pdfHash = :pdfHash") - suspend fun getNoteCount(pdfHash: String): Int + @Query("SELECT COUNT(*) FROM notes WHERE filePath = :filePath") + suspend fun getNoteCount(filePath: String): Int - @Query("DELETE FROM notes WHERE pdfHash = :pdfHash") - suspend fun deleteAllByPdf(pdfHash: String) + @Query("DELETE FROM notes WHERE filePath = :filePath") + suspend fun deleteAllByPdf(filePath: String) - @Query("DELETE FROM notes WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber") - suspend fun deleteByPage(pdfHash: String, pageNumber: Int) + @Query("DELETE FROM notes WHERE filePath = :filePath AND pageNumber = :pageNumber") + suspend fun deleteByPage(filePath: String, pageNumber: Int) } \ 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 16f90c5..0a72945 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 @@ -34,6 +34,6 @@ interface PdfDocumentDao { @Delete suspend fun delete(document: PdfDocumentEntity) - @Query("DELETE FROM pdf_documents WHERE fileHash = :fileHash") - suspend fun deleteByHash(fileHash: String) + @Query("DELETE FROM pdf_documents WHERE filePath = :filePath") + suspend fun deleteByPath(filePath: String) } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/RecentReadDao.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/RecentReadDao.kt index 1c5900b..5023ed0 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/dao/RecentReadDao.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/dao/RecentReadDao.kt @@ -11,25 +11,25 @@ interface RecentReadDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertOrUpdate(recentRead: RecentReadEntity) - @Query("SELECT * FROM recently_read WHERE pdfHash = :pdfHash") - suspend fun getByPdfHash(pdfHash: String): RecentReadEntity? + @Query("SELECT * FROM recently_read WHERE filePath = :filePath") + suspend fun getByPdfHash(filePath: String): RecentReadEntity? @Query(""" SELECT pdf_documents.* FROM pdf_documents - INNER JOIN recently_read ON pdf_documents.fileHash = recently_read.pdfHash + INNER JOIN recently_read ON pdf_documents.filePath = recently_read.filePath ORDER BY recently_read.lastOpenedTime DESC """) fun getRecentReadDocuments(): Flow> - @Query("UPDATE recently_read SET lastOpenedTime = :time, openedCount = openedCount + 1 WHERE pdfHash = :pdfHash") - suspend fun updateOpenTime(pdfHash: String, time: Long = System.currentTimeMillis()) + @Query("UPDATE recently_read SET lastOpenedTime = :time, openedCount = openedCount + 1 WHERE filePath = :filePath") + suspend fun updateOpenTime(filePath: String, time: Long = System.currentTimeMillis()) - @Query("UPDATE recently_read SET totalReadTime = totalReadTime + :additionalTime WHERE pdfHash = :pdfHash") - suspend fun addReadTime(pdfHash: String, additionalTime: Long) + @Query("UPDATE recently_read SET totalReadTime = totalReadTime + :additionalTime WHERE filePath = :filePath") + suspend fun addReadTime(filePath: String, additionalTime: Long) - @Query("DELETE FROM recently_read WHERE pdfHash = :pdfHash") - suspend fun deleteByPdfHash(pdfHash: String) + @Query("DELETE FROM recently_read WHERE filePath = :filePath") + suspend fun deleteByPdfPath(filePath: String) @Query("DELETE FROM recently_read WHERE lastOpenedTime < :cutoffTime") suspend fun deleteOldRecents(cutoffTime: Long) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/BookmarkEntity.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/BookmarkEntity.kt index f3eddca..607821b 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/BookmarkEntity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/BookmarkEntity.kt @@ -9,17 +9,17 @@ import androidx.room.PrimaryKey tableName = "bookmarks", foreignKeys = [ForeignKey( entity = PdfDocumentEntity::class, - parentColumns = ["fileHash"], - childColumns = ["pdfHash"], + parentColumns = ["filePath"], + childColumns = ["filePath"], onDelete = ForeignKey.CASCADE )], - indices = [Index(value = ["pdfHash"])] + indices = [Index(value = ["filePath"])] ) data class BookmarkEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, - val pdfHash: String, // 关联PdfDocumentEntity的fileHash + val filePath: String, // 关联PdfDocumentEntity的fileHash val pageNumber: Int, // 页码(从0开始) val label: String, // 书签标签 val positionX: Float = 0f, // 页面内X位置 diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/NoteEntity.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/NoteEntity.kt index 77dfc4c..a55cbb6 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/NoteEntity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/NoteEntity.kt @@ -9,17 +9,17 @@ import androidx.room.PrimaryKey tableName = "notes", foreignKeys = [ForeignKey( entity = PdfDocumentEntity::class, - parentColumns = ["fileHash"], - childColumns = ["pdfHash"], + parentColumns = ["filePath"], + childColumns = ["filePath"], onDelete = ForeignKey.CASCADE )], - indices = [Index(value = ["pdfHash"])] + indices = [Index(value = ["filePath"])] ) data class NoteEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, - val pdfHash: String, // 关联PdfDocumentEntity的fileHash + val filePath: String, // 关联PdfDocumentEntity的fileHash val pageNumber: Int, // 页码(从0开始) val noteType: String, // 注释类型: HIGHLIGHT, TEXT_NOTE, DRAWING val content: String, // 注释内容(文本或序列化的绘制数据) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/PdfDocumentEntity.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/PdfDocumentEntity.kt index 7f81577..1103f8a 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/PdfDocumentEntity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/PdfDocumentEntity.kt @@ -9,9 +9,9 @@ import kotlinx.parcelize.Parcelize @Entity(tableName = "pdf_documents") data class PdfDocumentEntity( @PrimaryKey - val fileHash: String, // 文件内容哈希(MD5/SHA-1) - val filePath: String, // 当前文件路径 + + val fileHash: String? = null, // 文件内容哈希(MD5/SHA-1),扫描时不进行获取,需要做判定再单独获取 val fileName: String, // 文件名 val fileSize: Long, // 文件大小(字节) val lastModified: Long, // 文件最后修改时间 diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/RecentReadEntity.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/RecentReadEntity.kt index ebcc9b0..11cf30d 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/entity/RecentReadEntity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/entity/RecentReadEntity.kt @@ -9,17 +9,17 @@ import androidx.room.PrimaryKey tableName = "recently_read", foreignKeys = [ForeignKey( entity = PdfDocumentEntity::class, - parentColumns = ["fileHash"], - childColumns = ["pdfHash"], + parentColumns = ["filePath"], + childColumns = ["filePath"], onDelete = ForeignKey.CASCADE )], - indices = [Index(value = ["pdfHash"])] + indices = [Index(value = ["filePath"])] ) data class RecentReadEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, - val pdfHash: String, // 关联PdfDocumentEntity的fileHash + val filePath: String, // 关联PdfDocumentEntity的filePath val lastOpenedTime: Long, // 最后打开时间 val openedCount: Int = 1, // 打开次数 val totalReadTime: Long = 0, // 总阅读时长(毫秒) 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 3b027dc..461af88 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 @@ -41,16 +41,16 @@ class PdfRepository private constructor(context: Context) { fun searchDocuments(query: String): Flow> = pdfDao.searchDocuments(query) - suspend fun updateFavoriteStatus(fileHash: String, isFavorite: Boolean) { - val document = pdfDao.getByPath(fileHash)?.copy( + suspend fun updateFavoriteStatus(filePath: String, isFavorite: Boolean) { + val document = pdfDao.getByPath(filePath)?.copy( isFavorite = isFavorite, addedToFavoriteTime = if (isFavorite) System.currentTimeMillis() else null ) document?.let { pdfDao.update(it) } } - suspend fun updateReadingProgress(fileHash: String, page: Int, progress: Float) { - val document = pdfDao.getByPath(fileHash)?.copy( + suspend fun updateReadingProgress(filePath: String, page: Int, progress: Float) { + val document = pdfDao.getByPath(filePath)?.copy( lastOpenedTime = System.currentTimeMillis(), lastReadPage = page, readingProgress = progress @@ -58,29 +58,37 @@ class PdfRepository private constructor(context: Context) { document?.let { pdfDao.update(it) } } - suspend fun updatePasswordStatus(fileHash: String, isPassword: Boolean) { - val document = pdfDao.getByPath(fileHash)?.copy( + suspend fun updatePasswordStatus(filePath: String, isPassword: Boolean) { + val document = pdfDao.getByPath(filePath)?.copy( isPassword = isPassword ) document?.let { pdfDao.update(it) } } - suspend fun updatePassword(fileHash: String, password: String?) { - val document = pdfDao.getByPath(fileHash)?.copy( + suspend fun updatePassword(filePath: String, password: String?) { + val document = pdfDao.getByPath(filePath)?.copy( password = password ) document?.let { pdfDao.update(it) } } + suspend fun updateThumbnailPath(filePath: String, path: String) { + val document = pdfDao.getByPath(filePath)?.copy( + thumbnailPath = path, + ) + document?.let { pdfDao.update(it) } + } + + // 最近阅读相关操作 - suspend fun addToRecent(pdfHash: String, page: Int = 0) { - val existing = recentDao.getByPdfHash(pdfHash) + suspend fun addToRecent(filePath: String, page: Int = 0) { + val existing = recentDao.getByPdfHash(filePath) if (existing != null) { - recentDao.updateOpenTime(pdfHash) + recentDao.updateOpenTime(filePath) } else { recentDao.insertOrUpdate( RecentReadEntity( - pdfHash = pdfHash, + filePath = filePath, lastOpenedTime = System.currentTimeMillis() ) ) @@ -113,7 +121,7 @@ class PdfRepository private constructor(context: Context) { // 组合查询 suspend fun getPdfWithDetails(pdfHash: String): Flow { return combine( - pdfDao.getByPath(pdfHash)?.let { kotlinx.coroutines.flow.flowOf(it) } + pdfDao.getByHash(pdfHash)?.let { kotlinx.coroutines.flow.flowOf(it) } ?: kotlinx.coroutines.flow.flowOf(null), bookmarkDao.getBookmarksByPdf(pdfHash), noteDao.getNotesByPdf(pdfHash) @@ -144,11 +152,11 @@ class PdfRepository private constructor(context: Context) { } // 数据清理 - suspend fun deleteDocument(fileHash: String) { - pdfDao.deleteByHash(fileHash) - recentDao.deleteByPdfHash(fileHash) - bookmarkDao.deleteAllByPdf(fileHash) - noteDao.deleteAllByPdf(fileHash) + suspend fun deleteDocument(filePath: String) { + pdfDao.deleteByPath(filePath) + recentDao.deleteByPdfPath(filePath) + bookmarkDao.deleteAllByPdf(filePath) + noteDao.deleteAllByPdf(filePath) } companion object { 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 1a39111..bffa6f4 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 @@ -10,16 +10,12 @@ import com.all.pdfreader.pro.app.PDFReaderApplication import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.ActivityMainBinding import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment +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 import com.all.pdfreader.pro.app.ui.fragment.RecentlyFrag import com.all.pdfreader.pro.app.ui.fragment.ToolsFrag -import com.all.pdfreader.pro.app.model.SortConfig -import com.all.pdfreader.pro.app.model.SortField -import com.all.pdfreader.pro.app.model.SortDirection -import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation -import com.all.pdfreader.pro.app.util.FileChangeObserver import com.all.pdfreader.pro.app.util.PdfScanner import com.all.pdfreader.pro.app.util.StoragePermissionHelper import com.gyf.immersionbar.ImmersionBar diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt index 6e91d34..da46b26 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt @@ -123,7 +123,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList private fun saveReadingProgress() { lifecycleScope.launch { repository.updateReadingProgress( - pdfDocument.fileHash, pdfDocument.lastReadPage, pdfDocument.readingProgress + pdfDocument.filePath, pdfDocument.lastReadPage, pdfDocument.readingProgress ) } } 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 new file mode 100644 index 0000000..a4d5beb --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt @@ -0,0 +1,128 @@ +package com.all.pdfreader.pro.app.ui.dialog + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.all.pdfreader.pro.app.R +import com.all.pdfreader.pro.app.databinding.DialogListMoreBinding +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.room.repository.PdfRepository +import com.all.pdfreader.pro.app.sp.AppStore +import com.all.pdfreader.pro.app.ui.act.MainActivity.SortableFragment +import com.all.pdfreader.pro.app.util.AppUtils.dpToPx +import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation +import com.all.pdfreader.pro.app.util.FileUtils.toFormatFileSize +import com.all.pdfreader.pro.app.util.FileUtils.toSlashDate +import com.all.pdfreader.pro.app.viewmodel.PdfViewModel +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.coroutines.launch + +class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() { + + private lateinit var binding: DialogListMoreBinding + private val pdfRepository = PdfRepository.getInstance() + private val viewModel by lazy { ViewModelProvider(this)[PdfViewModel::class.java] } + private lateinit var pdfDocument: PdfDocumentEntity + private var isFavorite: Boolean = false + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + binding = DialogListMoreBinding.inflate(layoutInflater) + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + ?.setBackgroundResource(R.drawable.dr_rounded_corner_12_bg_white) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.pdfDocument.observe(this) { document -> + document?.let { + pdfDocument = it + isFavorite = pdfDocument.isFavorite + initUi() + setupOnClick() + + } ?: run { + showToast(getString(R.string.file_not)) + dismiss() + } + } + viewModel.getPDFDocument(filePath) + } + + private fun initUi() { + binding.tvFileName.text = pdfDocument.fileName + binding.tvFileSize.text = pdfDocument.fileSize.toFormatFileSize() + binding.tvFileDate.text = pdfDocument.lastModified.toSlashDate() + if (pdfDocument.isPassword) { + binding.lockLayout.visibility = View.VISIBLE + binding.tvFileImg.visibility = View.GONE + } else { + binding.lockLayout.visibility = View.GONE + binding.tvFileImg.visibility = View.VISIBLE + Glide.with(binding.root).load(pdfDocument.thumbnailPath) + .transform(CenterCrop(), RoundedCorners(8.dpToPx(binding.root.context))) + .into(binding.tvFileImg) + } + updateCollectUi(isFavorite) + } + + private fun setupOnClick() { + binding.collectBtn.setClickWithAnimation(duration = 250) { + isFavorite = !isFavorite + updateCollectUi(isFavorite) + saveCollectState(isFavorite) + } + binding.renameFileBtn.setOnClickListener { + RenameDialogFragment(pdfDocument.filePath, onOkClick = { + + }, onCancelClick = { + + }).show(parentFragmentManager, "ListMoreDialogFragment") + dismiss() + } + } + + private fun saveCollectState(b: Boolean) { + pdfDocument = pdfDocument.copy( + isFavorite = b + ) + lifecycleScope.launch { + pdfRepository.updateFavoriteStatus(pdfDocument.filePath, pdfDocument.isFavorite) + } + if (b) { + showToast(getString(R.string.added_to_favorites)) + } else { + showToast(getString(R.string.removed_from_favorites)) + } + } + + private fun updateCollectUi(b: Boolean) { + if (b) { + binding.collectIv.setImageResource(R.drawable.collected) + } else { + binding.collectIv.setImageResource(R.drawable.collect) + } + } + + 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/PdfPasswordProtectionDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfPasswordProtectionDialogFragment.kt index 8b87451..c644936 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfPasswordProtectionDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PdfPasswordProtectionDialogFragment.kt @@ -2,7 +2,6 @@ package com.all.pdfreader.pro.app.ui.dialog import android.graphics.Color import android.os.Bundle -import android.text.InputType import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -21,7 +20,6 @@ class PdfPasswordProtectionDialogFragment( ) : DialogFragment() { private lateinit var binding: DialogPdfPasswordProtectionBinding - var isPasswordVisible = false override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -67,23 +65,5 @@ class PdfPasswordProtectionDialogFragment( binding.tilPassword.error = getString(R.string.incorrect_password) } } - binding.showPasswordBtn.setOnClickListener { - isPasswordVisible = !isPasswordVisible - showOrHidePassword(isPasswordVisible) - } - } - - private fun showOrHidePassword(b: Boolean) { - if (b) { - binding.etPassword.inputType = - InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD - binding.showPasswordIv.setImageResource(R.drawable.show_password) - } else { - binding.etPassword.inputType = - InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - binding.showPasswordIv.setImageResource(R.drawable.hide_password) - } - // 保持光标在末尾 - binding.etPassword.setSelection(binding.etPassword.text?.length ?: 0) } } \ No newline at end of file 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 4082ce4..cd051f6 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 @@ -3,18 +3,15 @@ package com.all.pdfreader.pro.app.ui.dialog import android.graphics.Color import android.os.Bundle import android.text.Editable -import android.text.InputType import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView 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.DialogPdfSetPasswordBinding import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard -import com.google.android.material.textfield.TextInputEditText class PdfSetPasswordDialog( private val onCancelled: () -> Unit, private val onPasswordSet: (String) -> Unit @@ -22,8 +19,6 @@ class PdfSetPasswordDialog( ) { private lateinit var binding: DialogPdfSetPasswordBinding - private var isEnterPasswordVisible = false - private var isConfirmPasswordVisible = false override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -66,23 +61,6 @@ class PdfSetPasswordDialog( dismiss() } } - - binding.enterShowPasswordBtn.setOnClickListener { - isEnterPasswordVisible = !isEnterPasswordVisible - showOrHidePassword( - binding.etPassword, - binding.enterShowPasswordIv, - isEnterPasswordVisible - ) - } - binding.confirmShowPasswordBtn.setOnClickListener { - isConfirmPasswordVisible = !isConfirmPasswordVisible - showOrHidePassword( - binding.etConfirmPassword, - binding.confirmShowPasswordIv, - isConfirmPasswordVisible - ) - } } private fun setupTextWatchers() { @@ -140,17 +118,4 @@ class PdfSetPasswordDialog( return true } - private fun showOrHidePassword(inputView: TextInputEditText, imageView: ImageView, b: Boolean) { - if (b) { - inputView.inputType = - InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD - imageView.setImageResource(R.drawable.show_password) - } else { - inputView.inputType = - InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - imageView.setImageResource(R.drawable.hide_password) - } - // 保持光标在末尾 - inputView.setSelection(inputView.text?.length ?: 0) - } } \ No newline at end of file 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 new file mode 100644 index 0000000..a90e5c8 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt @@ -0,0 +1,135 @@ +package com.all.pdfreader.pro.app.ui.dialog + +import android.graphics.Color +import android.os.Bundle +import android.text.InputType +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.lifecycle.ViewModelProvider +import com.all.pdfreader.pro.app.R +import com.all.pdfreader.pro.app.databinding.DialogPdfPasswordProtectionBinding +import com.all.pdfreader.pro.app.databinding.DialogRenameFileBinding +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.FileUtils.isPdfPasswordCorrect +import com.all.pdfreader.pro.app.viewmodel.PdfViewModel +import java.io.File + +class RenameDialogFragment( + private val filePath: String, + private val onOkClick: () -> Unit, + private val onCancelClick: () -> Unit +) : DialogFragment() { + + private lateinit var binding: DialogRenameFileBinding + private val viewModel by lazy { ViewModelProvider(this)[PdfViewModel::class.java] } + private lateinit var pdfDocument: PdfDocumentEntity + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + binding = DialogRenameFileBinding.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) + + viewModel.pdfDocument.observe(this) { document -> + document?.let { + pdfDocument = it + initView() + setupOnClick() + } ?: run { + showToast(getString(R.string.file_not)) + dismiss() + } + } + viewModel.getPDFDocument(filePath) + } + + private fun initView() { + binding.etName.showKeyboard() + binding.etName.setText(pdfDocument.fileName) + // 保持光标在末尾 + binding.etName.setSelection(binding.etName.text?.length ?: 0) + + } + + private fun setupOnClick() { + binding.tvCancel.setOnClickListener { + onCancelClick() + dismiss() + } + binding.tvConfirm.setOnClickListener { + val text = binding.etName.text.toString() + if (validateEnter(text)) { + + } + } + } + + private fun validateEnter(name: String): Boolean { + // 不允许为空 + if (name.isBlank()) { + binding.tilName.error = getString(R.string.name_not_empty) + return false + } + + // 名字未做修改 + if (name == pdfDocument.fileName) { + binding.tilName.error = getString(R.string.name_not_changed) + return false + } + + // 含有非法字符 + val invalidChars = "[/\\\\:*?\"<>|]".toRegex() + if (invalidChars.containsMatchIn(name)) { + binding.tilName.error = getString(R.string.name_invalid_chars) + return false + } + + // 长度过长 + if (name.length > 255) { + binding.tilName.error = getString(R.string.name_too_long) + return false + } + + // 与现有文件重名 + val parentDir = File(pdfDocument.filePath).parentFile + if (parentDir != null && File(parentDir, name).exists()) { + binding.tilName.error = getString(R.string.name_already_exists) + return false + } + + // 禁止开头/结尾空格 + if (name != name.trim()) { + binding.tilName.error = getString(R.string.name_start_end_space) + return false + } + + 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/SortDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/SortDialogFragment.kt index b507d9d..144eff3 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/SortDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/SortDialogFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.DialogPermissionBinding import com.all.pdfreader.pro.app.databinding.DialogSortBinding import com.all.pdfreader.pro.app.model.SortConfig @@ -30,6 +31,12 @@ class SortDialogFragment( return binding.root } + override fun onStart() { + super.onStart() + dialog?.window?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + ?.setBackgroundResource(R.drawable.dr_rounded_corner_12_bg_white) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val appStore = AppStore(requireActivity()) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt index 5fe2256..03e803a 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt @@ -15,6 +15,7 @@ import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.ui.act.MainActivity import com.all.pdfreader.pro.app.ui.act.PdfViewActivity import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter +import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment import com.all.pdfreader.pro.app.util.PdfScanner import kotlinx.coroutines.launch @@ -40,7 +41,7 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment { val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath) startActivity(intent) }, onMoreClick = { pdf -> - + ListMoreDialogFragment(pdf.filePath).show(parentFragmentManager, TAG) }) binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) 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 fe0dc23..99eb3fe 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 @@ -14,7 +14,11 @@ object AppUtils { * * @param onClick 点击后立即执行的逻辑 */ - fun View.setClickWithAnimation(onClick: () -> Unit) { + fun View.setClickWithAnimation( + scaleFactor: Float = 1.2f, + duration: Long = 150, + onClick: () -> Unit + ) { this.setOnClickListener { // 禁用点击,防止动画未完成重复点击 this.isEnabled = false @@ -24,14 +28,14 @@ object AppUtils { // 播放动画 this.animate() - .scaleX(1.2f) - .scaleY(1.2f) - .setDuration(150) + .scaleX(scaleFactor) + .scaleY(scaleFactor) + .setDuration(duration) .withEndAction { this.animate() .scaleX(1f) .scaleY(1f) - .setDuration(150) + .setDuration(duration) .withEndAction { // 动画结束恢复点击 this.isEnabled = true 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 e1c236c..85446ba 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 @@ -237,6 +237,9 @@ object FileUtils { } } + /** + * 计算文件哈希 + */ fun calculateFileHash(filePath: String): String? { return try { val file = File(filePath) 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 e61c217..2f5d762 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 @@ -2,9 +2,7 @@ package com.all.pdfreader.pro.app.util import android.content.Context import android.graphics.Bitmap -import android.graphics.Canvas import android.graphics.Color -import android.graphics.pdf.PdfRenderer import android.os.ParcelFileDescriptor import android.util.Log import androidx.core.graphics.createBitmap @@ -12,7 +10,10 @@ import com.all.pdfreader.pro.app.PDFReaderApplication 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.FileUtils.isPdfEncrypted +import com.shockwave.pdfium.PdfDocument +import com.shockwave.pdfium.PdfiumCore import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -29,7 +30,9 @@ class PdfScanner( suspend fun scanAndLoadPdfFiles(isNeedFullScan: Boolean, callback: (Boolean) -> Unit = {}) { if (!StoragePermissionHelper.hasBasicStoragePermission(context)) { LogUtil.logDebug(TAG, "权限不足") - callback.invoke(false) + withContext(Dispatchers.Main) { + callback.invoke(false) + } return } scanMutex.withLock {// 保证同一时间只有一次扫描 @@ -64,23 +67,30 @@ class PdfScanner( LogUtil.logDebug(TAG, "密码状态变化 -> ${doc.fileName}") updatedDoc = updatedDoc.copy(isPassword = currentIsPassword) } - // 缩略图(仅非加密文件) - if (!currentIsPassword) { - val newThumbnail = generateThumbnail(context, file) - if (doc.thumbnailPath != newThumbnail) { - updatedDoc = - updatedDoc.copy(thumbnailPath = newThumbnail) - } - } else if (doc.thumbnailPath != null) { - updatedDoc = updatedDoc.copy(thumbnailPath = null) - } pdfRepository.insertOrUpdateDocument(updatedDoc) LogUtil.logDebug(TAG, "✅数据库已更新: ${doc.fileName}") + + if (!currentIsPassword) { + // 异步生成缩略图,但要避免阻塞 + launch(Dispatchers.IO) { + LogUtil.logDebug(TAG, "异步获取图片更新数据") + val newThumbnail = generateFastThumbnail(context, file) + if (newThumbnail != null && doc.thumbnailPath != newThumbnail) { + pdfRepository.updateThumbnailPath(doc.filePath, newThumbnail) + LogUtil.logDebug(TAG, "✅ 缩略图已更新") + } + } + } else if (doc.thumbnailPath != null) { + val updatedDocWithoutThumb = + updatedDoc.copy(thumbnailPath = null) + pdfRepository.insertOrUpdateDocument(updatedDocWithoutThumb) + LogUtil.logDebug(TAG, "✅图片为Null: ${doc.fileName}") + } } } else { // 文件不存在 → 删除数据库记录,并触发全盘扫描 LogUtil.logDebug(TAG, "文件不存在 -> ${doc.fileName}, 删除记录") - pdfRepository.deleteDocument(doc.fileHash) + pdfRepository.deleteDocument(doc.filePath) needFullScan = true } } @@ -103,98 +113,92 @@ class PdfScanner( TAG, "🔄处理文件 ${index + 1}/${allFiles.size}: ${file.name} - ${file.absolutePath}" ) - if (FileUtils.isPdfFile(file)) { - val fileHash = FileUtils.calculateFileHash(file.absolutePath) - LogUtil.logDebug(TAG, "🔑文件哈希: $fileHash") + val existingDoc = + pdfRepository.getDocumentByPath(file.absolutePath) - if (fileHash != null) { - val existingDoc = - pdfRepository.getDocumentByPath(file.absolutePath) + if (existingDoc == null) { + LogUtil.logDebug( + TAG, "🆕发现新PDF文件: ${file.name}" + ) + val isPassword = isPdfEncrypted(file) + LogUtil.logDebug(TAG, "isPassword->${isPassword}") - if (existingDoc == null) { - LogUtil.logDebug( - TAG, "🆕发现新PDF文件: ${file.name}" - ) - var thumbnailPath: String? = null - val isPassword = isPdfEncrypted(file) - LogUtil.logDebug(TAG, "isPassword->${isPassword}") - if (!isPassword) {//没有密码的情况下才去获取缩略图 - thumbnailPath = generateThumbnail(context, file) ?: "" - } - LogUtil.logDebug(TAG, "thumbnailPath->${thumbnailPath}") - val metadata = - PdfMetadataExtractor.extractMetadata(file.absolutePath) - val document = PdfDocumentEntity( - fileHash = fileHash, - filePath = file.absolutePath, - fileName = file.name, - fileSize = file.length(), - lastModified = file.lastModified(), - pageCount = metadata?.pageCount ?: 0, - thumbnailPath = thumbnailPath, - 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) - LogUtil.logDebug( - TAG, " ✅ 已保存到数据库: ${file.name}" - ) - } else { - LogUtil.logDebug(TAG, " 📋 文件已存在: ${file.name}") - // 🔹 文件已存在,检查是否需要更新 - var needUpdate = false - var updatedDoc = existingDoc.copy() + 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) + LogUtil.logDebug(TAG, " ✅ 已保存到数据库: ${file.name}") - // 路径/修改时间更新 - if (existingDoc.filePath != file.absolutePath || existingDoc.lastModified != file.lastModified()) { - LogUtil.logDebug(TAG, "✅ 路径/修改时间需要更新") - updatedDoc = updatedDoc.copy( - filePath = file.absolutePath, - lastModified = file.lastModified() - ) - needUpdate = true - } - - // 是否加密更新 - val currentIsPassword = isPdfEncrypted(file) - if (existingDoc.isPassword != currentIsPassword) { - LogUtil.logDebug(TAG, "✅ 密码状态需要更新") - updatedDoc = - updatedDoc.copy(isPassword = currentIsPassword) - needUpdate = true - } - - if (!currentIsPassword) { - // 如果不是加密 PDF,再生成缩略图 - val newThumbnail = generateThumbnail(context, file) - if (existingDoc.thumbnailPath != newThumbnail) { - LogUtil.logDebug(TAG, "✅ 缩略图需要更新") - updatedDoc = - updatedDoc.copy(thumbnailPath = newThumbnail) - needUpdate = true + if (!isPassword) {//没有密码的情况下才去获取缩略图 + launch(Dispatchers.IO){ + val newThumbnail = generateFastThumbnail(context, file) + if (newThumbnail != null && document.thumbnailPath != newThumbnail) { + pdfRepository.updateThumbnailPath(document.filePath, newThumbnail) + LogUtil.logDebug(TAG, "✅ 缩略图已更新") } - } else { - updatedDoc = updatedDoc.copy(thumbnailPath = null) - needUpdate = true } + } + } else { + LogUtil.logDebug(TAG, " 📋 文件已存在: ${file.name}") + // 🔹 文件已存在,检查是否需要更新 + var needUpdate = false + var updatedDoc = existingDoc.copy() - // 执行更新 - if (needUpdate) { - pdfRepository.insertOrUpdateDocument(updatedDoc) - LogUtil.logDebug( - TAG, "✅ 数据库已更新: ${file.name}" - ) - } else { - LogUtil.logDebug( - TAG, "⏩ 无需更新: ${file.name}" - ) + // 路径/修改时间更新 + if (existingDoc.filePath != file.absolutePath || existingDoc.lastModified != file.lastModified()) { + LogUtil.logDebug(TAG, "✅ 路径/修改时间需要更新") + updatedDoc = updatedDoc.copy( + filePath = file.absolutePath, + lastModified = file.lastModified() + ) + needUpdate = true + } + + // 是否加密更新 + val currentIsPassword = isPdfEncrypted(file) + if (existingDoc.isPassword != currentIsPassword) { + LogUtil.logDebug(TAG, "✅ 密码状态需要更新") + updatedDoc = + updatedDoc.copy(isPassword = currentIsPassword) + needUpdate = true + } + + // 执行更新 + if (needUpdate) { + pdfRepository.insertOrUpdateDocument(updatedDoc) + LogUtil.logDebug(TAG, "✅ 数据库已更新: ${file.name}") + } else { + LogUtil.logDebug(TAG, "⏩ 无需更新: ${file.name}") + } + + // 处理缩略图 + if (!currentIsPassword) { + launch(Dispatchers.IO) { + LogUtil.logDebug(TAG, "异步获取图片更新数据") + val newThumbnail = generateFastThumbnail(context, file) + if (newThumbnail != null && existingDoc.thumbnailPath != newThumbnail) { + pdfRepository.updateThumbnailPath(existingDoc.filePath, newThumbnail) + LogUtil.logDebug(TAG, "✅ 缩略图已更新") + } } + } else { + val noThumbDoc = updatedDoc.copy(thumbnailPath = null) + pdfRepository.insertOrUpdateDocument(noThumbDoc) } } } @@ -202,7 +206,8 @@ class PdfScanner( // 打印数据库中的总记录数 pdfRepository.getAllDocumentsOnce().forEach { doc -> LogUtil.logDebug( - TAG, " 📖 ${doc.fileName} - ${doc.pageCount}页 - ${ + TAG, + " 📖 ${doc.fileName} - ${doc.filePath} - ${doc.pageCount}页 - ${ FileUtils.formatFileSize( doc.fileSize ) @@ -229,10 +234,14 @@ class PdfScanner( TAG, "$string 本次扫描耗时: $scannerTime ms (${scannerTime / 1000.0} 秒)" ) PDFReaderApplication.isNeedFullScan = false - callback.invoke(true) + withContext(Dispatchers.Main) { + callback.invoke(true) + } } catch (e: Exception) { Log.e(TAG, "❌ 扫描出错: ${e.message}", e) - callback.invoke(false) + withContext(Dispatchers.Main) { + callback.invoke(false) + } } } } @@ -252,40 +261,87 @@ class PdfScanner( } - private fun generateThumbnail(context: Context, pdfFile: File): String? { +// private fun generateFastThumbnail(context: Context, pdfFile: File): String? { +// return try { +// val fileDescriptor = +// ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY) +// val pdfRenderer = PdfRenderer(fileDescriptor) +// +// if (pdfRenderer.pageCount > 0) { +// val page = pdfRenderer.openPage(0) +// // 创建 Bitmap +// val bitmap = createBitmap(page.width, page.height) +// val canvas = Canvas(bitmap) +// canvas.drawColor(Color.WHITE) // 填充白色背景 +// +// page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) +// page.close() +// +// // 保存到缓存目录 +// 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) +// } +// +// pdfRenderer.close() +// fileDescriptor.close() +// +// thumbFile.absolutePath +// } else { +// pdfRenderer.close() +// fileDescriptor.close() +// null +// } +// } catch (e: Exception) { +// e.printStackTrace() +// null +// } +// } + + private fun generateFastThumbnail( + context: Context, + pdfFile: File, + maxWidth: Int = 200, + maxHeight: Int = 300 + ): String? { return try { - val fileDescriptor = - ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY) - val pdfRenderer = PdfRenderer(fileDescriptor) + val pdfiumCore = PdfiumCore(context) + val fd = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY) + val pdfDocument: PdfDocument = pdfiumCore.newDocument(fd) - if (pdfRenderer.pageCount > 0) { - val page = pdfRenderer.openPage(0) - // 创建 Bitmap - val bitmap = createBitmap(page.width, page.height) - val canvas = Canvas(bitmap) - canvas.drawColor(Color.WHITE) // 填充白色背景 + // 打开第一页 + pdfiumCore.openPage(pdfDocument, 0) - page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) - page.close() + // 获取原始页宽高 + val width = pdfiumCore.getPageWidthPoint(pdfDocument, 0) + val height = pdfiumCore.getPageHeightPoint(pdfDocument, 0) - // 保存到缓存目录 - val cacheDir = File(context.cacheDir, "thumbnails") - if (!cacheDir.exists()) cacheDir.mkdirs() - val thumbFile = File(cacheDir, pdfFile.nameWithoutExtension + ".jpg") + // 计算缩略图尺寸 + val scale = minOf(maxWidth.toFloat() / width, maxHeight.toFloat() / height) + val thumbWidth = (width * scale).toInt() + val thumbHeight = (height * scale).toInt() - FileOutputStream(thumbFile).use { out -> - bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out) - } + val bitmap = createBitmap(thumbWidth, thumbHeight) + bitmap.eraseColor(Color.WHITE) // 白底 - pdfRenderer.close() - fileDescriptor.close() + // 渲染第一页到 Bitmap + pdfiumCore.renderPageBitmap(pdfDocument, bitmap, 0, 0, 0, thumbWidth, thumbHeight) - thumbFile.absolutePath - } else { - pdfRenderer.close() - fileDescriptor.close() - null + // 保存到缓存 + 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 diff --git a/app/src/main/res/drawable/collect.xml b/app/src/main/res/drawable/collect.xml new file mode 100644 index 0000000..fc0c107 --- /dev/null +++ b/app/src/main/res/drawable/collect.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/collected.xml b/app/src/main/res/drawable/collected.xml new file mode 100644 index 0000000..a13e03b --- /dev/null +++ b/app/src/main/res/drawable/collected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 0000000..74a2c77 --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/dr_dialog_indicator_bg.xml b/app/src/main/res/drawable/dr_dialog_indicator_bg.xml new file mode 100644 index 0000000..795c798 --- /dev/null +++ b/app/src/main/res/drawable/dr_dialog_indicator_bg.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dr_password_state.xml b/app/src/main/res/drawable/dr_password_state.xml new file mode 100644 index 0000000..5e1036a --- /dev/null +++ b/app/src/main/res/drawable/dr_password_state.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/duplicate.xml b/app/src/main/res/drawable/duplicate.xml new file mode 100644 index 0000000..e79bd83 --- /dev/null +++ b/app/src/main/res/drawable/duplicate.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/file_delete.xml b/app/src/main/res/drawable/file_delete.xml new file mode 100644 index 0000000..883a77f --- /dev/null +++ b/app/src/main/res/drawable/file_delete.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/hide_password.xml b/app/src/main/res/drawable/hide_password.xml index 978912e..b595b0b 100644 --- a/app/src/main/res/drawable/hide_password.xml +++ b/app/src/main/res/drawable/hide_password.xml @@ -1,9 +1,9 @@ + android:fillColor="#666666"/> diff --git a/app/src/main/res/drawable/print.xml b/app/src/main/res/drawable/print.xml new file mode 100644 index 0000000..9093a39 --- /dev/null +++ b/app/src/main/res/drawable/print.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/prompt.xml b/app/src/main/res/drawable/prompt.xml new file mode 100644 index 0000000..0d7d183 --- /dev/null +++ b/app/src/main/res/drawable/prompt.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/rename_text.xml b/app/src/main/res/drawable/rename_text.xml new file mode 100644 index 0000000..c09332a --- /dev/null +++ b/app/src/main/res/drawable/rename_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/share.xml b/app/src/main/res/drawable/share.xml new file mode 100644 index 0000000..dbe1efb --- /dev/null +++ b/app/src/main/res/drawable/share.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/show_password.xml b/app/src/main/res/drawable/show_password.xml index eb91df0..3876fa6 100644 --- a/app/src/main/res/drawable/show_password.xml +++ b/app/src/main/res/drawable/show_password.xml @@ -1,9 +1,9 @@ + android:fillColor="#666666"/> diff --git a/app/src/main/res/layout/dialog_list_more.xml b/app/src/main/res/layout/dialog_list_more.xml new file mode 100644 index 0000000..83ad345 --- /dev/null +++ b/app/src/main/res/layout/dialog_list_more.xml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_pdf_password_protection.xml b/app/src/main/res/layout/dialog_pdf_password_protection.xml index 60f710e..d5ee6e7 100644 --- a/app/src/main/res/layout/dialog_pdf_password_protection.xml +++ b/app/src/main/res/layout/dialog_pdf_password_protection.xml @@ -38,7 +38,7 @@ android:layout_height="wrap_content" android:hint="@string/enter_password" app:endIconMode="password_toggle" - app:passwordToggleEnabled="true"> + app:endIconDrawable="@drawable/dr_password_state"> - - - - - diff --git a/app/src/main/res/layout/dialog_pdf_set_password.xml b/app/src/main/res/layout/dialog_pdf_set_password.xml index bb31797..47429be 100644 --- a/app/src/main/res/layout/dialog_pdf_set_password.xml +++ b/app/src/main/res/layout/dialog_pdf_set_password.xml @@ -37,6 +37,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/enter_password" + app:endIconDrawable="@drawable/dr_password_state" app:endIconMode="password_toggle" app:passwordToggleEnabled="true"> @@ -50,22 +51,6 @@ - - - - - - @@ -91,22 +77,6 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_sort.xml b/app/src/main/res/layout/dialog_sort.xml index e483b95..340de54 100644 --- a/app/src/main/res/layout/dialog_sort.xml +++ b/app/src/main/res/layout/dialog_sort.xml @@ -1,9 +1,7 @@ #F6F6F6 #E0E0E0 #2c2c2c + #666666 \ 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 ddc10e8..68b1574 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,4 +39,21 @@ PDF loading failed Password must be at least 4 characters Passwords do not match + Rename + Details + Share + Print + Added to Favorite + Removed from Favorites + Delete File + Set Password + Remove Password + Duplicate File + Enter a name + File name cannot be empty + File name has not been changed + File name contains invalid characters: / \ : * ? " < > | + File name is too long (max 255 characters) + A file with the same name already exists + File name cannot start or end with space \ No newline at end of file