一:添加阅读模式对话框

1.水平与垂直阅读
2.逐页
3.反转颜色(夜晚模式)
二:优化fileActionEvent事件通知写法,需要观察什么事件自己写
三:添加自定义CustomSwitchButton按钮
This commit is contained in:
ocean 2025-09-16 18:43:16 +08:00
parent 18190d15af
commit 038c1bc46b
27 changed files with 1738 additions and 215 deletions

View File

@ -1,5 +1,7 @@
package com.all.pdfreader.pro.app.model
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import java.io.File
sealed class FileActionEvent {
@ -16,4 +18,19 @@ sealed class FileActionEvent {
data class RemovePassword(val status: Status, val success: Boolean? = null) : FileActionEvent() {
enum class Status { START, COMPLETE }
}
//只做通知不携带任何数据使用object。
object NoticeReload : FileActionEvent()
inline fun <reified T : FileActionEvent> LiveData<FileActionEvent>.observeEvent(
owner: LifecycleOwner,
crossinline handler: (T) -> Unit
) {
this.observe(owner) { event ->
if (event is T) {
handler(event)
}
}
}
}

View File

@ -20,14 +20,33 @@ class AppStore(context: Context) {
key = DOCUMENT_SORT_TYPE, defaultValue = SortConfig.default().toPreferenceString()
)
// 是否开启护眼遮罩
var isEyeCareMode: Boolean by store.boolean(
key = IS_EYE_CARE_MODE, defaultValue = false
)
// 横or竖
var isVertical: Boolean by store.boolean(
key = IS_VERTICAL, defaultValue = true//默认竖
)
// 是否逐页
var isPageFling: Boolean by store.boolean(
key = IS_PAGE_FLING, defaultValue = false
)
// 是否反转颜色
var isColorInversion: Boolean by store.boolean(
key = IS_COLOR_INVERSION, defaultValue = false
)
companion object {
private const val FILE_NAME = "prp_sp_name"
private const val PERMISSIONS_DIALOG_PROMPT = "permissions_dialog_prompt"
private const val DOCUMENT_SORT_TYPE = "document_sort_type"
private const val IS_EYE_CARE_MODE = "is_eye_care_mode"
private const val IS_VERTICAL = "is_vertical"
private const val IS_PAGE_FLING = "is_page_fling"
private const val IS_COLOR_INVERSION = "is_color_inversion"
}
}

View File

@ -22,6 +22,7 @@ import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.PdfScanner
import com.all.pdfreader.pro.app.util.StoragePermissionHelper
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import com.all.pdfreader.pro.app.viewmodel.observeEvent
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.launch
@ -71,41 +72,35 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
private fun initObserve() {
//观察其余操作
viewModel.fileActionEvent.observe(this) { event ->
when (event) {
is FileActionEvent.Rename -> {
viewModel.fileActionEvent.observeEvent<FileActionEvent.Rename>(this) { event ->
if (event.renameResult.success) {
showToast(getString(R.string.rename_successfully))
} else {
showToast(event.renameResult.errorMessage.toString())
}
}
is FileActionEvent.Delete -> {
viewModel.fileActionEvent.observeEvent<FileActionEvent.Delete>(this){ event ->
if (event.deleteResult.success) {
showToast(getString(R.string.delete_successfully))
} else {
showToast(event.deleteResult.errorMessage.toString())
}
}
is FileActionEvent.Favorite -> {
viewModel.fileActionEvent.observeEvent<FileActionEvent.Favorite>(this){ event ->
if (event.isFavorite) {
showToast(getString(R.string.added_to_favorites))
} else {
showToast(getString(R.string.removed_from_favorites))
}
}
is FileActionEvent.Duplicate -> {
viewModel.fileActionEvent.observeEvent<FileActionEvent.Duplicate>(this){ event ->
if (event.file != null) {
showToast(getString(R.string.duplicate_created_successfully))
} else {
showToast(getString(R.string.duplicate_created_failed))
}
}
is FileActionEvent.SetPassword -> {
viewModel.fileActionEvent.observeEvent<FileActionEvent.SetPassword>(this){ event ->
when (event.status) {
FileActionEvent.SetPassword.Status.START -> {
progressDialog = ProgressDialogFragment()
@ -123,8 +118,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
}
}
}
is FileActionEvent.RemovePassword -> {
viewModel.fileActionEvent.observeEvent<FileActionEvent.RemovePassword>(this){ event ->
when (event.status) {
FileActionEvent.RemovePassword.Status.START -> {
progressDialog = ProgressDialogFragment()
@ -143,8 +137,6 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
}
}
}
}
}
private fun setupFragments() {
supportFragmentManager.beginTransaction().add(R.id.fragment_fl, toolsFragment, "TOOLS")

View File

@ -9,10 +9,14 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfViewBinding
import com.all.pdfreader.pro.app.model.FileActionEvent
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.ViewModelDialogFragment
import com.all.pdfreader.pro.app.ui.view.CustomScrollHandle
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import com.all.pdfreader.pro.app.viewmodel.observeEvent
import com.github.barteksc.pdfviewer.PDFView
import com.github.barteksc.pdfviewer.listener.OnErrorListener
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener
@ -69,6 +73,12 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
finish()
}
}
viewModel.fileActionEvent.observeEvent<FileActionEvent.NoticeReload>(this) {
logDebug("observeEvent NoticeReload")
val file = File(pdfDocument.filePath)
loadPdfInternal(file, null)
}
}
private fun setupOnClick() {
@ -80,7 +90,9 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
toggleEyeCareMode(appStore.isEyeCareMode)
}
binding.viewModelBtn.setOnClickListener {
ViewModelDialogFragment(pdfDocument.filePath).show(
supportFragmentManager, "ViewModelDialogFragment"
)
}
}
@ -102,7 +114,8 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
//PDF 文档加载完成时回调
override fun loadComplete(nbPages: Int) {
//加载完毕进行一次缩放
binding.pdfview.resetZoomWithAnimation()
}
//PDF 加载出错时回调
@ -161,8 +174,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
}
private fun loadPdfInternal(file: File, password: String?) {
binding.pdfview.fromFile(file)
.apply {
binding.pdfview.fromFile(file).apply {
password?.let { password(it) } // 只有在有密码时才调用
defaultPage(pdfDocument.lastReadPage) // 从上次阅读页码开始
enableDoubletap(true) // 是否允许双击缩放
@ -172,9 +184,17 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
onTap(this@PdfViewActivity) // 单击回调
onPageChange(this@PdfViewActivity) // 页面改变回调
scrollHandle(CustomScrollHandle(this@PdfViewActivity)) // 自定义的页数展示
pageFling(true)//逐页滑动
if (appStore.isPageFling) {
pageSnap(true)//页面可在逐页中,可居中展示,非逐页模式,滑动停止后可自动定格在居中位置。
autoSpacing(true)//开启逐页就开启自动间距
pageFling(true)//逐页
} else {
spacing(10)
pageFling(false)
}
.load()
swipeHorizontal(!appStore.isVertical)
nightMode(appStore.isColorInversion)
}.load()
}
private fun toggleFullScreen() {

View File

@ -1,36 +1,32 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.net.Uri
import android.animation.ValueAnimator
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.activityViewModels
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogListMoreBinding
import com.all.pdfreader.pro.app.databinding.DialogViewModelBinding
import com.all.pdfreader.pro.app.model.PrintResult
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
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.setClickWithAnimation
import com.all.pdfreader.pro.app.util.AppUtils.shareFile
import com.all.pdfreader.pro.app.util.FileUtils.toFormatFileSize
import com.all.pdfreader.pro.app.util.FileUtils.toSlashDate
import com.all.pdfreader.pro.app.sp.AppStore
import com.all.pdfreader.pro.app.ui.view.CustomSwitchButton
import com.all.pdfreader.pro.app.ui.view.CustomSwitchButton.OnCheckedChangeListener
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import java.io.File
class ViewModelDialogFragment(val filePath: String) : BottomSheetDialogFragment() {
private lateinit var binding: DialogViewModelBinding
private val viewModel: PdfViewModel by activityViewModels()
private val viewModel: PdfViewModel by activityViewModels()//为PdfViewActivity的PdfViewModel
private lateinit var pdfDocument: PdfDocumentEntity
private var isFavorite: Boolean = false
private var isFirstSetSelected = true// 第一次直接设置位置,不做指示器的切换动画
private val appstore by lazy { AppStore(requireActivity()) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -59,10 +55,8 @@ class ViewModelDialogFragment(val filePath: String) : BottomSheetDialogFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.pdfDocument.observe(this) { document ->
document?.let {
viewModel.pdfDocument.value?.let {
pdfDocument = it
isFavorite = pdfDocument.isFavorite
initUi()
setupOnClick()
} ?: run {
@ -70,116 +64,100 @@ class ViewModelDialogFragment(val filePath: String) : BottomSheetDialogFragment(
dismiss()
}
}
viewModel.getPDFDocument(filePath)
}
private fun initUi() {
binding.tvFileName.text = pdfDocument.fileName
binding.tvFileSize.text = pdfDocument.fileSize.toFormatFileSize()
binding.tvFileDate.text = pdfDocument.lastModified.toSlashDate()
if (pdfDocument.isPassword) {
binding.lockLayout.visibility = View.VISIBLE
binding.tvFileImg.visibility = View.GONE
} else {
binding.lockLayout.visibility = View.GONE
binding.tvFileImg.visibility = View.VISIBLE
Glide.with(binding.root).load(pdfDocument.thumbnailPath)
.transform(CenterCrop(), RoundedCorners(8.dpToPx(binding.root.context)))
.into(binding.tvFileImg)
// 初始化指示器宽度
binding.indicator.post {
val indicatorWidth = binding.btnHorizontal.width
binding.indicator.layoutParams.width = indicatorWidth
binding.indicator.requestLayout()
setSelectedMode(appstore.isVertical)
}
updateCollectUi(isFavorite)
updatePasswordUi(pdfDocument.isPassword)
binding.switchPageByPage.setChecked(appstore.isPageFling)
binding.switchColorInversion.setChecked(appstore.isColorInversion)
}
private fun setupOnClick() {
binding.collectBtn.setClickWithAnimation(duration = 250) {
isFavorite = !isFavorite
updateCollectUi(isFavorite)
viewModel.saveCollectState(pdfDocument.filePath, isFavorite)
dismiss()
binding.btnHorizontal.setOnClickListener {
setSelectedMode(isVertical = false)
AppStore(requireActivity()).isVertical = false
viewModel.noticeReloadPdf()
}
binding.renameFileBtn.setOnClickListener {
RenameDialogFragment().show(parentFragmentManager, "ListMoreDialogFragment")
dismiss()
binding.btnVertical.setOnClickListener {
setSelectedMode(isVertical = true)
AppStore(requireActivity()).isVertical = true
viewModel.noticeReloadPdf()
}
binding.deleteFileBtn.setOnClickListener {
DeleteDialogFragment().show(parentFragmentManager, "DeleteDialogFragment")
dismiss()
binding.switchPageByPage.setOnCheckedChangeListener(object : OnCheckedChangeListener {
override fun onCheckedChanged(view: CustomSwitchButton?, isChecked: Boolean) {
view?.setChecked(isChecked)
appstore.isPageFling = isChecked
viewModel.noticeReloadPdf()
}
binding.detailsBtn.setOnClickListener {
FileDetailsDialogFragment().show(parentFragmentManager, "FileDetailsDialogFragment")
dismiss()
})
binding.switchColorInversion.setOnCheckedChangeListener(object : OnCheckedChangeListener {
override fun onCheckedChanged(
view: CustomSwitchButton?,
isChecked: Boolean
) {
view?.setChecked(isChecked)
appstore.isColorInversion = isChecked
viewModel.noticeReloadPdf()
}
binding.shareBtn.setOnClickListener {
shareFile(requireActivity(), File(pdfDocument.filePath))
dismiss()
}
binding.printBtn.setOnClickListener {
val result = printPdfFile(requireActivity(), Uri.fromFile(File(pdfDocument.filePath)))
when (result) {
PrintResult.DeviceNotSupported -> {
Toast.makeText(
context,
R.string.device_does_not_support_printing,
Toast.LENGTH_LONG
).show()
})
}
is PrintResult.Error -> {
Toast.makeText(context, R.string.pdf_cannot_print_error, Toast.LENGTH_LONG)
.show()
}
private fun setSelectedMode(isVertical: Boolean) {
binding.apply {
val targetX = if (isVertical) btnVertical.x else btnHorizontal.x
PrintResult.MalformedPdf -> {
Toast.makeText(context, R.string.cannot_print_malformed_pdf, Toast.LENGTH_LONG)
.show()
}
PrintResult.PasswordRequired -> {
Toast.makeText(
context,
R.string.pdf_cant_print_password_protected,
Toast.LENGTH_LONG
).show()
}
PrintResult.Success -> {
}
}
dismiss()
}
binding.duplicateFileBtn.setOnClickListener {
viewModel.duplicateFile(requireActivity(), pdfDocument.filePath)
dismiss()
}
binding.setPasswordBtn.setOnClickListener {
if (pdfDocument.isPassword) {
PdfRemovePasswordDialog().show(parentFragmentManager, "PdfRemovePasswordDialog")
if (isFirstSetSelected) {
indicator.translationX = targetX
isFirstSetSelected = false
} else {
PdfSetPasswordDialog().show(parentFragmentManager, "PdfSetPasswordDialog")
indicator.animate().translationX(targetX).setDuration(250)
.setInterpolator(AccelerateDecelerateInterpolator()).start()
}
dismiss()
// 横向
animateIconAndTextColor(
horizontalTv,
horizontalIv,
if (isVertical) requireContext().getColor(R.color.white)
else requireContext().getColor(R.color.icon_color),
if (isVertical) requireContext().getColor(R.color.icon_color)
else requireContext().getColor(R.color.white)
)
// 垂直
animateIconAndTextColor(
verticalTv,
verticalIv,
if (isVertical) requireContext().getColor(R.color.icon_color)
else requireContext().getColor(R.color.white),
if (isVertical) requireContext().getColor(R.color.white)
else requireContext().getColor(R.color.icon_color)
)
}
}
private fun updateCollectUi(b: Boolean) {
if (b) {
binding.collectIv.setImageResource(R.drawable.collected)
} else {
binding.collectIv.setImageResource(R.drawable.collect)
/**
* 改变img与text的颜色值动画渐变同步indicator的时间
*/
private fun animateIconAndTextColor(
textView: TextView, imageView: ImageView, fromColor: Int, toColor: Int
) {
ValueAnimator.ofArgb(fromColor, toColor).apply {
duration = 250
addUpdateListener { animator ->
val animatedColor = animator.animatedValue as Int
textView.setTextColor(animatedColor)
imageView.setColorFilter(animatedColor, PorterDuff.Mode.SRC_IN)
}
start()
}
}
private fun updatePasswordUi(b: Boolean) {
if (b) {
binding.passwordIv.setImageResource(R.drawable.unlock)
binding.passwordTv.text = getString(R.string.remove_password)
} else {
binding.passwordIv.setImageResource(R.drawable.lock)
binding.passwordTv.text = getString(R.string.set_password)
}
}
private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,18 @@
package com.all.pdfreader.pro.app.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.core.graphics.createBitmap
import com.all.pdfreader.pro.app.PRApp
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.util.AppUtils.generateFastThumbnail
import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted
import com.shockwave.pdfium.PdfDocument
import com.shockwave.pdfium.PdfiumCore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.TimeUnit
class PdfScanner(

View File

@ -0,0 +1,19 @@
package com.all.pdfreader.pro.app.viewmodel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import com.all.pdfreader.pro.app.model.FileActionEvent
/**
* 公用观察FileActionEvent事件函数只观察需要观察的响应
*/
inline fun <reified T : FileActionEvent> LiveData<FileActionEvent>.observeEvent(
owner: LifecycleOwner,
crossinline handler: (T) -> Unit
) {
this.observe(owner) { event ->
if (event is T) {
handler(event)
}
}
}

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.all.pdfreader.pro.app.PRApp
import com.all.pdfreader.pro.app.model.FileActionEvent
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository
@ -156,19 +157,40 @@ class PdfViewModel : ViewModel() {
viewModelScope.launch {
_fileActionEvent.postValue(FileActionEvent.RemovePassword(FileActionEvent.RemovePassword.Status.START))
val success = withContext(Dispatchers.IO) {
PdfSecurityUtils.removePasswordFromPdf(filePath, password).also {
if (it) {
val success = try {
withContext(Dispatchers.IO) {
val removed = PdfSecurityUtils.removePasswordFromPdf(filePath, password)
Log.d("ocean", "密码移除$removed")
if (removed) {
pdfRepository.updateIsPassword(filePath, false)
val document = pdfRepository.getDocumentByPath(filePath)
if (document?.thumbnailPath.isNullOrEmpty()) {
val newThumbnail = generateFastThumbnail(PRApp.getContext(), File(filePath))
Log.d("ocean", "最新图片:$newThumbnail")
if (!newThumbnail.isNullOrEmpty() && filePath != newThumbnail) {
pdfRepository.updateThumbnailPath(filePath, newThumbnail)
}
}
}
removed // 这里直接返回移除密码的结果
}
} catch (e: Exception) {
Log.e("ocean", "removePassword 失败", e)
false
}
_fileActionEvent.postValue(
FileActionEvent.SetPassword(
FileActionEvent.SetPassword.Status.COMPLETE,
FileActionEvent.RemovePassword(
FileActionEvent.RemovePassword.Status.COMPLETE,
success
)
)
}
}
fun noticeReloadPdf() {
viewModelScope.launch {
_fileActionEvent.postValue(FileActionEvent.NoticeReload)
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 滑块颜色 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#C8E6C9" android:state_checked="true"/> <!---->
<item android:color="#E0E0E0" android:state_checked="false"/> <!---->
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 轨道颜色 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#4CAF50" android:state_checked="true"/> <!---->
<item android:color="#9E9E9E" android:state_checked="false"/> <!---->
</selector>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dip"
android:height="24.0dip"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillAlpha="0.01"
android:fillColor="@color/icon_color"
android:pathData="M0,0h24v24h-24z"
android:strokeAlpha="0.01" />
<path
android:fillColor="@color/icon_color"
android:fillType="evenOdd"
android:pathData="M2,12C2,6.477 6.477,2 12,2C17.523,2 22,6.477 22,12C22,17.523 17.523,22 12,22C6.477,22 2,17.523 2,12ZM20,12C20,7.582 16.418,4 12,4C7.582,4 4,7.582 4,12C4,16.418 7.582,20 12,20C16.418,20 20,16.418 20,12Z" />
<path
android:fillColor="@color/icon_color"
android:fillType="evenOdd"
android:pathData="M12,18C15.314,18 18,15.314 18,12C18,8.686 15.314,6 12,6V18Z" />
</vector>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/grey"/>
<corners android:radius="24dp"/>
</shape>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/black"/>
<corners android:radius="24dp"/>
</shape>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="24dp"/>
<solid android:color="@android:color/transparent"/>
</shape>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#000000"/>
<corners android:radius="24dp"/>
</shape>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M5.5,4L18.5,4C18.7761,4 19,4.2239 19,4.5L19,15.7929C19,15.9255 18.9473,16.0527 18.8536,16.1464L15.1464,19.8536C15.0527,19.9473 14.9255,20 14.7929,20L5.5,20C5.2239,20 5,19.7761 5,19.5L5,4.5C5,4.2239 5.2239,4 5.5,4Z"
android:strokeWidth="2.0"
android:strokeColor="@color/icon_color" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M18,15L14.5,15C14.2239,15 14,15.2239 14,15.5L14,20L14,20"
android:strokeWidth="2.0"
android:strokeColor="@color/icon_color"
android:strokeLineCap="square" />
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,7h16v10h-16z"
android:strokeWidth="2"
android:strokeColor="@color/icon_color"
android:fillColor="@android:color/transparent"
android:strokeLineJoin="round"
android:strokeLineCap="round" />
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7,4h10v16h-10z"
android:strokeWidth="2"
android:strokeColor="@color/icon_color"
android:fillColor="@android:color/transparent"
android:strokeLineJoin="round"
android:strokeLineCap="round" />
</vector>

View File

@ -44,12 +44,13 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/white">
android:gravity="center">
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:background="@color/grey" />
</LinearLayout>

View File

@ -246,7 +246,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:fontFamily="@font/poppins_medium"
android:text="@string/duplicate_file"
android:textColor="@color/grey_text_color"
android:textSize="16sp" />
@ -270,7 +269,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:fontFamily="@font/poppins_medium"
android:text="@string/set_password"
android:textColor="@color/grey_text_color"
android:textSize="16sp" />
@ -292,7 +290,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:fontFamily="@font/poppins_medium"
android:text="@string/delete_file"
android:textColor="@color/grey_text_color"
android:textSize="16sp" />

View File

@ -1,5 +1,6 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@ -17,4 +18,161 @@
android:background="@drawable/dr_dialog_indicator_bg" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:fontFamily="@font/poppins_semibold"
android:text="@string/view_model"
android:textColor="@color/black"
android:textSize="18sp" />
<FrameLayout
android:id="@+id/toggleContainer"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/dr_bg_toggle_group"
android:clipToPadding="false">
<View
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@drawable/dr_bg_toggle_indicator"
android:translationX="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/btnHorizontal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/horizontalIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/read_horizontal" />
<TextView
android:id="@+id/horizontalTv"
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:gravity="center"
android:text="@string/horizontal"
android:textColor="@color/icon_color"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/btnVertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/verticalIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/read_vertical" />
<TextView
android:id="@+id/verticalTv"
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:gravity="center"
android:text="@string/vertical"
android:textColor="@color/icon_color"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/page_by_page" />
<TextView
style="@style/TextViewFont_PopMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="@string/page_by_page"
android:textColor="@color/icon_color"
android:textSize="16sp" />
<com.all.pdfreader.pro.app.ui.view.CustomSwitchButton
android:id="@+id/switchPageByPage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:sb_background="@color/grey"
app:sb_show_indicator="false" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/color_inversion" />
<TextView
style="@style/TextViewFont_PopMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="@string/color_inversion"
android:textColor="@color/icon_color"
android:textSize="16sp" />
<com.all.pdfreader.pro.app.ui.view.CustomSwitchButton
android:id="@+id/switchColorInversion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:sb_background="@color/grey"
app:sb_show_indicator="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -15,4 +15,5 @@
<color name="black_img_color">#2c2c2c</color>
<color name="grey_text_color">#666666</color>
<color name="eye_protection_color">#80FFD699</color>
<color name="icon_color">#636366</color>
</resources>

View File

@ -97,4 +97,8 @@
<string name="duplicate_created_failed">Duplicate file created failed</string>
<string name="processing">Processing…</string>
<string name="view_model">View Model</string>
<string name="page_by_page">Page by page</string>
<string name="color_inversion">color inversion</string>
<string name="horizontal">Horizontal</string>
<string name="vertical">Vertical</string>
</resources>

View File

@ -17,7 +17,7 @@
<item name="android:windowIsTranslucent">true</item>
</style>
<style name="CustomBottomSheetDialogTheme" parent="@style/Theme.Design.BottomSheetDialog">
<style name="CustomBottomSheetDialogTheme" parent="@style/Theme.MaterialComponents.Light.BottomSheetDialog">
<!-- 关键属性:取消浮动效果 -->
<item name="android:windowIsFloating">false</item>
<!-- 设置导航栏颜色 -->
@ -31,4 +31,18 @@
<item name="android:background">@android:color/transparent</item>
</style>
<style name="TextViewFont_PopMedium">
<item name="android:includeFontPadding">false</item>
<item name="android:fontFamily">@font/poppins_medium</item>
</style>
<style name="TextViewFont_PopRegular">
<item name="android:includeFontPadding">false</item>
<item name="android:fontFamily">@font/poppins_regular</item>
</style>
<style name="TextViewFont_PopSemiBold">
<item name="android:includeFontPadding">false</item>
<item name="android:fontFamily">@font/poppins_semibold</item>
</style>
</resources>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SwitchButton">
<attr name="sb_shadow_radius" format="reference|dimension"/>
<attr name="sb_shadow_offset" format="reference|dimension"/>
<attr name="sb_shadow_color" format="reference|color"/>
<attr name="sb_uncheck_color" format="reference|color"/>
<attr name="sb_checked_color" format="reference|color"/>
<attr name="sb_border_width" format="reference|dimension"/>
<attr name="sb_checkline_color" format="reference|color"/>
<attr name="sb_checkline_width" format="reference|dimension"/>
<attr name="sb_uncheckcircle_color" format="reference|color"/>
<attr name="sb_uncheckcircle_width" format="reference|dimension"/>
<attr name="sb_uncheckcircle_radius" format="reference|dimension"/>
<attr name="sb_checked" format="reference|boolean"/>
<attr name="sb_shadow_effect" format="reference|boolean"/>
<attr name="sb_effect_duration" format="reference|integer"/>
<attr name="sb_button_color" format="reference|color"/>
<attr name="sb_show_indicator" format="reference|boolean"/>
<attr name="sb_background" format="reference|color"/>
<attr name="sb_enable_effect" format="reference|boolean"/>
<attr name="sb_checkedbutton_color" format="reference|color"/>
<attr name="sb_uncheckbutton_color" format="reference|color"/>
</declare-styleable>
</resources>

View File

@ -19,6 +19,7 @@ swiperefreshlayout = "1.1.0"
recyclerview = "1.4.0"
protoliteWellKnownTypes = "18.0.1"
material = "1.12.0"
composeMaterial3 = "1.5.1"
[libraries]
androidpdfviewer = { module = "com.github.marain87:AndroidPdfViewer", version.ref = "androidpdfviewer" }
@ -40,6 +41,7 @@ androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview"
pdfbox-android = { module = "com.tom-roush:pdfbox-android", version.ref = "pdfboxAndroid" }
protolite-well-known-types = { group = "com.google.firebase", name = "protolite-well-known-types", version.ref = "protoliteWellKnownTypes" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "composeMaterial3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }