添加合并pdf功能

This commit is contained in:
ocean 2025-09-30 15:15:40 +08:00
parent e20a8ab016
commit c04dae1fe3
26 changed files with 1106 additions and 157 deletions

1
.idea/misc.xml generated
View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSettings">
<configurations>

View File

@ -85,6 +85,18 @@
android:label="@string/app_name"
android:screenOrientation="portrait" />
<activity
android:name=".ui.act.PdfPickerActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait" />
<activity
android:name=".ui.act.MergePdfActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"

View File

@ -0,0 +1,10 @@
package com.all.pdfreader.pro.app.model
enum class PdfPickerSource {
NONE, // 没有任何
MERGE, // PDF合并
SPLIT, // PDF拆分
LOCK, // PDF加密/解密
TO_IMAGES, // PDF转图片
TO_LONG_IMAGE // PDF转长图
}

View File

@ -227,16 +227,17 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
}
}
binding.multiSelectMergeBtn.setOnSingleClickListener {
logDebug("合并")
val selectedItems = recentlyFragment.adapter.getSelectedItems()
// if (selectedItems.isNotEmpty()) {
// val inputFile = selectedItems.map { File(it.filePath) }
// val outputDir = File(
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
// "PDFReaderPro/merge"
// ).apply { if (!exists()) mkdirs() }
// PdfUtils.mergePdfFiles(inputFiles = inputFile, outputDir =outputDir, onProgress = {} )
// }
val selectedItems = when (activeFragment) {
is HomeFrag -> (activeFragment as HomeFrag).adapter.getSelectedItems()
is FavoriteFrag -> (activeFragment as FavoriteFrag).adapter.getSelectedItems()
is RecentlyFrag -> (activeFragment as RecentlyFrag).adapter.getSelectedItems()
else -> emptyList()
}
if (selectedItems.isNotEmpty()) {
val intent = MergePdfActivity.createIntent(this, ArrayList(selectedItems))
startActivity(intent)
exitAllMultiSelect()
}
}
binding.multiSelectRemoveBtn.setOnSingleClickListener {
val selectedItems = recentlyFragment.adapter.getSelectedItems()

View File

@ -0,0 +1,171 @@
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.Parcelable
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContract
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfMergeBinding
import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener
import com.gyf.immersionbar.ImmersionBar
class MergePdfActivity : BaseActivity() {
override val TAG: String = "MergePdfActivity"
private lateinit var binding: ActivityPdfMergeBinding
private lateinit var selectedList: ArrayList<PdfDocumentEntity>
private lateinit var adapter: PdfAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPdfMergeBinding.inflate(layoutInflater)
setContentView(binding.root)
setupBackPressedCallback()
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.bg_color).init()
selectedList = requireParcelableArrayList(EXTRA_PDF_LIST)
updateContinueNowBtnState(selectedList.size >= 2)
initView()
setupClick()
}
private fun initView() {
adapter = PdfAdapter(
pdfList = selectedList,
onDeleteItemClick = { item, position ->
adapter.removeItem(position)
selectedList.remove(item)
updateContinueNowBtnState(selectedList.size >= 2)
},
enableLongClick = false,
showMoreButton = false,
showCheckButton = false,
isMultiSelectMode = false,
showDeleteButton = true
)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
}
private fun setupClick() {
binding.backBtn.setOnSingleClickListener { onBackPressedDispatcher.onBackPressed() }
binding.addBtn.setClickWithAnimation {
openPicker()
}
binding.continueNowBtn.setOnSingleClickListener {
val list = selectedList.map { it.filePath }
val intent = SplitPdfResultActivity.createIntentInputFile(
this, ArrayList(list), PdfPickerSource.MERGE
)
startActivity(intent)
finish()
}
}
private val pickPdfLauncher =
registerForActivityResult(PickPdfContract(Companion.TAG)) { list ->
if (list.isNotEmpty()) {
handleSelectedPdfs(list)
}
}
private fun openPicker() {
pickPdfLauncher.launch(PdfPickerSource.MERGE to selectedList)
}
private fun handleSelectedPdfs(list: List<PdfDocumentEntity>) {
selectedList.clear()
selectedList.addAll(list)
adapter.notifyDataSetChanged()
updateContinueNowBtnState(selectedList.size >= 2)
}
private fun updateContinueNowBtnState(b: Boolean) {
binding.continueNowBtn.setBackgroundResource(
if (b) R.drawable.dr_click_btn_bg
else R.drawable.dr_btn_not_clickable_bg
)
binding.continueNowBtn.isEnabled = b
}
private fun setupBackPressedCallback() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
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))
}
})
}
companion object {
const val EXTRA_SELECTED_LIST = "extra_selected_list"
const val EXTRA_PDF_LIST = "extra_pdf_list"
const val TAG = "MergePdfActivity"
fun createIntent(context: Context, list: ArrayList<PdfDocumentEntity>): Intent {
return Intent(context, MergePdfActivity::class.java).apply {
putParcelableArrayListExtra(EXTRA_PDF_LIST, list)
}
}
}
/**
* 通用方法读取必传参数如果为 null 直接 finish
*/
@Suppress("DEPRECATION")
private inline fun <reified T : Parcelable> requireParcelableArrayList(key: String): ArrayList<T> {
val result: ArrayList<T>? = 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()
}
class PickPdfContract(
private val from: String
) : ActivityResultContract<Pair<PdfPickerSource, ArrayList<PdfDocumentEntity>>, List<PdfDocumentEntity>>() {
override fun createIntent(
context: Context, input: Pair<PdfPickerSource, ArrayList<PdfDocumentEntity>>
): Intent {
val (source, historyList) = input
return PdfPickerActivity.createIntent(context, source, from, historyList)
}
override fun parseResult(resultCode: Int, intent: Intent?): List<PdfDocumentEntity> {
if (resultCode != RESULT_OK || intent == null) return emptyList()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableArrayListExtra(
EXTRA_SELECTED_LIST, PdfDocumentEntity::class.java
) ?: emptyList()
} else {
@Suppress("DEPRECATION") intent.getParcelableArrayListExtra<PdfDocumentEntity>(
EXTRA_SELECTED_LIST
) ?: emptyList()
}
}
}
}

View File

@ -0,0 +1,218 @@
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.Parcelable
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfPickerBinding
import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.launch
import java.io.Serializable
class PdfPickerActivity : BaseActivity() {
override val TAG: String = "PdfPickerActivity"
private lateinit var binding: ActivityPdfPickerBinding
private lateinit var adapter: PdfAdapter
private lateinit var source: PdfPickerSource // 保存来源
private var showCheckButton = false
private var isMultiSelectMode = false
private var fromActivityResult: String = ""
private lateinit var historyList: ArrayList<PdfDocumentEntity>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPdfPickerBinding.inflate(layoutInflater)
setContentView(binding.root)
source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE)
if (source == PdfPickerSource.NONE) {
showToast(getString(R.string.unknown_source))
finish()
}
fromActivityResult = intent.getStringExtra(EXTRA_FROM) ?: ""
historyList = requireParcelableArrayList(EXTRA_HISTORY_LIST)
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.bg_color).init()
updateViewAndState()
initView()
setupClick()
initData()
}
private fun updateViewAndState(number: Int = 0) {
if (source == PdfPickerSource.MERGE) {
binding.title.text = getString(R.string.selected_page, number)
binding.continueNowBtn.visibility = View.VISIBLE
updateContinueNowBtnState(number >= 2)
} else {
binding.title.text = getString(R.string.please_select_a_file)
binding.continueNowBtn.visibility = View.GONE
}
}
private fun initView() {
if (source == PdfPickerSource.MERGE) {
showCheckButton = true
isMultiSelectMode = true
} else {
showCheckButton = false
isMultiSelectMode = false
}
adapter = PdfAdapter(
pdfList = mutableListOf(),
onItemClick = { pdf ->
when (source) {
PdfPickerSource.MERGE -> {}
PdfPickerSource.SPLIT -> {
val intent = SplitPdfActivity.createIntent(this, pdf.filePath)
startActivity(intent)
finish()
}
PdfPickerSource.LOCK -> {}
PdfPickerSource.TO_IMAGES -> {}
PdfPickerSource.TO_LONG_IMAGE -> {}
PdfPickerSource.NONE -> {}
}
},
onSelectModelItemClick = {
if (source == PdfPickerSource.MERGE) {
val selectedItems = adapter.getSelectedItems()
if (selectedItems.isNotEmpty()) {
val filePaths = selectedItems.map { it.filePath }
logDebug("${filePaths.size}")
}
updateViewAndState(selectedItems.size)
}
},
enableLongClick = false,
showMoreButton = false,
showCheckButton = showCheckButton,
isMultiSelectMode = isMultiSelectMode
)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
}
private fun initData() {
lifecycleScope.launch {
PdfRepository.getInstance().getAllDocuments().collect { list ->
val sortedList = sortDocuments(list)
// 标记已选择项
if (fromActivityResult == MergePdfActivity.TAG) {
markSelectedItems(sortedList, historyList)
}
if (list.isNotEmpty()) {
adapter.updateData(sortedList)
binding.noFilesLayout.visibility = View.GONE
} else {
binding.noFilesLayout.visibility = View.VISIBLE
}
}
}
}
private fun markSelectedItems(
sortedList: List<PdfDocumentEntity>, historyList: List<PdfDocumentEntity>
) {
val historySet = historyList.map { it.filePath }.toSet()
sortedList.forEach { item ->
item.isSelected = item.filePath in historySet
}
}
private fun setupClick() {
binding.backBtn.setOnSingleClickListener { finish() }
binding.searchBtn.setClickWithAnimation {
}
binding.continueNowBtn.setOnSingleClickListener {
val selectedItems = adapter.getSelectedItems()
if (selectedItems.size >= 2) {
if (fromActivityResult == MergePdfActivity.TAG) {
returnSelectedResult(ArrayList(selectedItems))
} else {
val intent = MergePdfActivity.createIntent(this, ArrayList(selectedItems))
startActivity(intent)
finish()
}
}
}
}
private fun returnSelectedResult(selectedList: ArrayList<PdfDocumentEntity>) {
val intent = Intent().apply {
putParcelableArrayListExtra(MergePdfActivity.EXTRA_SELECTED_LIST, selectedList)
}
setResult(RESULT_OK, intent)
finish()
}
private fun updateContinueNowBtnState(b: Boolean) {
binding.continueNowBtn.setBackgroundResource(
if (b) R.drawable.dr_click_btn_bg
else R.drawable.dr_btn_not_clickable_bg
)
binding.continueNowBtn.isEnabled = b
}
private fun sortDocuments(documents: List<PdfDocumentEntity>): List<PdfDocumentEntity> {
val sortConfig = SortConfig.fromPreferenceString(appStore.documentSortType)
return sortConfig.applySort(documents)
}
companion object {
private const val EXTRA_SOURCE = "extra_source"
private const val EXTRA_FROM = "extra_from"//从哪儿来
private const val EXTRA_HISTORY_LIST = "extra_history_list"//已经有选中的数据
fun createIntent(
context: Context,
source: PdfPickerSource,
from: String = "",
list: ArrayList<PdfDocumentEntity> = arrayListOf()
): Intent {
return Intent(context, PdfPickerActivity::class.java).apply {
putExtra(EXTRA_SOURCE, source)
putExtra(EXTRA_FROM, from)
putParcelableArrayListExtra(EXTRA_HISTORY_LIST, list)
}
}
}
@Suppress("DEPRECATION")
private inline fun <reified T : Parcelable> requireParcelableArrayList(key: String): ArrayList<T> {
val result: ArrayList<T>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableArrayListExtra(key, T::class.java)
} else {
intent.getParcelableArrayListExtra(key)
}
return result ?: arrayListOf()
}
private inline fun <reified T : Serializable> getSerializableOrDefault(
key: String, default: T
): T {
val result: T? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra(key, T::class.java)
} else {
@Suppress("DEPRECATION") intent.getSerializableExtra(key) as? T
}
return result ?: default
}
}

View File

@ -215,14 +215,13 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
override fun onError(t: Throwable?) {
logDebug("PDF loading error: ${t?.message}")
t?.let {
val errorMessage = it.message ?: "未知错误"
val errorMessage = it.message ?: getString(R.string.pdf_loading_failed)
// 检查是否是密码相关的错误
if (errorMessage.contains("Password") || errorMessage.contains("password")) {
val file = File(pdfDocument.filePath)
showPasswordDialog(file)
} else {
// 其他错误
showToast(getString(R.string.pdf_loading_failed))
showToast(errorMessage)
finish()
}
}

View File

@ -13,6 +13,7 @@ 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.PdfPickerSource
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
@ -121,7 +122,8 @@ class SplitPdfActivity : BaseActivity() {
}
binding.continueNowBtn.setOnSingleClickListener {
val selectedPages = splitList.filter { it.isSelected }.map { it.copy() }
val name = getString(R.string.split) + "_" + System.currentTimeMillis().toUnderscoreDateTime()
val name =
getString(R.string.split) + "_" + System.currentTimeMillis().toUnderscoreDateTime()
val item = PdfSelectedPagesItem(filePath, name, selectedPages)
selectedList.add(item)
selectedPdfAdapter.updateAdapter()
@ -136,8 +138,13 @@ class SplitPdfActivity : BaseActivity() {
updateViewState(false)
}
binding.splitBtn.setOnSingleClickListener {
logDebug("${selectedList.size}")
//因为图片做的路径缓存方式所以这里直接传入整个集合到result页处理
val intent = SplitPdfResultActivity.createIntent(this, ArrayList(selectedList))
val intent = SplitPdfResultActivity.createIntentPdfSelectedPagesItem(
this,
ArrayList(selectedList),
PdfPickerSource.SPLIT
)
startActivity(intent)
finish()
}
@ -151,9 +158,9 @@ class SplitPdfActivity : BaseActivity() {
PdfUtils.clearPdfThumbsCache(this@SplitPdfActivity)
}
// 删除完成后,开始收集数据
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
var firstPageLoaded = false
PdfUtils.splitPdfToPageItemsFlow(this@SplitPdfActivity, file).collect { pageItem ->
logDebug("splitPdfToPageItemsFlow pageItem->$pageItem")
if (splitList.size <= pageItem.pageIndex) {
splitList.add(pageItem)
adapter.notifyItemInserted(splitList.size - 1)
@ -163,9 +170,10 @@ class SplitPdfActivity : BaseActivity() {
}
if (!firstPageLoaded) {
binding.loadingRoot.root.visibility = View.GONE
if (!isSelectedViewShow) {
binding.selectAllBtn.visibility = View.VISIBLE
firstPageLoaded = true
}
firstPageLoaded = true
}
}
}

View File

@ -1,5 +1,6 @@
package com.all.pdfreader.pro.app.ui.act
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
@ -8,20 +9,17 @@ 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
import com.all.pdfreader.pro.app.model.PdfPickerSource
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.FileUtils.toUnderscoreDateTime
import com.all.pdfreader.pro.app.util.PdfScanner
import com.all.pdfreader.pro.app.util.PdfUtils
import com.gyf.immersionbar.ImmersionBar
@ -29,26 +27,41 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.Serializable
class SplitPdfResultActivity : BaseActivity() {
override val TAG: String = "SplitPdfResultActivity"
companion object {
private const val EXTRA_SELECTED_LIST = "extra_selected_list"
private const val EXTRA_FILE_LIST = "extra_file_list"
private const val EXTRA_SOURCE = "extra_source"
fun createIntent(
context: Context, list: ArrayList<PdfSelectedPagesItem>
fun createIntentPdfSelectedPagesItem(
context: Context, list: ArrayList<PdfSelectedPagesItem>, source: PdfPickerSource
): Intent {
return Intent(context, SplitPdfResultActivity::class.java).apply {
putParcelableArrayListExtra(EXTRA_SELECTED_LIST, list)
putExtra(EXTRA_SOURCE, source)
}
}
fun createIntentInputFile(
context: Context, list: ArrayList<String>, source: PdfPickerSource
): Intent {
return Intent(context, SplitPdfResultActivity::class.java).apply {
putStringArrayListExtra(EXTRA_FILE_LIST, list)
putExtra(EXTRA_SOURCE, source)
}
}
}
private lateinit var binding: ActivityPdfSplitResultBinding
private lateinit var adapter: SplitPdfResultAdapter
private var splitResultList: MutableList<PdfSplitResultItem> = mutableListOf()
private var resultList: MutableList<PdfSplitResultItem> = mutableListOf()
private lateinit var selectedList: ArrayList<PdfSelectedPagesItem>
private lateinit var inputFile: ArrayList<String>
private lateinit var source: PdfPickerSource
private var isSplitting = false
private var exitDialog: PromptDialogFragment? = null
private val pdfRepository = getRepository()
@ -62,6 +75,27 @@ class SplitPdfResultActivity : BaseActivity() {
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.bg_color).init()
selectedList = requireParcelableArrayList(EXTRA_SELECTED_LIST)
inputFile = requireStringArrayList(EXTRA_FILE_LIST)
source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE)
if (source == PdfPickerSource.NONE) {
showToast(getString(R.string.pdf_loading_failed))
finish()
return
} else {
if (source == PdfPickerSource.SPLIT) {
if (selectedList.isEmpty()) {
showToast(getString(R.string.pdf_loading_failed))
finish()
return
}
} else if (source == PdfPickerSource.MERGE) {
if (inputFile.isEmpty()) {
showToast(getString(R.string.pdf_loading_failed))
finish()
return
}
}
}
pdfScanner = PdfScanner(this, pdfRepository)
initView()
setupClick()
@ -69,28 +103,26 @@ class SplitPdfResultActivity : BaseActivity() {
}
private fun initView() {
adapter = SplitPdfResultAdapter(splitResultList)
adapter = SplitPdfResultAdapter(resultList)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
}
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.processingLayout.visibility = View.VISIBLE
binding.progressBar.isIndeterminate = false
binding.progressBar.progress = 0
binding.progressBar.max = 100
}
if (source == PdfPickerSource.SPLIT) {
val totalPages = selectedList.sumOf { it.pages.count { it.isSelected } }
var processedPages = 0
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),
@ -114,15 +146,45 @@ class SplitPdfResultActivity : BaseActivity() {
AppUtils.generateFastThumbnail(this@SplitPdfResultActivity, resultFile)
val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false)
pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) {
splitResultList.add(result)
resultList.add(result)
}
}
}
} else if (source == PdfPickerSource.MERGE) {
if (inputFile.isNotEmpty()) {
val inputFiles: List<File> = inputFile.map { path -> File(path) }
val outputDir = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
"PDFReaderPro/merge"
).apply { if (!exists()) mkdirs() }
val outputFileName =
getString(R.string.merge) + "_" + System.currentTimeMillis()
.toUnderscoreDateTime() + ".pdf"
PdfUtils.mergePdfFilesSafe(
inputFiles = inputFiles,
outputDir = outputDir,
outputFileName = outputFileName,
onProgress = { current, total ->
val progressPercent = current * 100 / total
lifecycleScope.launch(Dispatchers.Main) {
binding.progressBar.progress = progressPercent
binding.progressTv.text = "$progressPercent"
}
})?.let { resultFile ->
val thumbnails =
AppUtils.generateFastThumbnail(this@SplitPdfResultActivity, resultFile)
val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false)
pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) {
resultList.add(result)
}
}
}
}
withContext(Dispatchers.Main) {
binding.splittingLayout.visibility = View.GONE
binding.processingLayout.visibility = View.GONE
// 默认选中第一个
if (splitResultList.isNotEmpty() && splitResultList.none { it.isSelected }) {
splitResultList[0].isSelected = true
if (resultList.isNotEmpty() && resultList.none { it.isSelected }) {
resultList[0].isSelected = true
}
adapter.updateAdapter()
isSplitting = false//拆分结束
@ -177,9 +239,6 @@ class SplitPdfResultActivity : BaseActivity() {
})
}
/**
* 通用方法读取必传参数如果为 null 直接 finish
*/
@Suppress("DEPRECATION")
private inline fun <reified T : Parcelable> requireParcelableArrayList(key: String): ArrayList<T> {
val result: ArrayList<T>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@ -187,11 +246,28 @@ class SplitPdfResultActivity : BaseActivity() {
} else {
intent.getParcelableArrayListExtra(key)
}
return result ?: arrayListOf()
}
if (result.isNullOrEmpty()) {
showToast(getString(R.string.pdf_loading_failed))
finish()
private fun requireStringArrayList(key: String): ArrayList<String> {
val result: ArrayList<String>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getStringArrayListExtra(key)
} else {
@Suppress("DEPRECATION") intent.getStringArrayListExtra(key)
}
return result ?: arrayListOf()
}
private inline fun <reified T : Serializable> getSerializableOrDefault(
key: String, default: T
): T {
val result: T? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra(key, T::class.java)
} else {
@Suppress("DEPRECATION") intent.getSerializableExtra(key) as? T
}
return result ?: default
}
}

View File

@ -17,12 +17,17 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
class PdfAdapter(
private var pdfList: MutableList<PdfDocumentEntity>,
private val onItemClick: (PdfDocumentEntity) -> Unit,
private val onMoreClick: (PdfDocumentEntity) -> Unit,
private val onLongClick: (PdfDocumentEntity) -> Unit,
private val onSelectModelItemClick: (Int) -> Unit = {}
private val onItemClick: (PdfDocumentEntity) -> Unit = {},
private val onMoreClick: (PdfDocumentEntity) -> Unit = {},
private val onLongClick: (PdfDocumentEntity) -> Unit = {},
private val onSelectModelItemClick: (Int) -> Unit = {},
private val onDeleteItemClick: (PdfDocumentEntity, Int) -> Unit = { _, _ -> },
private var enableLongClick: Boolean = true,
private var showMoreButton: Boolean = true, // 是否显示更多按钮
private var showCheckButton: Boolean = false, // 是否显示选择框按钮
private var isMultiSelectMode: Boolean = false, // 是否进入多选模式
private var showDeleteButton: Boolean = false // 是否显示删除按钮
) : RecyclerView.Adapter<PdfAdapter.PdfViewHolder>() {
private var isMultiSelectMode = false
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
RecyclerView.ViewHolder(binding.root)
@ -57,13 +62,14 @@ class PdfAdapter(
if (isMultiSelectMode) {
holder.binding.checkBtn.visibility = View.VISIBLE
holder.binding.moreBtn.visibility = View.GONE
holder.binding.deleteBtn.visibility = View.GONE
} else {
holder.binding.checkBtn.visibility = View.GONE
holder.binding.moreBtn.visibility = View.VISIBLE
holder.binding.checkBtn.visibility = if (showCheckButton) View.VISIBLE else View.GONE
holder.binding.moreBtn.visibility = if (showMoreButton) View.VISIBLE else View.GONE
holder.binding.deleteBtn.visibility = if (showDeleteButton) View.VISIBLE else View.GONE
}
holder.binding.checkbox.isChecked = item.isSelected
holder.binding.root.setOnClickListener {
if (isMultiSelectMode) {
item.isSelected = !item.isSelected
@ -76,6 +82,7 @@ class PdfAdapter(
holder.binding.moreBtn.setOnClickListener {
onMoreClick(item)
}
if (enableLongClick) {
holder.binding.root.setOnLongClickListener {
if (!isMultiSelectMode) {
isMultiSelectMode = true
@ -85,9 +92,17 @@ class PdfAdapter(
}
true
}
} else {
holder.binding.root.setOnLongClickListener(null) // 禁用长按
}
holder.binding.checkBtn.setOnClickListener {
toggleSelection(item, holder.bindingAdapterPosition)
}
holder.binding.deleteBtn.setOnClickListener {
onDeleteItemClick(item, holder.bindingAdapterPosition)
}
}
override fun getItemCount(): Int = pdfList.size
@ -104,6 +119,11 @@ class PdfAdapter(
notifyItemChanged(position)
}
fun removeItem(position: Int) {
pdfList.removeAt(position)
notifyItemRemoved(position)
}
// 退出多选模式
@SuppressLint("NotifyDataSetChanged")
fun exitMultiSelectMode() {
@ -138,7 +158,7 @@ class PdfAdapter(
return pdfList.filter { it.isSelected }
}
fun getIsMultiSelectMode(): Boolean{
fun getIsMultiSelectMode(): Boolean {
return isMultiSelectMode
}
}

View File

@ -20,7 +20,9 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
class SearchPdfAdapter(
private val onItemClick: (PdfDocumentEntity) -> Unit,
private val onMoreClick: (PdfDocumentEntity) -> Unit
private val onMoreClick: (PdfDocumentEntity) -> Unit,
private var showMoreButton: Boolean = true, // 是否显示更多按钮
private var showCheckButton: Boolean = false // 是否显示选择框按钮
) : ListAdapter<PdfDocumentEntity, SearchPdfAdapter.PdfViewHolder>(PdfDiffCallback()) {
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
@ -50,6 +52,9 @@ class SearchPdfAdapter(
private fun bindItem(holder: PdfViewHolder, item: PdfDocumentEntity, highlightKeyword: String?) {
val context = holder.binding.root.context
holder.binding.checkBtn.visibility = if (showCheckButton) View.VISIBLE else View.GONE
holder.binding.moreBtn.visibility = if (showMoreButton) View.VISIBLE else View.GONE
// 文件名高亮,如果 highlightKeyword 为 null 或空字符串,就显示普通文本
holder.binding.tvFileName.text = if (highlightKeyword.isNullOrBlank()) {
item.fileName

View File

@ -84,7 +84,6 @@ class FavoriteFrag : BaseFrag() {
private fun observeDocuments(onComplete: () -> Unit = {}) {
lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
PdfRepository.getInstance().getFavoriteDocuments().collect { list ->
if (list.isNotEmpty()) {
adapter.updateData(list)
@ -96,7 +95,6 @@ class FavoriteFrag : BaseFrag() {
}
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)

View File

@ -93,7 +93,6 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
private fun observeDocuments() {
lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
PdfRepository.getInstance().getAllDocuments().collect { list ->
val sortedList = sortDocuments(list)
if (list.isNotEmpty()) {
@ -106,7 +105,6 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
}
}
}
}
private fun sortDocuments(documents: List<PdfDocumentEntity>): List<PdfDocumentEntity> {
val sortConfig = SortConfig.fromPreferenceString(appStore.documentSortType)

View File

@ -83,7 +83,6 @@ class RecentlyFrag : BaseFrag() {
private fun observeDocuments(onComplete: () -> Unit = {}) {
lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
PdfRepository.getInstance().getRecentlyOpenedDocuments().collect { list ->
if (list.isNotEmpty()) {
adapter.updateData(list)
@ -95,7 +94,6 @@ class RecentlyFrag : BaseFrag() {
}
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)

View File

@ -6,6 +6,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.all.pdfreader.pro.app.databinding.FragmentToolsBinding
import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.ui.act.PdfPickerActivity
class ToolsFrag : Fragment() {
private lateinit var binding: FragmentToolsBinding
@ -23,6 +25,13 @@ class ToolsFrag : Fragment() {
}
private fun initView() {
binding.mergeBtn.setOnClickListener {
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.MERGE)
startActivity(intent)
}
binding.splitBtn.setOnClickListener {
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.SPLIT)
startActivity(intent)
}
}
}

View File

@ -7,6 +7,8 @@ import android.util.Log
import androidx.core.graphics.createBitmap
import com.all.pdfreader.pro.app.model.PdfPageItem
import com.shockwave.pdfium.PdfiumCore
import com.tom_roush.pdfbox.io.MemoryUsageSetting
import com.tom_roush.pdfbox.multipdf.PDFMergerUtility
import com.tom_roush.pdfbox.pdmodel.PDDocument
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -138,8 +140,7 @@ object PdfUtils {
/**
* 合并多个 PDF 文件到一个新的 PDF 文件
*
* 使用 PDFBox PDDocument 合并多个 PDF支持进度回调
* 避免直接生成缩略图以保持原 PDF 的矢量质量
* 使用 PDFBox PDFMergerUtility 合并 PDF支持进度回调
*
* @param inputFiles 要合并的 PDF 文件列表
* @param outputDir 输出目录如果不存在会自动创建
@ -147,7 +148,18 @@ object PdfUtils {
* @param onProgress 可选回调当前处理进度 (current 文件, total 文件)
* @return 新生成的 PDF 文件失败返回 null
*/
suspend fun mergePdfFiles(
/**
* 合并多个 PDF 文件到一个新的 PDF 文件
*
* 使用 PDFBox PDFMergerUtility 合并 PDF支持进度回调
*
* @param inputFiles 要合并的 PDF 文件列表
* @param outputDir 输出目录如果不存在会自动创建
* @param outputFileName 输出文件名例如 "merged.pdf"
* @param onProgress 可选回调当前处理进度 (current 文件, total 文件)
* @return 新生成的 PDF 文件失败返回 null
*/
suspend fun mergePdfFilesSafe(
inputFiles: List<File>,
outputDir: File,
outputFileName: String,
@ -156,28 +168,25 @@ object PdfUtils {
if (inputFiles.isEmpty()) return@withContext null
if (!outputDir.exists()) outputDir.mkdirs()
val outputFile = File(outputDir, outputFileName)
try {
PDDocument().use { mergedDocument ->
val totalFiles = inputFiles.size
val merger = PDFMergerUtility()
inputFiles.forEachIndexed { index, file ->
PDDocument.load(file).use { doc ->
for (page in doc.pages) {
mergedDocument.addPage(page)
}
}
// 回调进度:按文件计数,也可以按总页数进一步精细化
onProgress?.invoke(index + 1, totalFiles)
delay(1) // 给 UI 更新留点时间
}
mergedDocument.save(outputFile)
merger.addSource(file)
onProgress?.invoke(index + 1, inputFiles.size)
delay(100)
}
merger.destinationFileName = outputFile.absolutePath
merger.mergeDocuments(MemoryUsageSetting.setupTempFileOnly())
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}

View File

@ -6,7 +6,7 @@
<item>
<shape android:shape="rectangle">
<solid android:color="@color/grey" />
<corners android:radius="12dp" />
<corners android:radius="24dp" />
</shape>
</item>

View File

@ -6,7 +6,7 @@
<item>
<shape android:shape="rectangle">
<solid android:color="#E43521"/>
<corners android:radius="12dp"/>
<corners android:radius="24dp"/>
</shape>
</item>

View File

@ -2,6 +2,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:color="@color/line_color"
android:width="1dp"/>
android:width="0.5dp"/>
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_color"
android:orientation="vertical">
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/backBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/back_black" />
</LinearLayout>
<TextView
android:id="@+id/title"
style="@style/TextViewFont_PopMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/merge_pdf"
android:textColor="@color/black"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/addBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
android:id="@+id/addIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/dr_circular_sel_on_bg"
android:padding="2dp"
android:src="@drawable/add_icon_white" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/noFilesLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/img_no_files_yet" />
<TextView
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_files_yet"
android:textColor="#B6BFCC"
android:textSize="20sp" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/continue_now_btn"
android:overScrollMode="never"
android:scrollbars="none" />
<LinearLayout
android:id="@+id/continue_now_btn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/dr_btn_not_clickable_bg"
android:gravity="center">
<TextView
style="@style/TextViewFont_PopSemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/continue_now"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_color"
android:orientation="vertical">
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/backBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/back_black" />
</LinearLayout>
<TextView
android:id="@+id/title"
style="@style/TextViewFont_PopMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/searchBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/search" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/noFilesLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/img_no_files_yet" />
<TextView
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_files_yet"
android:textColor="#B6BFCC"
android:textSize="20sp" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/continue_now_btn"
android:overScrollMode="never"
android:scrollbars="none" />
<LinearLayout
android:id="@+id/continue_now_btn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/dr_btn_not_clickable_bg"
android:gravity="center">
<TextView
style="@style/TextViewFont_PopSemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/continue_now"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -12,7 +12,7 @@
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/splittingLayout"
android:id="@+id/processingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
@ -48,7 +48,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/splitting"
android:text="@string/processing"
android:textColor="@color/black"
android:textSize="18sp" />

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingTop="8dp"
tools:ignore="RtlSymmetry">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/dr_item_img_frame">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/dr_placeholder_bg"
android:gravity="center">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/file_document" />
</LinearLayout>
<ImageView
android:id="@+id/tvFileImg"
android:layout_width="56dp"
android:layout_height="56dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:id="@+id/editTitleBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:id="@+id/tvFileName"
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/icon_color"
android:textSize="16sp" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="4dp"
android:src="@drawable/edit_icon" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="2dp"
android:background="@drawable/underline_dashed" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
style="@style/TextViewFont_PopRegular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/pages"
android:textColor="@color/black_60"
android:textSize="14sp" />
<TextView
android:id="@+id/tvPages"
style="@style/TextViewFont_PopRegular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="0"
android:textColor="@color/black_60"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/deleteBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/delete_cha_icon" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@color/line_color" />
</LinearLayout>

View File

@ -22,8 +22,7 @@
<RelativeLayout
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/dr_item_img_frame">
android:layout_height="56dp">
<LinearLayout
android:layout_width="match_parent"
@ -38,10 +37,17 @@
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/dr_item_img_frame">
<ImageView
android:id="@+id/tvFileImg"
android:layout_width="56dp"
android:layout_height="56dp" />
android:layout_height="56dp"
android:layout_margin="0.5dp" />
</RelativeLayout>
<ImageView
android:id="@+id/collectState"
@ -73,6 +79,7 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
@ -130,6 +137,7 @@
android:layout_height="24dp"
android:src="@drawable/more" />
</LinearLayout>
<LinearLayout
android:id="@+id/checkBtn"
android:layout_width="48dp"
@ -147,6 +155,19 @@
android:focusable="false" />
</LinearLayout>
<LinearLayout
android:id="@+id/deleteBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/delete_cha_icon" />
</LinearLayout>
</LinearLayout>

View File

@ -9,9 +9,17 @@
android:layout_width="match_parent"
android:layout_height="0dp" />
<TextView
android:layout_width="wrap_content"
<Button
android:id="@+id/mergeBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tools" />
android:text="@string/merge_pdf" />
<Button
android:id="@+id/splitBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/split_pdf" />
</LinearLayout>

View File

@ -150,4 +150,6 @@
<string name="tip">Tip</string>
<string name="clear_history_desc">Are you sure you want to clear history?</string>
<string name="clear">Clear</string>
<string name="please_select_a_file">Please select a file</string>
<string name="unknown_source">Unknown Source</string>
</resources>