From 298585b602f0365c4c82d2cf7ef37d56a59c186f Mon Sep 17 00:00:00 2001 From: ocean <503259349@qq.com> Date: Wed, 24 Sep 2025 14:33:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=8C=E5=8C=85=E5=90=AB=E6=89=80=E6=9C=89=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 6 + .../pro/app/ui/act/SplitPdfActivity.kt | 68 ++++++--- .../pro/app/ui/act/SplitPdfResultActivity.kt | 138 ++++++++++++++++-- .../app/ui/adapter/SplitPdfResultAdapter.kt | 20 ++- .../all/pdfreader/pro/app/util/PdfScanner.kt | 82 ++++++++--- .../all/pdfreader/pro/app/util/PdfUtils.kt | 1 - .../res/layout/activity_pdf_split_result.xml | 53 ++++++- app/src/main/res/values/strings.xml | 4 +- 8 files changed, 315 insertions(+), 57 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 29ec8d2..afd2cb2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,6 +73,12 @@ android:label="@string/app_name" android:screenOrientation="portrait" /> + + @@ -143,7 +160,7 @@ class SplitPdfActivity : BaseActivity() { splitList[pageItem.pageIndex] = pageItem adapter.updateItem(pageItem.pageIndex) } - if (!firstPageLoaded) {//有数据回来则隐藏loading,显示全选按钮 + if (!firstPageLoaded) { binding.loadingRoot.root.visibility = View.GONE binding.selectAllBtn.visibility = View.VISIBLE firstPageLoaded = true @@ -176,10 +193,12 @@ class SplitPdfActivity : BaseActivity() { private fun updateViewState(b: Boolean) { if (b) { + currentViewState = ViewState.SPLIT_SELECTED binding.selectAllBtn.visibility = View.GONE//隐藏全选按钮 binding.addBtn.visibility = View.VISIBLE//显示add按钮 binding.title.text = getString(R.string.split_pdf)//设置标题 } else { + currentViewState = ViewState.SPLIT_LIST binding.selectAllBtn.visibility = View.VISIBLE binding.addBtn.visibility = View.GONE binding.title.text = @@ -221,26 +240,37 @@ class SplitPdfActivity : BaseActivity() { override fun onDestroy() { super.onDestroy() - splitList.clear() - PdfUtils.clearPdfThumbsCache(this) } private fun setupBackPressedCallback() { onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - if (isSelectedViewShow) { - //使用提示对话框 - PromptDialogFragment( - getString(R.string.exit_split), - getString(R.string.confirm_discard_changes), - getString(R.string.discard), - onOkClick = { - isEnabled = false - onBackPressedDispatcher.onBackPressed() - }).show(supportFragmentManager, getString(R.string.exit_split)) - } else { + if (!isSelectedViewShow) {//点击继续后,说明已经操过过了,不直接finish isEnabled = false onBackPressedDispatcher.onBackPressed() + return + } + when (currentViewState) { + ViewState.SPLIT_SELECTED -> { + // 已选页面列表,提示是否退出 + PromptDialogFragment( + getString(R.string.exit_split), + getString(R.string.confirm_discard_changes), + getString(R.string.discard), + onOkClick = { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + }).show(supportFragmentManager, getString(R.string.exit_split)) + } + + ViewState.SPLIT_LIST -> { + // 拆分页列表,返回到已选页面列表 + adapter.setAllSelected(false)//重置所有选中状态为false + updateSelectAllState(false)//重置选中按钮 + binding.continueNowBtn.isEnabled = false//继续按钮不可点击 + updateContinueNowBtnState(false)//重置继续按钮背景 + updateViewState(true) + } } } }) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfResultActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfResultActivity.kt index 21bcbd0..6da8d6b 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfResultActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfResultActivity.kt @@ -2,11 +2,17 @@ package com.all.pdfreader.pro.app.ui.act import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle +import android.os.Environment +import android.os.Parcelable +import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager +import com.all.pdfreader.pro.app.PRApp import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitResultBinding import com.all.pdfreader.pro.app.model.PdfPageItem @@ -14,9 +20,14 @@ import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem import com.all.pdfreader.pro.app.model.PdfSplitResultItem import com.all.pdfreader.pro.app.ui.act.SplitPdfActivity import com.all.pdfreader.pro.app.ui.adapter.SplitPdfResultAdapter +import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment +import com.all.pdfreader.pro.app.util.AppUtils +import com.all.pdfreader.pro.app.util.PdfScanner import com.all.pdfreader.pro.app.util.PdfUtils import com.gyf.immersionbar.ImmersionBar +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File class SplitPdfResultActivity : BaseActivity() { @@ -26,11 +37,10 @@ class SplitPdfResultActivity : BaseActivity() { private const val EXTRA_SELECTED_LIST = "extra_selected_list" fun createIntent( - context: Context, - selectedPageIndices: List + context: Context, list: ArrayList ): Intent { return Intent(context, SplitPdfResultActivity::class.java).apply { - + putParcelableArrayListExtra(EXTRA_SELECTED_LIST, list) } } } @@ -38,15 +48,24 @@ class SplitPdfResultActivity : BaseActivity() { private lateinit var binding: ActivityPdfSplitResultBinding private lateinit var adapter: SplitPdfResultAdapter private var splitResultList: MutableList = mutableListOf() + private lateinit var selectedList: ArrayList + private var isSplitting = false + private var exitDialog: PromptDialogFragment? = null + private val pdfRepository = getRepository() + private lateinit var pdfScanner: PdfScanner override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityPdfSplitResultBinding.inflate(layoutInflater) setContentView(binding.root) + setupBackPressedCallback() ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true) .navigationBarColor(R.color.bg_color).init() + selectedList = requireParcelableArrayList(EXTRA_SELECTED_LIST) + pdfScanner = PdfScanner(this, pdfRepository) initView() setupClick() + initData() } private fun initView() { @@ -55,25 +74,80 @@ class SplitPdfResultActivity : BaseActivity() { binding.recyclerView.adapter = adapter } - private fun startSplittingPDF( - inputFile: File, selectedPages: List, outputDir: File, outputFileName: String - ) { - lifecycleScope.launch { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - val resultFile = PdfUtils.exportSelectedPages( + private fun initData() { + lifecycleScope.launch(Dispatchers.IO) { + val totalPages = selectedList.sumOf { it.pages.count { it.isSelected } } + var processedPages = 0 + + withContext(Dispatchers.Main) { + isSplitting = true + binding.splittingLayout.visibility = View.VISIBLE + binding.progressBar.isIndeterminate = false + binding.progressBar.progress = 0 + binding.progressBar.max = 100 + } + + for (item in selectedList) { + val selectedPages = item.pages.filter { it.isSelected } + if (selectedPages.isEmpty()) continue + + val inputFile = File(item.filePath) + val outputDir = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), + "PDFReaderPro/split" + ).apply { if (!exists()) mkdirs() } + + PdfUtils.exportSelectedPages( inputFile = inputFile, selectedPages = selectedPages, outputDir = outputDir, - outputFileName = outputFileName, - onProgress = { current, total -> - - }) + outputFileName = "${item.fileName}.pdf", + onProgress = { _, _ -> // 不需要单文件百分比 + processedPages++// 每页处理完成就加一,多个 PDF 顺序处理时,总进度线性递增 + val percent = (processedPages.toFloat() / totalPages * 100).toInt() + lifecycleScope.launch(Dispatchers.Main) { + binding.progressTv.text = "$percent" + binding.progressBar.progress = percent + } + })?.let { resultFile -> + val thumbnails = + AppUtils.generateFastThumbnail(this@SplitPdfResultActivity, resultFile) + val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false) + pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) { + splitResultList.add(result) + } + } + } + withContext(Dispatchers.Main) { + binding.splittingLayout.visibility = View.GONE + // 默认选中第一个 + if (splitResultList.isNotEmpty() && splitResultList.none { it.isSelected }) { + splitResultList[0].isSelected = true + } + adapter.updateAdapter() + isSplitting = false//拆分结束 + exitDialog?.dismissAllowingStateLoss() + exitDialog = null } } } private fun setupClick() { binding.backBtn.setOnClickListener { + onBackPressedDispatcher.onBackPressed() + } + binding.shareBtn.setOnClickListener { + val selectedItem = adapter.getSelectedItem() + selectedItem?.let { + AppUtils.shareFile(this, File(selectedItem.filePath)) + } + } + binding.okBtn.setOnClickListener { + val selectedItem = adapter.getSelectedItem() + selectedItem?.let { + val intent = PdfViewActivity.createIntent(this, selectedItem.filePath) + startActivity(intent) + } finish() } } @@ -82,4 +156,42 @@ class SplitPdfResultActivity : BaseActivity() { super.onDestroy() } + private fun setupBackPressedCallback() { + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (isSplitting) { + exitDialog = PromptDialogFragment( + getString(R.string.exit_split), + getString(R.string.confirm_discard_changes), + getString(R.string.discard), + onOkClick = { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + }) + exitDialog?.show(supportFragmentManager, getString(R.string.exit_split)) + } else { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + }) + } + + /** + * 通用方法:读取必传参数,如果为 null 直接 finish + */ + @Suppress("DEPRECATION") + private inline fun requireParcelableArrayList(key: String): ArrayList { + val result: ArrayList? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra(key, T::class.java) + } else { + intent.getParcelableArrayListExtra(key) + } + + if (result.isNullOrEmpty()) { + showToast(getString(R.string.pdf_loading_failed)) + finish() + } + return result ?: arrayListOf() + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfResultAdapter.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfResultAdapter.kt index 08a9bac..701f572 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfResultAdapter.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfResultAdapter.kt @@ -43,6 +43,20 @@ class SplitPdfResultAdapter( } else { holder.binding.selectIv.visibility = View.INVISIBLE } + holder.binding.root.setOnClickListener { + val oldSelectedIndex = list.indexOfFirst { it.isSelected } + val newSelectedIndex = holder.bindingAdapterPosition + if (oldSelectedIndex != newSelectedIndex && newSelectedIndex != RecyclerView.NO_POSITION) { + // 取消之前选中 + if (oldSelectedIndex >= 0) { + list[oldSelectedIndex].isSelected = false + notifyItemChanged(oldSelectedIndex) + } + // 选中当前 + list[newSelectedIndex].isSelected = true + notifyItemChanged(newSelectedIndex) + } + } } override fun getItemCount(): Int = list.size @@ -52,7 +66,7 @@ class SplitPdfResultAdapter( notifyDataSetChanged() } - fun updateItem(position: Int) = notifyItemChanged(position) - - fun removeItem(position: Int) = notifyItemRemoved(position) + fun getSelectedItem(): PdfSplitResultItem? { + return list.firstOrNull { it.isSelected } + } } 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 5996ca0..cc535ff 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 @@ -70,8 +70,7 @@ class PdfScanner( val newThumbnail = generateFastThumbnail(context, file) if (newThumbnail != null && doc.thumbnailPath != newThumbnail) { pdfRepository.updateThumbnailPath( - doc.filePath, - newThumbnail + doc.filePath, newThumbnail ) LogUtil.logDebug(TAG, "✅ 缩略图已更新") } @@ -136,8 +135,7 @@ class PdfScanner( val newThumbnail = generateFastThumbnail(context, file) if (newThumbnail != null && document.thumbnailPath != newThumbnail) { pdfRepository.updateThumbnailPath( - document.filePath, - newThumbnail + document.filePath, newThumbnail ) LogUtil.logDebug(TAG, "✅ 缩略图已更新") } @@ -162,8 +160,7 @@ class PdfScanner( val currentIsPassword = isPdfEncrypted(file) if (existingDoc.isPassword != currentIsPassword) { LogUtil.logDebug(TAG, "✅ 密码状态需要更新") - updatedDoc = - updatedDoc.copy(isPassword = currentIsPassword) + updatedDoc = updatedDoc.copy(isPassword = currentIsPassword) needUpdate = true } @@ -180,8 +177,7 @@ class PdfScanner( val newThumbnail = generateFastThumbnail(context, file) if (newThumbnail != null && existingDoc.thumbnailPath != newThumbnail) { pdfRepository.updateThumbnailPath( - existingDoc.filePath, - newThumbnail + existingDoc.filePath, newThumbnail ) LogUtil.logDebug(TAG, "✅ 缩略图已更新") } @@ -194,7 +190,9 @@ class PdfScanner( val file = File(doc.filePath) if (!file.exists()) { // 文件不存在 → 删除数据库记录 - LogUtil.logDebug(TAG, "最终过滤:文件不存在 -> ${doc.fileName}, 删除记录") + LogUtil.logDebug( + TAG, "最终过滤:文件不存在 -> ${doc.fileName}, 删除记录" + ) pdfRepository.deleteDocument(doc.filePath) } else { LogUtil.logDebug( @@ -240,16 +238,62 @@ class PdfScanner( } } - fun shouldScan(): Boolean { - return ScanManager.shouldScan(context) + /** + * 添加单个pdf文件到数据库 + */ + suspend fun addNewPdfToDatabase( + pathFile: String, + thumbnailPath: String?, + callback: (Boolean) -> Unit = {} + ) { + val file = File(pathFile) + if (!file.exists() || !FileUtils.isPdfFile(file)) { + LogUtil.logDebug(TAG, "文件不存在或不是PDF: $pathFile") + withContext(Dispatchers.Main) { + callback.invoke(false) + } + return + } + + try { + withContext(Dispatchers.IO) { + val existingDoc = pdfRepository.getDocumentByPath(file.absolutePath) + if (existingDoc == 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 = 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, "✅ 新PDF已保存到数据库: ${file.name}") + + withContext(Dispatchers.Main) { + callback.invoke(true) + } + } else { + Log.e(TAG, "数据库有已有相同的文件") + } + } + } catch (e: Exception) { + Log.e(TAG, "❌ 添加新PDF出错: ${e.message}", e) + withContext(Dispatchers.Main) { + callback.invoke(false) + } + } } - fun getLastScanTime(): Long { - return ScanManager.getLastScanTime(context) - } - - fun getHoursSinceLastScan(): Long { - val lastScan = getLastScanTime() - return TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - lastScan) - } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt index 3abdbfc..53bdc8e 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt @@ -133,5 +133,4 @@ object PdfUtils { null } } - } diff --git a/app/src/main/res/layout/activity_pdf_split_result.xml b/app/src/main/res/layout/activity_pdf_split_result.xml index bb11106..90f4f80 100644 --- a/app/src/main/res/layout/activity_pdf_split_result.xml +++ b/app/src/main/res/layout/activity_pdf_split_result.xml @@ -11,6 +11,57 @@ android:layout_width="match_parent" android:layout_height="0dp" /> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5990f19..4ffd7fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,10 +15,11 @@ Permission is required to access files Cancel OK + Open Sort by Created Date Path - Path %1$s + Path: %1$s File Name File Size Ascending @@ -133,4 +134,5 @@ Congratulations Your file has been successfully created Please select at least one page + Splitting… \ No newline at end of file