添加合并pdf功能
This commit is contained in:
parent
e20a8ab016
commit
c04dae1fe3
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeSettings">
|
||||
<configurations>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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转长图
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,21 +158,22 @@ class SplitPdfActivity : BaseActivity() {
|
||||
PdfUtils.clearPdfThumbsCache(this@SplitPdfActivity)
|
||||
}
|
||||
// 删除完成后,开始收集数据
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
var firstPageLoaded = false
|
||||
PdfUtils.splitPdfToPageItemsFlow(this@SplitPdfActivity, file).collect { pageItem ->
|
||||
if (splitList.size <= pageItem.pageIndex) {
|
||||
splitList.add(pageItem)
|
||||
adapter.notifyItemInserted(splitList.size - 1)
|
||||
} else {
|
||||
splitList[pageItem.pageIndex] = pageItem
|
||||
adapter.updateItem(pageItem.pageIndex)
|
||||
}
|
||||
if (!firstPageLoaded) {
|
||||
binding.loadingRoot.root.visibility = View.GONE
|
||||
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)
|
||||
} else {
|
||||
splitList[pageItem.pageIndex] = pageItem
|
||||
adapter.updateItem(pageItem.pageIndex)
|
||||
}
|
||||
if (!firstPageLoaded) {
|
||||
binding.loadingRoot.root.visibility = View.GONE
|
||||
if (!isSelectedViewShow) {
|
||||
binding.selectAllBtn.visibility = View.VISIBLE
|
||||
firstPageLoaded = true
|
||||
}
|
||||
firstPageLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,60 +103,88 @@ 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),
|
||||
"PDFReaderPro/split"
|
||||
).apply { if (!exists()) mkdirs() }
|
||||
|
||||
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 = "${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
|
||||
PdfUtils.exportSelectedPages(
|
||||
inputFile = inputFile,
|
||||
selectedPages = selectedPages,
|
||||
outputDir = outputDir,
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
})?.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
|
||||
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)
|
||||
}
|
||||
|
||||
if (result.isNullOrEmpty()) {
|
||||
showToast(getString(R.string.pdf_loading_failed))
|
||||
finish()
|
||||
}
|
||||
return result ?: arrayListOf()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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,18 +82,27 @@ class PdfAdapter(
|
||||
holder.binding.moreBtn.setOnClickListener {
|
||||
onMoreClick(item)
|
||||
}
|
||||
holder.binding.root.setOnLongClickListener {
|
||||
if (!isMultiSelectMode) {
|
||||
isMultiSelectMode = true
|
||||
item.isSelected = !item.isSelected
|
||||
notifyDataSetChanged()
|
||||
onLongClick(item)
|
||||
if (enableLongClick) {
|
||||
holder.binding.root.setOnLongClickListener {
|
||||
if (!isMultiSelectMode) {
|
||||
isMultiSelectMode = true
|
||||
item.isSelected = !item.isSelected
|
||||
notifyDataSetChanged()
|
||||
onLongClick(item)
|
||||
}
|
||||
true
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -84,15 +84,13 @@ 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)
|
||||
onComplete()
|
||||
binding.noFilesLayout.visibility = View.GONE
|
||||
} else {
|
||||
binding.noFilesLayout.visibility = View.VISIBLE
|
||||
}
|
||||
PdfRepository.getInstance().getFavoriteDocuments().collect { list ->
|
||||
if (list.isNotEmpty()) {
|
||||
adapter.updateData(list)
|
||||
onComplete()
|
||||
binding.noFilesLayout.visibility = View.GONE
|
||||
} else {
|
||||
binding.noFilesLayout.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,17 +93,15 @@ 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()) {
|
||||
adapter.updateData(sortedList)
|
||||
binding.noFilesLayout.visibility = View.GONE
|
||||
} else {
|
||||
binding.noFilesLayout.visibility = View.VISIBLE
|
||||
}
|
||||
logDebug("更新adapter数据,排序方式: ${appStore.documentSortType}")
|
||||
PdfRepository.getInstance().getAllDocuments().collect { list ->
|
||||
val sortedList = sortDocuments(list)
|
||||
if (list.isNotEmpty()) {
|
||||
adapter.updateData(sortedList)
|
||||
binding.noFilesLayout.visibility = View.GONE
|
||||
} else {
|
||||
binding.noFilesLayout.visibility = View.VISIBLE
|
||||
}
|
||||
logDebug("更新adapter数据,排序方式: ${appStore.documentSortType}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,15 +83,13 @@ 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)
|
||||
onComplete()
|
||||
binding.noFilesLayout.visibility = View.GONE
|
||||
} else {
|
||||
binding.noFilesLayout.visibility = View.VISIBLE
|
||||
}
|
||||
PdfRepository.getInstance().getRecentlyOpenedDocuments().collect { list ->
|
||||
if (list.isNotEmpty()) {
|
||||
adapter.updateData(list)
|
||||
onComplete()
|
||||
binding.noFilesLayout.visibility = View.GONE
|
||||
} else {
|
||||
binding.noFilesLayout.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
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)
|
||||
val merger = PDFMergerUtility()
|
||||
inputFiles.forEachIndexed { index, file ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/grey" />
|
||||
<corners android:radius="12dp" />
|
||||
<corners android:radius="24dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#E43521"/>
|
||||
<corners android:radius="12dp"/>
|
||||
<corners android:radius="24dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
|
||||
@ -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>
|
||||
125
app/src/main/res/layout/activity_pdf_merge.xml
Normal file
125
app/src/main/res/layout/activity_pdf_merge.xml
Normal 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>
|
||||
122
app/src/main/res/layout/activity_pdf_picker.xml
Normal file
122
app/src/main/res/layout/activity_pdf_picker.xml
Normal 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>
|
||||
@ -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" />
|
||||
|
||||
|
||||
140
app/src/main/res/layout/adapter_merge_list_item.xml
Normal file
140
app/src/main/res/layout/adapter_merge_list_item.xml
Normal 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>
|
||||
@ -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>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tvFileImg"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp" />
|
||||
<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_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>
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue
Block a user