diff --git a/app/src/main/java/com/all/pdfreader/pro/app/model/PdfPageItem.kt b/app/src/main/java/com/all/pdfreader/pro/app/model/PdfPageItem.kt index 9adcc62..a955bf4 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/model/PdfPageItem.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/model/PdfPageItem.kt @@ -1,10 +1,14 @@ package com.all.pdfreader.pro.app.model +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + /** * 拆分pdf,展示用的每页信息 */ +@Parcelize data class PdfPageItem( val pageIndex: Int, var previewFilePath: String?, // 缓存文件路径 var isSelected: Boolean -) +) : Parcelable diff --git a/app/src/main/java/com/all/pdfreader/pro/app/model/PdfSelectedPagesItem.kt b/app/src/main/java/com/all/pdfreader/pro/app/model/PdfSelectedPagesItem.kt new file mode 100644 index 0000000..d2d6841 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/model/PdfSelectedPagesItem.kt @@ -0,0 +1,31 @@ +package com.all.pdfreader.pro.app.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + * 拆分pdf,被选中使用的结构 + */ +@Parcelize +data class PdfSelectedPagesItem( + val filePath: String, + var fileName: String, // 文件名 + val pages: List // 所有页的数据 +): Parcelable { + /** + * 获取已选中的页面列表 + */ + fun selectedPages(): List = + pages.filter { it.isSelected }.sortedBy { it.pageIndex } + + /** + * 获取已选中的页码字符串,例如 "1,2,3,4,6" + */ + fun selectedPageNumbersString(): String = + selectedPages().joinToString(",") { (it.pageIndex + 1).toString() } + + /** + * 获取第一个图片,没有返回空字符串 + */ + fun getFirstThumbPath(): String = pages.first().previewFilePath ?: "" +} diff --git a/app/src/main/java/com/all/pdfreader/pro/app/model/PdfSplitResultItem.kt b/app/src/main/java/com/all/pdfreader/pro/app/model/PdfSplitResultItem.kt new file mode 100644 index 0000000..1be956b --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/model/PdfSplitResultItem.kt @@ -0,0 +1,7 @@ +package com.all.pdfreader.pro.app.model + +data class PdfSplitResultItem( + val filePath: String, + val thumbnailPath: String? = null, + var isSelected: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/model/RenameConfig.kt b/app/src/main/java/com/all/pdfreader/pro/app/model/RenameConfig.kt index 6643aaa..7be16c3 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/model/RenameConfig.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/model/RenameConfig.kt @@ -2,5 +2,6 @@ package com.all.pdfreader.pro.app.model enum class RenameType { FILE, // 修改文件名 - BOOKMARK // 修改书签名 + BOOKMARK, // 修改书签名 + NAME // 修改集合的item名称 } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfActivity.kt index b660b85..481f61e 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfActivity.kt @@ -4,14 +4,22 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitBinding import com.all.pdfreader.pro.app.model.PdfPageItem +import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem +import com.all.pdfreader.pro.app.model.RenameType import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter +import com.all.pdfreader.pro.app.ui.adapter.SplitSelectedPdfAdapter +import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment +import com.all.pdfreader.pro.app.ui.dialog.RenameDialogFragment +import com.all.pdfreader.pro.app.util.FileUtils.toUnderscoreDateTime import com.all.pdfreader.pro.app.util.PdfUtils import com.gyf.immersionbar.ImmersionBar import kotlinx.coroutines.launch @@ -33,14 +41,19 @@ class SplitPdfActivity : BaseActivity() { private lateinit var binding: ActivityPdfSplitBinding private lateinit var adapter: SplitPdfAdapter private var splitList: MutableList = mutableListOf() + private var selectedList: MutableList = mutableListOf() + private lateinit var selectedPdfAdapter: SplitSelectedPdfAdapter + private var isSelectedViewShow = false//用于返回的时候是否提示(点击了继续按钮就会产生提示) + private lateinit var filePath: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityPdfSplitBinding.inflate(layoutInflater) setContentView(binding.root) + setupBackPressedCallback() ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true) - .navigationBarColor(R.color.white).init() - val filePath = intent.getStringExtra(EXTRA_PDF_PATH) + .navigationBarColor(R.color.bg_color).init() + filePath = intent.getStringExtra(EXTRA_PDF_PATH) ?: throw IllegalArgumentException("PDF file hash is required") if (filePath.isEmpty()) { showToast(getString(R.string.file_not)) @@ -52,40 +65,77 @@ class SplitPdfActivity : BaseActivity() { } private fun initView() { + binding.continueNowBtn.isEnabled = false binding.title.text = getString(R.string.selected_page, 0) - binding.selectAllBtn.visibility = View.GONE - binding.loadingRoot.root.visibility = View.VISIBLE + binding.selectAllBtn.visibility = View.GONE//初始隐藏全选按钮 + binding.addBtn.visibility = View.GONE//初始隐藏add按钮 + binding.loadingRoot.root.visibility = View.VISIBLE//初始显示loading binding.splitRv.layoutManager = GridLayoutManager(this, 2) adapter = SplitPdfAdapter(splitList) { item, pos -> item.isSelected = !item.isSelected adapter.setItemSelected(pos, item.isSelected) binding.title.text = getString(R.string.selected_page, adapter.getSelPages()) + + // 只有存在选中的item则为true + val anySelected = splitList.any { it.isSelected } + binding.continueNowBtn.isEnabled = anySelected + updateContinueNowBtnState(anySelected) } binding.splitRv.adapter = adapter + + selectedPdfAdapter = SplitSelectedPdfAdapter(selectedList, onEditClick = { item, pos -> + RenameDialogFragment( + RenameType.NAME, null, item.fileName, onOkClickString = { string -> + selectedList[pos].fileName = string + selectedPdfAdapter.updateItem(pos) + }).show(supportFragmentManager, "BookmarksDialogFragment") + }, onDeleteClick = { item, pos -> + selectedList.remove(item) + selectedPdfAdapter.removeItem(pos) + updateSplitBtnState(selectedList.isNotEmpty()) + }) + binding.splitSelectRv.layoutManager = LinearLayoutManager(this) + binding.splitSelectRv.adapter = selectedPdfAdapter } private fun setupClick() { - binding.backBtn.setOnClickListener { finish() } + binding.backBtn.setOnClickListener { onBackPressedDispatcher.onBackPressed() } binding.selectAllBtn.setOnClickListener { - val selectAll = splitList.any { !it.isSelected } + val selectAll = splitList.any { !it.isSelected }//如果列表里有一页没选中 → 返回 true adapter.setAllSelected(selectAll) binding.title.text = getString(R.string.selected_page, adapter.getSelPages()) + binding.continueNowBtn.isEnabled = selectAll + updateSelectAllState(selectAll) + updateContinueNowBtnState(selectAll) + } + binding.continueNowBtn.setOnClickListener { + val selectedPages = splitList.filter { it.isSelected }.map { it.copy() } + val name = + getString(R.string.split) + "_" + System.currentTimeMillis().toUnderscoreDateTime() + val item = PdfSelectedPagesItem(filePath, name, selectedPages) + selectedList.add(item) + selectedPdfAdapter.updateAdapter() + isSelectedViewShow = true + updateViewState(true) + } + binding.addBtn.setOnClickListener { + //点击重新添加后,选中状态全部置为false,全选按钮置为false + adapter.setAllSelected(false) + updateSelectAllState(false) + updateViewState(false) + } + binding.splitBtn.setOnClickListener { - if (selectAll) { - binding.selectAll.setBackgroundResource(R.drawable.dr_circular_sel_on_bg) - } else { - binding.selectAll.setBackgroundResource(R.drawable.dr_circular_sel_off_bg) - } } } private fun initSplitData(file: File) { lifecycleScope.launch { + splitList.clear() PdfUtils.clearPdfThumbsCache(this@SplitPdfActivity) // 先清理旧缓存 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { var firstPageLoaded = false PdfUtils.splitPdfToPageItemsFlow(this@SplitPdfActivity, file).collect { pageItem -> - logDebug("pageItem flow ->$pageItem") if (splitList.size <= pageItem.pageIndex) { splitList.add(pageItem) adapter.notifyItemInserted(splitList.size - 1) @@ -93,20 +143,106 @@ class SplitPdfActivity : BaseActivity() { splitList[pageItem.pageIndex] = pageItem adapter.updateItem(pageItem.pageIndex) } - if (!firstPageLoaded) { + if (!firstPageLoaded) {//有数据回来则隐藏loading,显示全选按钮 binding.loadingRoot.root.visibility = View.GONE binding.selectAllBtn.visibility = View.VISIBLE firstPageLoaded = true } } - binding.loadingRoot.root.visibility = View.GONE } } } + private fun updateSelectAllState(b: Boolean) { + binding.selectAll.setBackgroundResource( + if (b) R.drawable.dr_circular_sel_on_bg + else R.drawable.dr_circular_sel_off_bg + ) + } + + private fun updateContinueNowBtnState(b: Boolean) { + binding.continueNowBtn.setBackgroundResource( + if (b) R.drawable.dr_click_btn_bg + else R.drawable.dr_btn_not_clickable_bg + ) + } + + private fun updateSplitBtnState(b: Boolean) { + binding.splitBtn.setBackgroundResource( + if (b) R.drawable.dr_click_btn_bg + else R.drawable.dr_btn_not_clickable_bg + ) + } + + private fun updateViewState(b: Boolean) { + if (b) { + binding.selectAllBtn.visibility = View.GONE//隐藏全选按钮 + binding.addBtn.visibility = View.VISIBLE//显示add按钮 + binding.title.text = getString(R.string.split_pdf)//设置标题 + } else { + binding.selectAllBtn.visibility = View.VISIBLE + binding.addBtn.visibility = View.GONE + binding.title.text = + getString(R.string.selected_page, adapter.getSelPages())//重新设置标题为选中了多少个 + } + updateViewStateWithAnimation(b) + + updateSplitBtnState(selectedList.isNotEmpty()) + } + + private fun updateViewStateWithAnimation(showSelectedView: Boolean) { + if (showSelectedView) { + // 切换到已选页面列表 + binding.splitListLayout.animate().translationY(-50f).alpha(0f).setDuration(200) + .withEndAction { + binding.splitListLayout.visibility = View.GONE + binding.splitListLayout.translationY = 0f + }.start() + + binding.splitSelectLayout.alpha = 0f + binding.splitSelectLayout.translationY = 50f + binding.splitSelectLayout.visibility = View.VISIBLE + binding.splitSelectLayout.animate().translationY(0f).alpha(1f).setDuration(200).start() + } else { + // 切回拆分页列表 + binding.splitSelectLayout.animate().translationY(50f).alpha(0f).setDuration(200) + .withEndAction { + binding.splitSelectLayout.visibility = View.GONE + binding.splitSelectLayout.translationY = 0f + }.start() + + binding.splitListLayout.alpha = 0f + binding.splitListLayout.translationY = -50f + binding.splitListLayout.visibility = View.VISIBLE + binding.splitListLayout.animate().translationY(0f).alpha(1f).setDuration(200).start() + } + } + + 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 { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + }) + } } \ No newline at end of file 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 new file mode 100644 index 0000000..21bcbd0 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SplitPdfResultActivity.kt @@ -0,0 +1,85 @@ +package com.all.pdfreader.pro.app.ui.act + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.LinearLayoutManager +import com.all.pdfreader.pro.app.R +import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitResultBinding +import com.all.pdfreader.pro.app.model.PdfPageItem +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.util.PdfUtils +import com.gyf.immersionbar.ImmersionBar +import kotlinx.coroutines.launch +import java.io.File + +class SplitPdfResultActivity : BaseActivity() { + override val TAG: String = "SplitPdfResultActivity" + + companion object { + private const val EXTRA_SELECTED_LIST = "extra_selected_list" + + fun createIntent( + context: Context, + selectedPageIndices: List + ): Intent { + return Intent(context, SplitPdfResultActivity::class.java).apply { + + } + } + } + + private lateinit var binding: ActivityPdfSplitResultBinding + private lateinit var adapter: SplitPdfResultAdapter + private var splitResultList: MutableList = mutableListOf() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityPdfSplitResultBinding.inflate(layoutInflater) + setContentView(binding.root) + ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true) + .navigationBarColor(R.color.bg_color).init() + initView() + setupClick() + } + + private fun initView() { + adapter = SplitPdfResultAdapter(splitResultList) + binding.recyclerView.layoutManager = LinearLayoutManager(this) + 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( + inputFile = inputFile, + selectedPages = selectedPages, + outputDir = outputDir, + outputFileName = outputFileName, + onProgress = { current, total -> + + }) + } + } + } + + private fun setupClick() { + binding.backBtn.setOnClickListener { + finish() + } + } + + override fun onDestroy() { + super.onDestroy() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfAdapter.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfAdapter.kt index 44dc759..edf4fcf 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfAdapter.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfAdapter.kt @@ -51,10 +51,12 @@ class SplitPdfAdapter( holder.binding.apply { //更新文字图片与选中状态 pageNumberTv.text = "${item.pageIndex + 1}" - Glide.with(holder.binding.root) - .load(File(item.previewFilePath ?: "")) - .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context))) - .into(image) + if (item.previewFilePath != null) { + Glide.with(holder.binding.root) + .load(File(item.previewFilePath ?: "")) + .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context))) + .into(image) + } bindSelection(item, holder) } holder.binding.root.setOnClickListener { @@ -74,7 +76,7 @@ class SplitPdfAdapter( private fun bindSelection(item: PdfPageItem, holder: PdfViewHolder) { val b = holder.binding val selRes = - if (item.isSelected) R.drawable.dr_circular_sel_on_bg else R.drawable.dr_circular_sel_off_bg + if (item.isSelected) R.drawable.dr_item_page_img_sel_on_bg else R.drawable.dr_item_page_img_sel_off_bg b.selIv.setBackgroundResource(selRes) val bgRes = if (item.isSelected) R.drawable.dr_sel_on_frame else R.drawable.dr_sel_off_frame 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 new file mode 100644 index 0000000..08a9bac --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitPdfResultAdapter.kt @@ -0,0 +1,58 @@ +package com.all.pdfreader.pro.app.ui.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.all.pdfreader.pro.app.R +import com.all.pdfreader.pro.app.databinding.AdapterSplitSelectedResultItemBinding +import com.all.pdfreader.pro.app.model.PdfSplitResultItem +import com.all.pdfreader.pro.app.util.AppUtils.dpToPx +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import java.io.File + +class SplitPdfResultAdapter( + private val list: MutableList +) : RecyclerView.Adapter() { + + inner class PdfViewHolder(val binding: AdapterSplitSelectedResultItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PdfViewHolder( + AdapterSplitSelectedResultItemBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: PdfViewHolder, position: Int) { + val item = list[position] + if (item.thumbnailPath != null) { + Glide.with(holder.binding.root).load(File(item.thumbnailPath)) + .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context))) + .into(holder.binding.image) + } + holder.binding.nameTv.text = File(item.filePath).name + holder.binding.pathTv.text = + holder.binding.root.context.getString(R.string.path_details, item.filePath) + if (item.isSelected) { + holder.binding.selectIv.visibility = View.VISIBLE + } else { + holder.binding.selectIv.visibility = View.INVISIBLE + } + } + + override fun getItemCount(): Int = list.size + + @SuppressLint("NotifyDataSetChanged") + fun updateAdapter() { + notifyDataSetChanged() + } + + fun updateItem(position: Int) = notifyItemChanged(position) + + fun removeItem(position: Int) = notifyItemRemoved(position) +} diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitSelectedPdfAdapter.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitSelectedPdfAdapter.kt new file mode 100644 index 0000000..f422619 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SplitSelectedPdfAdapter.kt @@ -0,0 +1,61 @@ +package com.all.pdfreader.pro.app.ui.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.all.pdfreader.pro.app.databinding.AdapterSplitSelectedPageItemBinding +import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem +import com.all.pdfreader.pro.app.util.AppUtils.dpToPx +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import java.io.File + +class SplitSelectedPdfAdapter( + private val list: MutableList, + private val onEditClick: (PdfSelectedPagesItem, Int) -> Unit, + private val onDeleteClick: (PdfSelectedPagesItem, Int) -> Unit +) : RecyclerView.Adapter() { + + inner class PdfViewHolder(val binding: AdapterSplitSelectedPageItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PdfViewHolder( + AdapterSplitSelectedPageItemBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: PdfViewHolder, position: Int) { + val item = list[position] + holder.binding.tvPages.text = item.selectedPageNumbersString() + val thumbPath = item.getFirstThumbPath() + if (thumbPath.isNotEmpty()) { + Glide.with(holder.binding.root).load(File(thumbPath)) + .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context))) + .into(holder.binding.tvFileImg) + } + holder.binding.tvFileName.text = item.fileName + holder.binding.editTitleBtn.setOnClickListener { + val pos = holder.bindingAdapterPosition + onEditClick(item, pos) + } + holder.binding.deleteBtn.setOnClickListener { + val pos = holder.bindingAdapterPosition + onDeleteClick(item, pos) + } + } + + override fun getItemCount(): Int = list.size + + @SuppressLint("NotifyDataSetChanged") + fun updateAdapter() { + notifyDataSetChanged() + } + + fun updateItem(position: Int) = notifyItemChanged(position) + + fun removeItem(position: Int) = notifyItemRemoved(position) +} diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/BookmarksDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/BookmarksDialogFragment.kt index 0b7b1be..6eddffe 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/BookmarksDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/BookmarksDialogFragment.kt @@ -1,7 +1,6 @@ package com.all.pdfreader.pro.app.ui.dialog import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -125,10 +124,10 @@ class BookmarksDialogFragment( dialogAddBookMarks() } binding.deleteALLPageBtn.setOnClickListener { - DeleteDialogFragment( + PromptDialogFragment( getString(R.string.delete_bookmarks_title), getString(R.string.delete_bookmarks_desc), - onDeleteClick = { + onOkClick = { viewModel.deleteAllBookmark(pdfDocument.filePath) adapter.removeAllItems() bookmarks = emptyList() diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt index 9cfcea1..338d2d3 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/ListMoreDialogFragment.kt @@ -1,7 +1,6 @@ package com.all.pdfreader.pro.app.ui.dialog import android.net.Uri -import android.nfc.Tag import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -115,10 +114,10 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() dismiss() } binding.deleteFileBtn.setOnClickListener { - DeleteDialogFragment( + PromptDialogFragment( getString(R.string.delete_file_title), getString(R.string.delete_file_desc), - onDeleteClick = { + onOkClick = { viewModel.deleteFile(pdfDocument.filePath) }).show(parentFragmentManager, "deleteFile") dismiss() @@ -179,11 +178,11 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() dismiss() } binding.removeRecentBtn.setOnClickListener { - DeleteDialogFragment( + PromptDialogFragment( getString(R.string.remove_dialog_title), getString(R.string.remove_dialog_desc), getString(R.string.remove), - onDeleteClick = { + onOkClick = { viewModel.removeRecent(pdfDocument.filePath) }).show(parentFragmentManager, "removeRecent") dismiss() diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/DeleteDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PromptDialogFragment.kt similarity index 92% rename from app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/DeleteDialogFragment.kt rename to app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PromptDialogFragment.kt index 7dbc7a0..8b756ad 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/DeleteDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/PromptDialogFragment.kt @@ -11,11 +11,12 @@ import androidx.fragment.app.DialogFragment import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.DialogDeleteBinding -class DeleteDialogFragment( +class PromptDialogFragment( private val title: String, private val desc: String, private val okBtnText: String? = null, - private val onDeleteClick: () -> Unit + private val onOkClick: () -> Unit, + private val onCancelClick: () -> Unit = {} ) : DialogFragment() { private lateinit var binding: DialogDeleteBinding @@ -54,10 +55,11 @@ class DeleteDialogFragment( private fun setupOnClick() { binding.okBtn.setOnClickListener { - onDeleteClick() + onOkClick() dismiss() } binding.cancelBtn.setOnClickListener { + onCancelClick() dismiss() } } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt index a5be105..3d74372 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/dialog/RenameDialogFragment.kt @@ -23,7 +23,9 @@ import java.io.File class RenameDialogFragment( private val type: RenameType, private val bookmark: BookmarkEntity? = null, - private val onOkClick: (BookmarkEntity) -> Unit= {} + private val name: String? = null, + private val onOkClick: (BookmarkEntity) -> Unit = {}, + private val onOkClickString: (String) -> Unit = {} ) : DialogFragment() { private lateinit var binding: DialogRenameFileBinding @@ -71,6 +73,15 @@ class RenameDialogFragment( dismiss() } } + + RenameType.NAME -> { + name?.let { + initView(it) + setupOnClick(it, it, null) + }?:run { + dismiss() + } + } } } @@ -104,6 +115,14 @@ class RenameDialogFragment( dismiss() } } + + RenameType.NAME -> { + val text = binding.etName.text.toString() + if (validateEnter(text, oldName, filePath)) { + onOkClickString(text) + dismiss() + } + } } } @@ -161,6 +180,17 @@ class RenameDialogFragment( return false } } + + RenameType.NAME -> { + if (name.length > 50) { + binding.tilName.error = getString(R.string.name_too_long) + return false + } + if (name == oldName) { + binding.tilName.error = getString(R.string.name_not_changed) + return false + } + } } // 禁止开头/结尾空格 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 56afd3e..28abf24 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 @@ -5,38 +5,28 @@ 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 -import android.os.CancellationSignal import android.os.ParcelFileDescriptor -import android.print.PageRange import android.print.PrintAttributes -import android.print.PrintDocumentAdapter -import android.print.PrintDocumentInfo import android.print.PrintManager -import android.print.pdf.PrintedPdfDocument import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager import android.webkit.MimeTypeMap -import android.webkit.WebView -import android.webkit.WebViewClient import android.widget.EditText import android.widget.Toast import androidx.core.content.FileProvider -import com.all.pdfreader.pro.app.R -import java.io.File -import java.io.FileOutputStream import androidx.core.graphics.createBitmap import androidx.print.PrintHelper +import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.model.PrintResult 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.PDDocument import com.tom_roush.pdfbox.pdmodel.common.PDPageLabelRange +import java.io.File +import java.io.FileOutputStream import java.io.IOException object AppUtils { 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 8055f31..75727f1 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 @@ -326,7 +326,10 @@ object FileUtils { return sdf.format(Date(this)) } - + fun Long.toUnderscoreDateTime(): String { + val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH) + return sdf.format(Date(this)) + } data class FileInfo( val name: String, val size: Long, val uri: Uri ) 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 9508e60..3abdbfc 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 @@ -2,8 +2,8 @@ package com.all.pdfreader.pro.app.util import android.content.Context import android.graphics.Bitmap -import android.graphics.Canvas import android.os.ParcelFileDescriptor +import android.util.Log import androidx.core.graphics.createBitmap import com.all.pdfreader.pro.app.model.PdfPageItem import com.shockwave.pdfium.PdfiumCore @@ -26,7 +26,8 @@ object PdfUtils { fun clearPdfThumbsCache(context: Context) { val cacheDir = File(context.cacheDir, child) if (cacheDir.exists()) { - cacheDir.listFiles()?.forEach { it.delete() } + val ss = cacheDir.deleteRecursively() // Kotlin 提供的递归删除方法 + Log.d("ocean", "clearPdfThumbsCache->$ss") } } @@ -66,13 +67,10 @@ object PdfUtils { val targetHeight = (height * scale).toInt() val bitmap = createBitmap(thumbWidth, targetHeight, Bitmap.Config.RGB_565) - val canvas = Canvas(bitmap) - canvas.scale(scale, scale) - - pdfiumCore.renderPageBitmap(pdfDocument, bitmap, i, 0, 0, width, height) + pdfiumCore.renderPageBitmap(pdfDocument, bitmap, i, 0, 0, thumbWidth, targetHeight) // 保存为压缩 JPEG - val outFile = File(cacheDir, "page_$i.jpg") + val outFile = File(cacheDir, inputFile.name + "_page_$i.jpg") FileOutputStream(outFile).use { fos -> bitmap.compress(Bitmap.CompressFormat.JPEG, 70, fos) } @@ -89,36 +87,45 @@ object PdfUtils { }.flowOn(Dispatchers.IO) /** - * 获取 PDF 总页数 - */ - fun getPdfPageCount(inputFile: File): Int { - PDDocument.load(inputFile).use { return it.numberOfPages } - } - - /** - * 导出用户勾选的页生成新的 PDF - * @param inputFile 原 PDF 文件 - * @param selectedPages 用户选择的页列表 - * @param outputDir 输出目录(外部存储可访问目录) - * @param outputFileName 输出文件名 + * 导出选中的 PDF 页到一个新的 PDF 文件 + * + * 使用 PDFBox 的 PDDocument 导出原 PDF 中选中的页,支持进度回调。 + * 避免直接使用缩略图生成 PDF,以保持原 PDF 的矢量质量。 + * + * @param inputFile 原 PDF 文件 + * @param selectedPages 选中的页对象列表,每个对象包含 pageIndex + * @param outputDir 输出目录,如果不存在会自动创建 + * @param outputFileName 输出文件名,例如 "selected.pdf" + * @param onProgress 可选回调,当前处理进度 (current 页, total 页) + * @return 新生成的 PDF 文件,失败返回 null */ suspend fun exportSelectedPages( - inputFile: File, selectedPages: List, outputDir: File, outputFileName: String + inputFile: File, + selectedPages: List, + outputDir: File, + outputFileName: String, + onProgress: ((current: Int, total: Int) -> Unit)? = null ): File? = withContext(Dispatchers.IO) { + + // 如果没有选中的页,直接返回 null if (selectedPages.isEmpty()) return@withContext null - - if (!outputDir.exists()) outputDir.mkdirs() // 确保目录存在 - + if (!outputDir.exists()) outputDir.mkdirs() val outputFile = File(outputDir, outputFileName) try { PDDocument.load(inputFile).use { document -> - val newDocument = PDDocument() - selectedPages.sortedBy { it.pageIndex }.forEach { pageItem -> - val page = document.getPage(pageItem.pageIndex) - newDocument.addPage(page) + PDDocument().use { newDocument -> + // 按页索引排序,保证顺序正确 + val sortedPages = selectedPages.sortedBy { it.pageIndex } + val total = sortedPages.size//总数 + sortedPages.forEachIndexed { index, pageItem -> + // 导入原文档的页面到新文档 + newDocument.importPage(document.getPage(pageItem.pageIndex)) + // 回调进度 + onProgress?.invoke(index + 1, total) + } + // 保存新 PDF 文件 + newDocument.save(outputFile) } - newDocument.save(outputFile) - newDocument.close() } outputFile } catch (e: Exception) { @@ -126,4 +133,5 @@ object PdfUtils { null } } + } diff --git a/app/src/main/res/drawable/dr_item_lock_bg.xml b/app/src/main/res/drawable/dr_item_lock_bg.xml index e36a68c..4369dcf 100644 --- a/app/src/main/res/drawable/dr_item_lock_bg.xml +++ b/app/src/main/res/drawable/dr_item_lock_bg.xml @@ -2,6 +2,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/underline_dashed.xml b/app/src/main/res/drawable/underline_dashed.xml new file mode 100644 index 0000000..5af7ab3 --- /dev/null +++ b/app/src/main/res/drawable/underline_dashed.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/activity_pdf_split.xml b/app/src/main/res/layout/activity_pdf_split.xml index 38409b9..1c95400 100644 --- a/app/src/main/res/layout/activity_pdf_split.xml +++ b/app/src/main/res/layout/activity_pdf_split.xml @@ -62,6 +62,25 @@ android:src="@drawable/gou_white" /> + + + + + + + + android:layout_height="match_parent" + android:visibility="visible"> @@ -103,4 +128,43 @@ android:textSize="16sp" /> + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_pdf_split_result.xml b/app/src/main/res/layout/activity_pdf_split_result.xml new file mode 100644 index 0000000..bb11106 --- /dev/null +++ b/app/src/main/res/layout/activity_pdf_split_result.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/adapter_pdf_item.xml b/app/src/main/res/layout/adapter_pdf_item.xml index 2302f89..5e6068d 100644 --- a/app/src/main/res/layout/adapter_pdf_item.xml +++ b/app/src/main/res/layout/adapter_pdf_item.xml @@ -19,11 +19,23 @@ android:layout_height="56dp" android:background="@drawable/dr_item_img_frame"> + + + + + + + android:layout_height="56dp" /> + android:layout_height="match_parent" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/adapter_split_selected_result_item.xml b/app/src/main/res/layout/adapter_split_selected_result_item.xml new file mode 100644 index 0000000..14e084b --- /dev/null +++ b/app/src/main/res/layout/adapter_split_selected_result_item.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 19fc48b..5990f19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,7 @@ Sort by Created Date Path + Path %1$s File Name File Size Ascending @@ -120,8 +121,16 @@ Loading bookmarks, please try again later no files yet Split PDF + Split Merge PDF %1$d Selected Loading Continue Now + Exit Split + Are you sure you want to exit and discard changes? + Discard + Pages: + Congratulations + Your file has been successfully created + Please select at least one page \ No newline at end of file