添加拆分pdf展示界面。

This commit is contained in:
ocean 2025-09-23 10:21:17 +08:00
parent 2089866c61
commit 06a2720547
30 changed files with 715 additions and 10 deletions

View File

@ -61,4 +61,5 @@ dependencies {
implementation(libs.glide) implementation(libs.glide)
implementation(libs.androidpdfviewer) implementation(libs.androidpdfviewer)
implementation(libs.pdfbox.android) implementation(libs.pdfbox.android)
implementation(libs.jp2forandroid)
} }

View File

@ -67,6 +67,12 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.act.SplitPdfActivity"
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"

View File

@ -5,6 +5,7 @@ import android.content.Context
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.util.FileChangeObserver import com.all.pdfreader.pro.app.util.FileChangeObserver
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
class PRApp : Application() { class PRApp : Application() {
@ -34,6 +35,8 @@ class PRApp : Application() {
// 初始化数据库 // 初始化数据库
PdfRepository.initialize(this) PdfRepository.initialize(this)
// 初始化pdfbox
PDFBoxResourceLoader.init(this)
} }
// 在权限授权后调用 // 在权限授权后调用

View File

@ -0,0 +1,10 @@
package com.all.pdfreader.pro.app.model
/**
* 拆分pdf展示用的每页信息
*/
data class PdfPageItem(
val pageIndex: Int,
var previewFilePath: String?, // 缓存文件路径
var isSelected: Boolean
)

View File

@ -0,0 +1,112 @@
package com.all.pdfreader.pro.app.ui.act
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
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.ui.adapter.SplitPdfAdapter
import com.all.pdfreader.pro.app.util.PdfUtils
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.launch
import java.io.File
class SplitPdfActivity : BaseActivity() {
override val TAG: String = "SplitPdfActivity"
companion object {
private const val EXTRA_PDF_PATH = "extra_pdf_path"
fun createIntent(context: Context, filePath: String): Intent {
return Intent(context, SplitPdfActivity::class.java).apply {
putExtra(EXTRA_PDF_PATH, filePath)
}
}
}
private lateinit var binding: ActivityPdfSplitBinding
private lateinit var adapter: SplitPdfAdapter
private var splitList: MutableList<PdfPageItem> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPdfSplitBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.white).init()
val filePath = intent.getStringExtra(EXTRA_PDF_PATH)
?: throw IllegalArgumentException("PDF file hash is required")
if (filePath.isEmpty()) {
showToast(getString(R.string.file_not))
finish()
}
initView()
setupClick()
initSplitData(File(filePath))
}
private fun initView() {
binding.title.text = getString(R.string.selected_page, 0)
binding.selectAllBtn.visibility = View.GONE
binding.loadingRoot.root.visibility = View.VISIBLE
binding.splitRv.layoutManager = GridLayoutManager(this, 2)
adapter = SplitPdfAdapter(splitList) { item, pos ->
item.isSelected = !item.isSelected
adapter.setItemSelected(pos, item.isSelected)
binding.title.text = getString(R.string.selected_page, adapter.getSelPages())
}
binding.splitRv.adapter = adapter
}
private fun setupClick() {
binding.backBtn.setOnClickListener { finish() }
binding.selectAllBtn.setOnClickListener {
val selectAll = splitList.any { !it.isSelected }
adapter.setAllSelected(selectAll)
binding.title.text = getString(R.string.selected_page, adapter.getSelPages())
if (selectAll) {
binding.selectAll.setBackgroundResource(R.drawable.dr_circular_sel_on_bg)
} else {
binding.selectAll.setBackgroundResource(R.drawable.dr_circular_sel_off_bg)
}
}
}
private fun initSplitData(file: File) {
lifecycleScope.launch {
PdfUtils.clearPdfThumbsCache(this@SplitPdfActivity) // 先清理旧缓存
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
var firstPageLoaded = false
PdfUtils.splitPdfToPageItemsFlow(this@SplitPdfActivity, file).collect { pageItem ->
logDebug("pageItem flow ->$pageItem")
if (splitList.size <= pageItem.pageIndex) {
splitList.add(pageItem)
adapter.notifyItemInserted(splitList.size - 1)
} else {
splitList[pageItem.pageIndex] = pageItem
adapter.updateItem(pageItem.pageIndex)
}
if (!firstPageLoaded) {
binding.loadingRoot.root.visibility = View.GONE
binding.selectAllBtn.visibility = View.VISIBLE
firstPageLoaded = true
}
}
binding.loadingRoot.root.visibility = View.GONE
}
}
}
override fun onDestroy() {
super.onDestroy()
splitList.clear()
PdfUtils.clearPdfThumbsCache(this)
}
}

View File

@ -0,0 +1,106 @@
package com.all.pdfreader.pro.app.ui.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.AdapterSplitPageItemBinding
import com.all.pdfreader.pro.app.model.PdfPageItem
import com.all.pdfreader.pro.app.util.AppUtils.dpToPx
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import java.io.File
class SplitPdfAdapter(
private val pdfList: MutableList<PdfPageItem>,
private val onItemClick: (PdfPageItem, Int) -> Unit
) : RecyclerView.Adapter<SplitPdfAdapter.PdfViewHolder>() {
inner class PdfViewHolder(val binding: AdapterSplitPageItemBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PdfViewHolder(
AdapterSplitPageItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: PdfViewHolder, position: Int) {
bindAll(holder, position)
}
// 支持 payload局部刷新
override fun onBindViewHolder(
holder: PdfViewHolder, position: Int, payloads: MutableList<Any>
) {
if (payloads.isEmpty()) {
bindAll(holder, position) // 全量刷新
} else {
if (payloads.any { it == "selection" }) {
bindSelectionOnly(holder, position)
}
}
}
@SuppressLint("SetTextI18n")
private fun bindAll(holder: PdfViewHolder, position: Int) {
val item = pdfList[position]
holder.binding.apply {
//更新文字图片与选中状态
pageNumberTv.text = "${item.pageIndex + 1}"
Glide.with(holder.binding.root)
.load(File(item.previewFilePath ?: ""))
.transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context)))
.into(image)
bindSelection(item, holder)
}
holder.binding.root.setOnClickListener {
val pos = holder.bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onItemClick(pdfList[pos], pos)
}
}
}
private fun bindSelectionOnly(holder: PdfViewHolder, position: Int) {
val item = pdfList[position]
bindSelection(item, holder)
}
//更新选中状态
private fun bindSelection(item: PdfPageItem, holder: PdfViewHolder) {
val b = holder.binding
val selRes =
if (item.isSelected) R.drawable.dr_circular_sel_on_bg else R.drawable.dr_circular_sel_off_bg
b.selIv.setBackgroundResource(selRes)
val bgRes = if (item.isSelected) R.drawable.dr_sel_on_frame else R.drawable.dr_sel_off_frame
b.itemBgLayout.setBackgroundResource(bgRes)
val pageBgRes =
if (item.isSelected) R.drawable.dr_item_page_number_sel_on_bg else R.drawable.dr_item_page_number_sel_off_bg
b.pageNumberTv.setBackgroundResource(pageBgRes)
}
override fun getItemCount() = pdfList.size
fun updateItem(position: Int) = notifyItemChanged(position)
// 只刷新选中状态
fun setItemSelected(position: Int, selected: Boolean) {
pdfList[position].isSelected = selected
notifyItemChanged(position, "selection") // 这里的 payload 就是标记
}
fun getSelPages() = pdfList.count { it.isSelected }
// 批量更新选中状态,用 payload 避免图片重新加载
fun setAllSelected(selected: Boolean) {
pdfList.forEach { it.isSelected = selected }
notifyItemRangeChanged(0, pdfList.size, "selection")
}
}

View File

@ -1,6 +1,7 @@
package com.all.pdfreader.pro.app.ui.dialog package com.all.pdfreader.pro.app.ui.dialog
import android.net.Uri import android.net.Uri
import android.nfc.Tag
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -12,6 +13,8 @@ import com.all.pdfreader.pro.app.databinding.DialogListMoreBinding
import com.all.pdfreader.pro.app.model.PrintResult import com.all.pdfreader.pro.app.model.PrintResult
import com.all.pdfreader.pro.app.model.RenameType import com.all.pdfreader.pro.app.model.RenameType
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.ui.act.SplitPdfActivity
import com.all.pdfreader.pro.app.ui.fragment.HomeFrag
import com.all.pdfreader.pro.app.util.AppUtils.dpToPx import com.all.pdfreader.pro.app.util.AppUtils.dpToPx
import com.all.pdfreader.pro.app.util.AppUtils.printPdfFile import com.all.pdfreader.pro.app.util.AppUtils.printPdfFile
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
@ -71,10 +74,14 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
} }
private fun initUi() { private fun initUi() {
if (pdfDocument.lastOpenedTime > 0) { if (tag == HomeFrag().tag) {
binding.removeRecentBtn.visibility = View.VISIBLE
} else {
binding.removeRecentBtn.visibility = View.GONE binding.removeRecentBtn.visibility = View.GONE
} else {
if (pdfDocument.lastOpenedTime > 0) {
binding.removeRecentBtn.visibility = View.VISIBLE
} else {
binding.removeRecentBtn.visibility = View.GONE
}
} }
binding.tvFileName.text = pdfDocument.fileName binding.tvFileName.text = pdfDocument.fileName
binding.tvFileSize.text = pdfDocument.fileSize.toFormatFileSize() binding.tvFileSize.text = pdfDocument.fileSize.toFormatFileSize()
@ -181,6 +188,11 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
}).show(parentFragmentManager, "removeRecent") }).show(parentFragmentManager, "removeRecent")
dismiss() dismiss()
} }
binding.splitBtn.setOnClickListener {
val intent = SplitPdfActivity.createIntent(requireActivity(), pdfDocument.filePath)
startActivity(intent)
dismiss()
}
} }
private fun updateCollectUi(b: Boolean) { private fun updateCollectUi(b: Boolean) {

View File

@ -0,0 +1,129 @@
package com.all.pdfreader.pro.app.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.os.ParcelFileDescriptor
import androidx.core.graphics.createBitmap
import com.all.pdfreader.pro.app.model.PdfPageItem
import com.shockwave.pdfium.PdfiumCore
import com.tom_roush.pdfbox.pdmodel.PDDocument
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
object PdfUtils {
//拆分缩略图缓存地址
const val child = "pdf_split_thumbs"
//清除拆分缓存文件夹
fun clearPdfThumbsCache(context: Context) {
val cacheDir = File(context.cacheDir, child)
if (cacheDir.exists()) {
cacheDir.listFiles()?.forEach { it.delete() }
}
}
/**
* 分页渲染 PDF 并返回 Flow<PdfPageItem>
* emit 页对象preview = nullUI 可立即显示总页数
* 后续异步渲染 Bitmap 并更新对象
*/
fun splitPdfToPageItemsFlow(
context: Context,
inputFile: File,
dpi: Float = 72f,
chunkSize: Int = 5,
thumbWidth: Int = 200
): Flow<PdfPageItem> = flow {
val pdfiumCore = PdfiumCore(context)
ParcelFileDescriptor.open(inputFile, ParcelFileDescriptor.MODE_READ_ONLY).use { fd ->
val pdfDocument = pdfiumCore.newDocument(fd)
val pageCount = pdfiumCore.getPageCount(pdfDocument)
val pages = List(pageCount) {
PdfPageItem(pageIndex = it, previewFilePath = null, isSelected = false)
}
// 先 emit 页对象,让 UI 知道总页数
pages.forEach { emit(it) }
val cacheDir = File(context.cacheDir, child).apply { mkdirs() }
for (i in 0 until pageCount) {
pdfiumCore.openPage(pdfDocument, i)
val width = (pdfiumCore.getPageWidthPoint(pdfDocument, i) * dpi / 72).toInt()
val height = (pdfiumCore.getPageHeightPoint(pdfDocument, i) * dpi / 72).toInt()
val scale = thumbWidth.toFloat() / width
val targetHeight = (height * scale).toInt()
val bitmap = createBitmap(thumbWidth, targetHeight, Bitmap.Config.RGB_565)
val canvas = Canvas(bitmap)
canvas.scale(scale, scale)
pdfiumCore.renderPageBitmap(pdfDocument, bitmap, i, 0, 0, width, height)
// 保存为压缩 JPEG
val outFile = File(cacheDir, "page_$i.jpg")
FileOutputStream(outFile).use { fos ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, fos)
}
bitmap.recycle()
pages[i].previewFilePath = outFile.absolutePath
emit(pages[i])
if ((i + 1) % chunkSize == 0) delay(50) // 分批渲染,保证 UI 流畅
}
pdfiumCore.closeDocument(pdfDocument)
}
}.flowOn(Dispatchers.IO)
/**
* 获取 PDF 总页数
*/
fun getPdfPageCount(inputFile: File): Int {
PDDocument.load(inputFile).use { return it.numberOfPages }
}
/**
* 导出用户勾选的页生成新的 PDF
* @param inputFile PDF 文件
* @param selectedPages 用户选择的页列表
* @param outputDir 输出目录外部存储可访问目录
* @param outputFileName 输出文件名
*/
suspend fun exportSelectedPages(
inputFile: File, selectedPages: List<PdfPageItem>, outputDir: File, outputFileName: String
): File? = withContext(Dispatchers.IO) {
if (selectedPages.isEmpty()) return@withContext null
if (!outputDir.exists()) outputDir.mkdirs() // 确保目录存在
val outputFile = File(outputDir, outputFileName)
try {
PDDocument.load(inputFile).use { document ->
val newDocument = PDDocument()
selectedPages.sortedBy { it.pageIndex }.forEach { pageItem ->
val page = document.getPage(pageItem.pageIndex)
newDocument.addPage(page)
}
newDocument.save(outputFile)
newDocument.close()
}
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}

View File

@ -5,5 +5,5 @@
android:viewportHeight="1024"> android:viewportHeight="1024">
<path <path
android:pathData="M927.9,478.1 L168.9,478.1l308.7,-308.9c11.7,-11.7 11.4,-30.9 -0.6,-42.9 -12,-12 -31.2,-12.2 -42.9,-0.5L75.2,484.9c-2,1.7 -3.8,3.6 -5.3,5.7 -4,5.4 -6,11.8 -5.9,18.3 -0.1,7.8 2.7,15.6 8.6,21.4l361.6,361.7c11.7,11.7 30.9,11.4 42.9,-0.5 12,-12 12.2,-31.2 0.6,-42.9L168.4,539.5l759.4,0c16.5,0 29.9,-13.7 29.9,-30.7S944.4,478.1 927.9,478.1z" android:pathData="M927.9,478.1 L168.9,478.1l308.7,-308.9c11.7,-11.7 11.4,-30.9 -0.6,-42.9 -12,-12 -31.2,-12.2 -42.9,-0.5L75.2,484.9c-2,1.7 -3.8,3.6 -5.3,5.7 -4,5.4 -6,11.8 -5.9,18.3 -0.1,7.8 2.7,15.6 8.6,21.4l361.6,361.7c11.7,11.7 30.9,11.4 42.9,-0.5 12,-12 12.2,-31.2 0.6,-42.9L168.4,539.5l759.4,0c16.5,0 29.9,-13.7 29.9,-30.7S944.4,478.1 927.9,478.1z"
android:fillColor="#2c2c2c"/> android:fillColor="#000000"/>
</vector> </vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#CC000000"/>
<corners android:radius="12dp"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/icon_sel_off_color"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/icon_sel_on_color"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="8dp"
android:bottomRightRadius="8dp" />
<solid android:color="@color/icon_sel_off_color" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="8dp"
android:bottomRightRadius="8dp" />
<solid android:color="@color/icon_sel_on_color" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomLeftRadius="8dp"
android:topRightRadius="8dp" />
<solid android:color="@color/icon_sel_off_color" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomLeftRadius="8dp"
android:topRightRadius="8dp" />
<solid android:color="@color/icon_sel_on_color" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/placeholder_bg_color"/>
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:color="@color/placeholder_bg_color"
android:width="1dp"/>
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:color="@color/icon_sel_on_color"
android:width="1dp"/>
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M842.7,285.9l-187.7,-187.7c-14.9,-14.9 -32,-21.3 -53.3,-21.3L234.7,76.8C194.1,74.7 160,108.8 160,149.3v725.3c0,40.5 34.1,74.7 74.7,74.7h554.7c40.5,0 74.7,-34.1 74.7,-74.7L864,337.1c0,-19.2 -8.5,-38.4 -21.3,-51.2zM797.9,330.7c-2.1,2.1 -4.3,0 -8.5,0h-170.7c-6.4,0 -10.7,-4.3 -10.7,-10.7L608,149.3c0,-2.1 0,-6.4 -2.1,-8.5 0,0 2.1,0 2.1,2.1l189.9,187.7zM789.3,885.3L234.7,885.3c-6.4,0 -10.7,-4.3 -10.7,-10.7L224,149.3c0,-6.4 4.3,-10.7 10.7,-10.7h311.5c-2.1,4.3 -2.1,6.4 -2.1,10.7v170.7c0,40.5 34.1,74.7 74.7,74.7h170.7c4.3,0 6.4,0 10.7,-2.1L800,874.7c0,6.4 -4.3,10.7 -10.7,10.7z"
android:fillColor="#cccccc"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M384,768c-12.8,0 -21.3,-4.3 -29.9,-12.8l-213.3,-213.3c-17.1,-17.1 -17.1,-42.7 0,-59.7s42.7,-17.1 59.7,0L384,665.6 823.5,226.1c17.1,-17.1 42.7,-17.1 59.7,0s17.1,42.7 0,59.7l-469.3,469.3c-8.5,8.5 -17.1,12.8 -29.9,12.8z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M255.9,255.9v511.7h511.7v-511.7h-511.7zM703.6,703.6h-383.8v-383.8h383.8v383.8zM895.5,575.7v319.8h-319.8v64h383.8v-383.8zM127.9,447.8v-319.8h319.8v-64h-383.8v383.8z"
android:fillColor="#666666"/>
</vector>

View File

@ -3,10 +3,10 @@
android:height="256dp" android:height="256dp"
android:viewportWidth="1024" android:viewportWidth="1024"
android:viewportHeight="1024"> android:viewportHeight="1024">
<path <path
android:pathData="M785.1,416h-61.9v-121.6c0,-108.8 -91.7,-198.4 -202.7,-198.4s-202.7,89.6 -202.7,198.4v121.6h-78.9c-55.5,0 -100.3,44.8 -100.3,100.3v311.5c0,55.5 44.8,100.3 100.3,100.3h546.1c55.5,0 100.3,-44.8 100.3,-100.3L885.3,516.3c0,-55.5 -44.8,-100.3 -100.3,-100.3zM381.9,294.4c0,-74.7 61.9,-134.4 138.7,-134.4s138.7,59.7 138.7,134.4v121.6h-277.3v-121.6zM821.3,827.7c0,19.2 -17.1,36.3 -36.3,36.3L238.9,864c-19.2,0 -36.3,-17.1 -36.3,-36.3L202.7,516.3c0,-19.2 17.1,-36.3 36.3,-36.3h546.1c19.2,0 36.3,17.1 36.3,36.3v311.5z" android:fillColor="#666666"
android:fillColor="#666666"/> android:pathData="M785.1,416h-61.9v-121.6c0,-108.8 -91.7,-198.4 -202.7,-198.4s-202.7,89.6 -202.7,198.4v121.6h-78.9c-55.5,0 -100.3,44.8 -100.3,100.3v311.5c0,55.5 44.8,100.3 100.3,100.3h546.1c55.5,0 100.3,-44.8 100.3,-100.3L885.3,516.3c0,-55.5 -44.8,-100.3 -100.3,-100.3zM381.9,294.4c0,-74.7 61.9,-134.4 138.7,-134.4s138.7,59.7 138.7,134.4v121.6h-277.3v-121.6zM821.3,827.7c0,19.2 -17.1,36.3 -36.3,36.3L238.9,864c-19.2,0 -36.3,-17.1 -36.3,-36.3L202.7,516.3c0,-19.2 17.1,-36.3 36.3,-36.3h546.1c19.2,0 36.3,17.1 36.3,36.3v311.5z" />
<path <path
android:pathData="M512,544c-17.1,0 -32,14.9 -32,32v106.7c0,17.1 14.9,32 32,32s32,-14.9 32,-32v-106.7c0,-17.1 -14.9,-32 -32,-32z" android:fillColor="#666666"
android:fillColor="#666666"/> android:pathData="M512,544c-17.1,0 -32,14.9 -32,32v106.7c0,17.1 14.9,32 32,32s32,-14.9 32,-32v-106.7c0,-17.1 -14.9,-32 -32,-32z" />
</vector> </vector>

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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: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:gravity="center_vertical"
android:text="@string/selected_page"
android:textColor="@color/black"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/selectAllBtn"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:gravity="center">
<ImageView
android:id="@+id/selectAll"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/dr_circular_sel_off_bg"
android:padding="2dp"
android:src="@drawable/gou_white" />
</LinearLayout>
</LinearLayout>
<include
android:id="@+id/loadingRoot"
layout="@layout/layout_loading"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/splitRv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
android:overScrollMode="never"
android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="6"
tools:listitem="@layout/adapter_split_page_item" />
<LinearLayout
android:id="@+id/allow_access_btn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:background="@drawable/dr_btn_not_clickable_bg"
android:gravity="center">
<TextView
style="@style/TextViewFont_PopSemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/continue_now"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/itemBgLayout"
android:layout_width="match_parent"
android:layout_height="230dp"
android:layout_margin="8dp"
android:background="@drawable/dr_sel_off_frame"
android:gravity="center"
android:padding="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/dr_placeholder_bg"
android:gravity="center">
<ImageView
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/file_document" />
</LinearLayout>
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/pageNumberTv"
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_alignParentBottom="true"
android:background="@drawable/dr_item_page_number_sel_off_bg"
android:gravity="center"
android:minWidth="24dp"
android:padding="2dp"
android:text="1"
android:textColor="@color/white" />
<ImageView
android:id="@+id/selIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:background="@drawable/dr_item_page_img_sel_off_bg"
android:padding="2dp"
android:src="@drawable/gou_white" />
</RelativeLayout>
</LinearLayout>

View File

@ -251,6 +251,28 @@
android:textSize="16sp" /> android:textSize="16sp" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/splitBtn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/icon_split" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/split_pdf"
android:textColor="@color/grey_text_color"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/setPasswordBtn" android:id="@+id/setPasswordBtn"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/loading_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#50000000"
android:clickable="true"
android:focusable="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp">
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="48dp"
android:indeterminateTint="@color/icon_sel_on_color"
android:layout_height="48dp"
android:indeterminate="true" />
<TextView
android:id="@+id/tv_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/loading"
style="@style/TextViewFont_PopMedium"
android:textColor="#FFFFFF"
android:textSize="16sp" />
</LinearLayout>
</FrameLayout>

View File

@ -23,4 +23,5 @@
<color name="text_red_lv2">#BB6D64</color> <color name="text_red_lv2">#BB6D64</color>
<color name="btn_sel_on_color">#E43521</color> <color name="btn_sel_on_color">#E43521</color>
<color name="btn_sel_off_color">#33E43521</color> <color name="btn_sel_off_color">#33E43521</color>
<color name="placeholder_bg_color">#E8EAEE</color>
</resources> </resources>

View File

@ -119,4 +119,9 @@
<string name="delete_bookmarks_desc">Are you sure you want to delete all Bookmarks?</string> <string name="delete_bookmarks_desc">Are you sure you want to delete all Bookmarks?</string>
<string name="bookmark_loading">Loading bookmarks, please try again later</string> <string name="bookmark_loading">Loading bookmarks, please try again later</string>
<string name="no_files_yet">no files yet</string> <string name="no_files_yet">no files yet</string>
<string name="split_pdf">Split PDF</string>
<string name="merge_pdf">Merge PDF</string>
<string name="selected_page">%1$d Selected</string>
<string name="loading">Loading</string>
<string name="continue_now">Continue Now</string>
</resources> </resources>

View File

@ -4,6 +4,7 @@ appcompat = "1.7.1"
agp = "8.10.1" agp = "8.10.1"
fragmentKtx = "1.8.9" fragmentKtx = "1.8.9"
glide = "5.0.4" glide = "5.0.4"
jp2forandroid = "1.0.4"
kotlin = "2.0.21" kotlin = "2.0.21"
ksp = "2.0.21-1.0.27" ksp = "2.0.21-1.0.27"
coreKtx = "1.17.0" coreKtx = "1.17.0"
@ -30,6 +31,7 @@ androidx-room-compiler = { group = "androidx.room", name = "room-compiler", vers
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room_version" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room_version" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room_version" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room_version" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
jp2forandroid = { module = "com.github.Tgo1014:JP2ForAndroid", version.ref = "jp2forandroid" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }