添加合并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">
|
<project version="4">
|
||||||
<component name="CMakeSettings">
|
<component name="CMakeSettings">
|
||||||
<configurations>
|
<configurations>
|
||||||
|
|||||||
@ -85,6 +85,18 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="portrait" />
|
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
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.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 {
|
binding.multiSelectMergeBtn.setOnSingleClickListener {
|
||||||
logDebug("合并")
|
val selectedItems = when (activeFragment) {
|
||||||
val selectedItems = recentlyFragment.adapter.getSelectedItems()
|
is HomeFrag -> (activeFragment as HomeFrag).adapter.getSelectedItems()
|
||||||
// if (selectedItems.isNotEmpty()) {
|
is FavoriteFrag -> (activeFragment as FavoriteFrag).adapter.getSelectedItems()
|
||||||
// val inputFile = selectedItems.map { File(it.filePath) }
|
is RecentlyFrag -> (activeFragment as RecentlyFrag).adapter.getSelectedItems()
|
||||||
// val outputDir = File(
|
else -> emptyList()
|
||||||
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
|
}
|
||||||
// "PDFReaderPro/merge"
|
if (selectedItems.isNotEmpty()) {
|
||||||
// ).apply { if (!exists()) mkdirs() }
|
val intent = MergePdfActivity.createIntent(this, ArrayList(selectedItems))
|
||||||
// PdfUtils.mergePdfFiles(inputFiles = inputFile, outputDir =outputDir, onProgress = {} )
|
startActivity(intent)
|
||||||
// }
|
exitAllMultiSelect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
binding.multiSelectRemoveBtn.setOnSingleClickListener {
|
binding.multiSelectRemoveBtn.setOnSingleClickListener {
|
||||||
val selectedItems = recentlyFragment.adapter.getSelectedItems()
|
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?) {
|
override fun onError(t: Throwable?) {
|
||||||
logDebug("PDF loading error: ${t?.message}")
|
logDebug("PDF loading error: ${t?.message}")
|
||||||
t?.let {
|
t?.let {
|
||||||
val errorMessage = it.message ?: "未知错误"
|
val errorMessage = it.message ?: getString(R.string.pdf_loading_failed)
|
||||||
// 检查是否是密码相关的错误
|
// 检查是否是密码相关的错误
|
||||||
if (errorMessage.contains("Password") || errorMessage.contains("password")) {
|
if (errorMessage.contains("Password") || errorMessage.contains("password")) {
|
||||||
val file = File(pdfDocument.filePath)
|
val file = File(pdfDocument.filePath)
|
||||||
showPasswordDialog(file)
|
showPasswordDialog(file)
|
||||||
} else {
|
} else {
|
||||||
// 其他错误
|
showToast(errorMessage)
|
||||||
showToast(getString(R.string.pdf_loading_failed))
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import com.all.pdfreader.pro.app.R
|
import com.all.pdfreader.pro.app.R
|
||||||
import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitBinding
|
import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitBinding
|
||||||
import com.all.pdfreader.pro.app.model.PdfPageItem
|
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.PdfSelectedPagesItem
|
||||||
import com.all.pdfreader.pro.app.model.RenameType
|
import com.all.pdfreader.pro.app.model.RenameType
|
||||||
import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter
|
import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter
|
||||||
@ -121,7 +122,8 @@ class SplitPdfActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
binding.continueNowBtn.setOnSingleClickListener {
|
binding.continueNowBtn.setOnSingleClickListener {
|
||||||
val selectedPages = splitList.filter { it.isSelected }.map { it.copy() }
|
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)
|
val item = PdfSelectedPagesItem(filePath, name, selectedPages)
|
||||||
selectedList.add(item)
|
selectedList.add(item)
|
||||||
selectedPdfAdapter.updateAdapter()
|
selectedPdfAdapter.updateAdapter()
|
||||||
@ -136,8 +138,13 @@ class SplitPdfActivity : BaseActivity() {
|
|||||||
updateViewState(false)
|
updateViewState(false)
|
||||||
}
|
}
|
||||||
binding.splitBtn.setOnSingleClickListener {
|
binding.splitBtn.setOnSingleClickListener {
|
||||||
|
logDebug("${selectedList.size}")
|
||||||
//因为图片做的路径缓存方式,所以这里直接传入整个集合到result页处理
|
//因为图片做的路径缓存方式,所以这里直接传入整个集合到result页处理
|
||||||
val intent = SplitPdfResultActivity.createIntent(this, ArrayList(selectedList))
|
val intent = SplitPdfResultActivity.createIntentPdfSelectedPagesItem(
|
||||||
|
this,
|
||||||
|
ArrayList(selectedList),
|
||||||
|
PdfPickerSource.SPLIT
|
||||||
|
)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
@ -151,21 +158,22 @@ class SplitPdfActivity : BaseActivity() {
|
|||||||
PdfUtils.clearPdfThumbsCache(this@SplitPdfActivity)
|
PdfUtils.clearPdfThumbsCache(this@SplitPdfActivity)
|
||||||
}
|
}
|
||||||
// 删除完成后,开始收集数据
|
// 删除完成后,开始收集数据
|
||||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
var firstPageLoaded = false
|
||||||
var firstPageLoaded = false
|
PdfUtils.splitPdfToPageItemsFlow(this@SplitPdfActivity, file).collect { pageItem ->
|
||||||
PdfUtils.splitPdfToPageItemsFlow(this@SplitPdfActivity, file).collect { pageItem ->
|
logDebug("splitPdfToPageItemsFlow pageItem->$pageItem")
|
||||||
if (splitList.size <= pageItem.pageIndex) {
|
if (splitList.size <= pageItem.pageIndex) {
|
||||||
splitList.add(pageItem)
|
splitList.add(pageItem)
|
||||||
adapter.notifyItemInserted(splitList.size - 1)
|
adapter.notifyItemInserted(splitList.size - 1)
|
||||||
} else {
|
} else {
|
||||||
splitList[pageItem.pageIndex] = pageItem
|
splitList[pageItem.pageIndex] = pageItem
|
||||||
adapter.updateItem(pageItem.pageIndex)
|
adapter.updateItem(pageItem.pageIndex)
|
||||||
}
|
}
|
||||||
if (!firstPageLoaded) {
|
if (!firstPageLoaded) {
|
||||||
binding.loadingRoot.root.visibility = View.GONE
|
binding.loadingRoot.root.visibility = View.GONE
|
||||||
|
if (!isSelectedViewShow) {
|
||||||
binding.selectAllBtn.visibility = View.VISIBLE
|
binding.selectAllBtn.visibility = View.VISIBLE
|
||||||
firstPageLoaded = true
|
|
||||||
}
|
}
|
||||||
|
firstPageLoaded = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.all.pdfreader.pro.app.ui.act
|
package com.all.pdfreader.pro.app.ui.act
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -8,20 +9,17 @@ import android.os.Environment
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.R
|
||||||
import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitResultBinding
|
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.PdfSelectedPagesItem
|
||||||
import com.all.pdfreader.pro.app.model.PdfSplitResultItem
|
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.adapter.SplitPdfResultAdapter
|
||||||
import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment
|
import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment
|
||||||
import com.all.pdfreader.pro.app.util.AppUtils
|
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.PdfScanner
|
||||||
import com.all.pdfreader.pro.app.util.PdfUtils
|
import com.all.pdfreader.pro.app.util.PdfUtils
|
||||||
import com.gyf.immersionbar.ImmersionBar
|
import com.gyf.immersionbar.ImmersionBar
|
||||||
@ -29,26 +27,41 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
class SplitPdfResultActivity : BaseActivity() {
|
class SplitPdfResultActivity : BaseActivity() {
|
||||||
override val TAG: String = "SplitPdfResultActivity"
|
override val TAG: String = "SplitPdfResultActivity"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_SELECTED_LIST = "extra_selected_list"
|
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(
|
fun createIntentPdfSelectedPagesItem(
|
||||||
context: Context, list: ArrayList<PdfSelectedPagesItem>
|
context: Context, list: ArrayList<PdfSelectedPagesItem>, source: PdfPickerSource
|
||||||
): Intent {
|
): Intent {
|
||||||
return Intent(context, SplitPdfResultActivity::class.java).apply {
|
return Intent(context, SplitPdfResultActivity::class.java).apply {
|
||||||
putParcelableArrayListExtra(EXTRA_SELECTED_LIST, list)
|
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 binding: ActivityPdfSplitResultBinding
|
||||||
private lateinit var adapter: SplitPdfResultAdapter
|
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 selectedList: ArrayList<PdfSelectedPagesItem>
|
||||||
|
private lateinit var inputFile: ArrayList<String>
|
||||||
|
private lateinit var source: PdfPickerSource
|
||||||
private var isSplitting = false
|
private var isSplitting = false
|
||||||
private var exitDialog: PromptDialogFragment? = null
|
private var exitDialog: PromptDialogFragment? = null
|
||||||
private val pdfRepository = getRepository()
|
private val pdfRepository = getRepository()
|
||||||
@ -62,6 +75,27 @@ class SplitPdfResultActivity : BaseActivity() {
|
|||||||
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
|
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
|
||||||
.navigationBarColor(R.color.bg_color).init()
|
.navigationBarColor(R.color.bg_color).init()
|
||||||
selectedList = requireParcelableArrayList(EXTRA_SELECTED_LIST)
|
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)
|
pdfScanner = PdfScanner(this, pdfRepository)
|
||||||
initView()
|
initView()
|
||||||
setupClick()
|
setupClick()
|
||||||
@ -69,60 +103,88 @@ class SplitPdfResultActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
adapter = SplitPdfResultAdapter(splitResultList)
|
adapter = SplitPdfResultAdapter(resultList)
|
||||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initData() {
|
private fun initData() {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val totalPages = selectedList.sumOf { it.pages.count { it.isSelected } }
|
|
||||||
var processedPages = 0
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
isSplitting = true
|
isSplitting = true
|
||||||
binding.splittingLayout.visibility = View.VISIBLE
|
binding.processingLayout.visibility = View.VISIBLE
|
||||||
binding.progressBar.isIndeterminate = false
|
binding.progressBar.isIndeterminate = false
|
||||||
binding.progressBar.progress = 0
|
binding.progressBar.progress = 0
|
||||||
binding.progressBar.max = 100
|
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) {
|
PdfUtils.exportSelectedPages(
|
||||||
val selectedPages = item.pages.filter { it.isSelected }
|
inputFile = inputFile,
|
||||||
if (selectedPages.isEmpty()) continue
|
selectedPages = selectedPages,
|
||||||
|
outputDir = outputDir,
|
||||||
val inputFile = File(item.filePath)
|
outputFileName = "${item.fileName}.pdf",
|
||||||
val outputDir = File(
|
onProgress = { _, _ -> // 不需要单文件百分比
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
|
processedPages++// 每页处理完成就加一,多个 PDF 顺序处理时,总进度线性递增
|
||||||
"PDFReaderPro/split"
|
val percent = (processedPages.toFloat() / totalPages * 100).toInt()
|
||||||
).apply { if (!exists()) mkdirs() }
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
binding.progressTv.text = "$percent"
|
||||||
PdfUtils.exportSelectedPages(
|
binding.progressBar.progress = percent
|
||||||
inputFile = inputFile,
|
}
|
||||||
selectedPages = selectedPages,
|
})?.let { resultFile ->
|
||||||
outputDir = outputDir,
|
val thumbnails =
|
||||||
outputFileName = "${item.fileName}.pdf",
|
AppUtils.generateFastThumbnail(this@SplitPdfResultActivity, resultFile)
|
||||||
onProgress = { _, _ -> // 不需要单文件百分比
|
val result = PdfSplitResultItem(resultFile.absolutePath, thumbnails, false)
|
||||||
processedPages++// 每页处理完成就加一,多个 PDF 顺序处理时,总进度线性递增
|
pdfScanner.addNewPdfToDatabase(result.filePath, result.thumbnailPath) {
|
||||||
val percent = (processedPages.toFloat() / totalPages * 100).toInt()
|
resultList.add(result)
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
}
|
||||||
binding.progressTv.text = "$percent"
|
}
|
||||||
binding.progressBar.progress = percent
|
}
|
||||||
|
} 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) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.splittingLayout.visibility = View.GONE
|
binding.processingLayout.visibility = View.GONE
|
||||||
// 默认选中第一个
|
// 默认选中第一个
|
||||||
if (splitResultList.isNotEmpty() && splitResultList.none { it.isSelected }) {
|
if (resultList.isNotEmpty() && resultList.none { it.isSelected }) {
|
||||||
splitResultList[0].isSelected = true
|
resultList[0].isSelected = true
|
||||||
}
|
}
|
||||||
adapter.updateAdapter()
|
adapter.updateAdapter()
|
||||||
isSplitting = false//拆分结束
|
isSplitting = false//拆分结束
|
||||||
@ -177,9 +239,6 @@ class SplitPdfResultActivity : BaseActivity() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用方法:读取必传参数,如果为 null 直接 finish
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private inline fun <reified T : Parcelable> requireParcelableArrayList(key: String): ArrayList<T> {
|
private inline fun <reified T : Parcelable> requireParcelableArrayList(key: String): ArrayList<T> {
|
||||||
val result: ArrayList<T>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val result: ArrayList<T>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
@ -187,11 +246,28 @@ class SplitPdfResultActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
intent.getParcelableArrayListExtra(key)
|
intent.getParcelableArrayListExtra(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.isNullOrEmpty()) {
|
|
||||||
showToast(getString(R.string.pdf_loading_failed))
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
return result ?: arrayListOf()
|
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(
|
class PdfAdapter(
|
||||||
private var pdfList: MutableList<PdfDocumentEntity>,
|
private var pdfList: MutableList<PdfDocumentEntity>,
|
||||||
private val onItemClick: (PdfDocumentEntity) -> Unit,
|
private val onItemClick: (PdfDocumentEntity) -> Unit = {},
|
||||||
private val onMoreClick: (PdfDocumentEntity) -> Unit,
|
private val onMoreClick: (PdfDocumentEntity) -> Unit = {},
|
||||||
private val onLongClick: (PdfDocumentEntity) -> Unit,
|
private val onLongClick: (PdfDocumentEntity) -> Unit = {},
|
||||||
private val onSelectModelItemClick: (Int) -> 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>() {
|
) : RecyclerView.Adapter<PdfAdapter.PdfViewHolder>() {
|
||||||
private var isMultiSelectMode = false
|
|
||||||
|
|
||||||
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
|
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
@ -57,13 +62,14 @@ class PdfAdapter(
|
|||||||
if (isMultiSelectMode) {
|
if (isMultiSelectMode) {
|
||||||
holder.binding.checkBtn.visibility = View.VISIBLE
|
holder.binding.checkBtn.visibility = View.VISIBLE
|
||||||
holder.binding.moreBtn.visibility = View.GONE
|
holder.binding.moreBtn.visibility = View.GONE
|
||||||
|
holder.binding.deleteBtn.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
holder.binding.checkBtn.visibility = View.GONE
|
holder.binding.checkBtn.visibility = if (showCheckButton) View.VISIBLE else View.GONE
|
||||||
holder.binding.moreBtn.visibility = View.VISIBLE
|
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.checkbox.isChecked = item.isSelected
|
||||||
|
|
||||||
|
|
||||||
holder.binding.root.setOnClickListener {
|
holder.binding.root.setOnClickListener {
|
||||||
if (isMultiSelectMode) {
|
if (isMultiSelectMode) {
|
||||||
item.isSelected = !item.isSelected
|
item.isSelected = !item.isSelected
|
||||||
@ -76,18 +82,27 @@ class PdfAdapter(
|
|||||||
holder.binding.moreBtn.setOnClickListener {
|
holder.binding.moreBtn.setOnClickListener {
|
||||||
onMoreClick(item)
|
onMoreClick(item)
|
||||||
}
|
}
|
||||||
holder.binding.root.setOnLongClickListener {
|
if (enableLongClick) {
|
||||||
if (!isMultiSelectMode) {
|
holder.binding.root.setOnLongClickListener {
|
||||||
isMultiSelectMode = true
|
if (!isMultiSelectMode) {
|
||||||
item.isSelected = !item.isSelected
|
isMultiSelectMode = true
|
||||||
notifyDataSetChanged()
|
item.isSelected = !item.isSelected
|
||||||
onLongClick(item)
|
notifyDataSetChanged()
|
||||||
|
onLongClick(item)
|
||||||
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
true
|
} else {
|
||||||
|
holder.binding.root.setOnLongClickListener(null) // 禁用长按
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.binding.checkBtn.setOnClickListener {
|
holder.binding.checkBtn.setOnClickListener {
|
||||||
toggleSelection(item, holder.bindingAdapterPosition)
|
toggleSelection(item, holder.bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holder.binding.deleteBtn.setOnClickListener {
|
||||||
|
onDeleteItemClick(item, holder.bindingAdapterPosition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = pdfList.size
|
override fun getItemCount(): Int = pdfList.size
|
||||||
@ -104,6 +119,11 @@ class PdfAdapter(
|
|||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeItem(position: Int) {
|
||||||
|
pdfList.removeAt(position)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
}
|
||||||
|
|
||||||
// 退出多选模式
|
// 退出多选模式
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun exitMultiSelectMode() {
|
fun exitMultiSelectMode() {
|
||||||
@ -138,7 +158,7 @@ class PdfAdapter(
|
|||||||
return pdfList.filter { it.isSelected }
|
return pdfList.filter { it.isSelected }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIsMultiSelectMode(): Boolean{
|
fun getIsMultiSelectMode(): Boolean {
|
||||||
return isMultiSelectMode
|
return isMultiSelectMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,9 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
|||||||
|
|
||||||
class SearchPdfAdapter(
|
class SearchPdfAdapter(
|
||||||
private val onItemClick: (PdfDocumentEntity) -> Unit,
|
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()) {
|
) : ListAdapter<PdfDocumentEntity, SearchPdfAdapter.PdfViewHolder>(PdfDiffCallback()) {
|
||||||
|
|
||||||
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
|
inner class PdfViewHolder(val binding: AdapterPdfItemBinding) :
|
||||||
@ -50,6 +52,9 @@ class SearchPdfAdapter(
|
|||||||
private fun bindItem(holder: PdfViewHolder, item: PdfDocumentEntity, highlightKeyword: String?) {
|
private fun bindItem(holder: PdfViewHolder, item: PdfDocumentEntity, highlightKeyword: String?) {
|
||||||
val context = holder.binding.root.context
|
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 或空字符串,就显示普通文本
|
// 文件名高亮,如果 highlightKeyword 为 null 或空字符串,就显示普通文本
|
||||||
holder.binding.tvFileName.text = if (highlightKeyword.isNullOrBlank()) {
|
holder.binding.tvFileName.text = if (highlightKeyword.isNullOrBlank()) {
|
||||||
item.fileName
|
item.fileName
|
||||||
|
|||||||
@ -84,15 +84,13 @@ class FavoriteFrag : BaseFrag() {
|
|||||||
|
|
||||||
private fun observeDocuments(onComplete: () -> Unit = {}) {
|
private fun observeDocuments(onComplete: () -> Unit = {}) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
PdfRepository.getInstance().getFavoriteDocuments().collect { list ->
|
||||||
PdfRepository.getInstance().getFavoriteDocuments().collect { list ->
|
if (list.isNotEmpty()) {
|
||||||
if (list.isNotEmpty()) {
|
adapter.updateData(list)
|
||||||
adapter.updateData(list)
|
onComplete()
|
||||||
onComplete()
|
binding.noFilesLayout.visibility = View.GONE
|
||||||
binding.noFilesLayout.visibility = View.GONE
|
} else {
|
||||||
} else {
|
binding.noFilesLayout.visibility = View.VISIBLE
|
||||||
binding.noFilesLayout.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,17 +93,15 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
|
|||||||
|
|
||||||
private fun observeDocuments() {
|
private fun observeDocuments() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
PdfRepository.getInstance().getAllDocuments().collect { list ->
|
||||||
PdfRepository.getInstance().getAllDocuments().collect { list ->
|
val sortedList = sortDocuments(list)
|
||||||
val sortedList = sortDocuments(list)
|
if (list.isNotEmpty()) {
|
||||||
if (list.isNotEmpty()) {
|
adapter.updateData(sortedList)
|
||||||
adapter.updateData(sortedList)
|
binding.noFilesLayout.visibility = View.GONE
|
||||||
binding.noFilesLayout.visibility = View.GONE
|
} else {
|
||||||
} else {
|
binding.noFilesLayout.visibility = View.VISIBLE
|
||||||
binding.noFilesLayout.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
logDebug("更新adapter数据,排序方式: ${appStore.documentSortType}")
|
|
||||||
}
|
}
|
||||||
|
logDebug("更新adapter数据,排序方式: ${appStore.documentSortType}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,15 +83,13 @@ class RecentlyFrag : BaseFrag() {
|
|||||||
|
|
||||||
private fun observeDocuments(onComplete: () -> Unit = {}) {
|
private fun observeDocuments(onComplete: () -> Unit = {}) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
PdfRepository.getInstance().getRecentlyOpenedDocuments().collect { list ->
|
||||||
PdfRepository.getInstance().getRecentlyOpenedDocuments().collect { list ->
|
if (list.isNotEmpty()) {
|
||||||
if (list.isNotEmpty()) {
|
adapter.updateData(list)
|
||||||
adapter.updateData(list)
|
onComplete()
|
||||||
onComplete()
|
binding.noFilesLayout.visibility = View.GONE
|
||||||
binding.noFilesLayout.visibility = View.GONE
|
} else {
|
||||||
} else {
|
binding.noFilesLayout.visibility = View.VISIBLE
|
||||||
binding.noFilesLayout.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.all.pdfreader.pro.app.databinding.FragmentToolsBinding
|
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() {
|
class ToolsFrag : Fragment() {
|
||||||
private lateinit var binding: FragmentToolsBinding
|
private lateinit var binding: FragmentToolsBinding
|
||||||
@ -23,6 +25,13 @@ class ToolsFrag : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
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 androidx.core.graphics.createBitmap
|
||||||
import com.all.pdfreader.pro.app.model.PdfPageItem
|
import com.all.pdfreader.pro.app.model.PdfPageItem
|
||||||
import com.shockwave.pdfium.PdfiumCore
|
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 com.tom_roush.pdfbox.pdmodel.PDDocument
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -138,8 +140,7 @@ object PdfUtils {
|
|||||||
/**
|
/**
|
||||||
* 合并多个 PDF 文件到一个新的 PDF 文件
|
* 合并多个 PDF 文件到一个新的 PDF 文件
|
||||||
*
|
*
|
||||||
* 使用 PDFBox 的 PDDocument 合并多个 PDF,支持进度回调。
|
* 使用 PDFBox 的 PDFMergerUtility 合并 PDF,支持进度回调。
|
||||||
* 避免直接生成缩略图,以保持原 PDF 的矢量质量。
|
|
||||||
*
|
*
|
||||||
* @param inputFiles 要合并的 PDF 文件列表
|
* @param inputFiles 要合并的 PDF 文件列表
|
||||||
* @param outputDir 输出目录,如果不存在会自动创建
|
* @param outputDir 输出目录,如果不存在会自动创建
|
||||||
@ -147,7 +148,18 @@ object PdfUtils {
|
|||||||
* @param onProgress 可选回调,当前处理进度 (current 文件, total 文件)
|
* @param onProgress 可选回调,当前处理进度 (current 文件, total 文件)
|
||||||
* @return 新生成的 PDF 文件,失败返回 null
|
* @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>,
|
inputFiles: List<File>,
|
||||||
outputDir: File,
|
outputDir: File,
|
||||||
outputFileName: String,
|
outputFileName: String,
|
||||||
@ -156,28 +168,25 @@ object PdfUtils {
|
|||||||
|
|
||||||
if (inputFiles.isEmpty()) return@withContext null
|
if (inputFiles.isEmpty()) return@withContext null
|
||||||
if (!outputDir.exists()) outputDir.mkdirs()
|
if (!outputDir.exists()) outputDir.mkdirs()
|
||||||
|
|
||||||
val outputFile = File(outputDir, outputFileName)
|
val outputFile = File(outputDir, outputFileName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PDDocument().use { mergedDocument ->
|
val merger = PDFMergerUtility()
|
||||||
val totalFiles = inputFiles.size
|
inputFiles.forEachIndexed { index, file ->
|
||||||
inputFiles.forEachIndexed { index, file ->
|
merger.addSource(file)
|
||||||
PDDocument.load(file).use { doc ->
|
onProgress?.invoke(index + 1, inputFiles.size)
|
||||||
for (page in doc.pages) {
|
delay(100)
|
||||||
mergedDocument.addPage(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 回调进度:按文件计数,也可以按总页数进一步精细化
|
|
||||||
onProgress?.invoke(index + 1, totalFiles)
|
|
||||||
delay(1) // 给 UI 更新留点时间
|
|
||||||
}
|
|
||||||
mergedDocument.save(outputFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
merger.destinationFileName = outputFile.absolutePath
|
||||||
|
|
||||||
|
merger.mergeDocuments(MemoryUsageSetting.setupTempFileOnly())
|
||||||
|
|
||||||
outputFile
|
outputFile
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="@color/grey" />
|
<solid android:color="@color/grey" />
|
||||||
<corners android:radius="12dp" />
|
<corners android:radius="24dp" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="#E43521"/>
|
<solid android:color="#E43521"/>
|
||||||
<corners android:radius="12dp"/>
|
<corners android:radius="24dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,6 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<stroke android:color="@color/line_color"
|
<stroke android:color="@color/line_color"
|
||||||
android:width="1dp"/>
|
android:width="0.5dp"/>
|
||||||
<corners android:radius="8dp" />
|
<corners android:radius="8dp" />
|
||||||
</shape>
|
</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" />
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/splittingLayout"
|
android:id="@+id/processingLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
@ -48,7 +48,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/splitting"
|
android:text="@string/processing"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="18sp" />
|
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
|
<RelativeLayout
|
||||||
android:layout_width="56dp"
|
android:layout_width="56dp"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp">
|
||||||
android:background="@drawable/dr_item_img_frame">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -38,10 +37,17 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
<RelativeLayout
|
||||||
android:id="@+id/tvFileImg"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="56dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="56dp" />
|
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
|
<ImageView
|
||||||
android:id="@+id/collectState"
|
android:id="@+id/collectState"
|
||||||
@ -73,6 +79,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
@ -130,6 +137,7 @@
|
|||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:src="@drawable/more" />
|
android:src="@drawable/more" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/checkBtn"
|
android:id="@+id/checkBtn"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
@ -147,6 +155,19 @@
|
|||||||
android:focusable="false" />
|
android:focusable="false" />
|
||||||
</LinearLayout>
|
</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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,17 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp" />
|
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: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>
|
</LinearLayout>
|
||||||
@ -150,4 +150,6 @@
|
|||||||
<string name="tip">Tip</string>
|
<string name="tip">Tip</string>
|
||||||
<string name="clear_history_desc">Are you sure you want to clear history?</string>
|
<string name="clear_history_desc">Are you sure you want to clear history?</string>
|
||||||
<string name="clear">Clear</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>
|
</resources>
|
||||||
Loading…
Reference in New Issue
Block a user