1.再次优化扫描文件性能,异步获取缩略图

2.添加列表item,更多设置选项对话框
3.修复数据库键值,不使用hash来作为键值,依据文件路径来判定
4.修复密码输入框实现,使用自带的错误等提示TextInputEditText
This commit is contained in:
ocean 2025-09-10 11:11:11 +08:00
parent ff14a8f3ee
commit 369dc9d129
40 changed files with 1048 additions and 315 deletions

View File

@ -19,18 +19,18 @@ interface BookmarkDao {
@Query("SELECT * FROM bookmarks WHERE id = :bookmarkId")
suspend fun getById(bookmarkId: Long): BookmarkEntity?
@Query("SELECT * FROM bookmarks WHERE pdfHash = :pdfHash ORDER BY pageNumber ASC, createTime ASC")
fun getBookmarksByPdf(pdfHash: String): Flow<List<BookmarkEntity>>
@Query("SELECT * FROM bookmarks WHERE filePath = :filePath ORDER BY pageNumber ASC, createTime ASC")
fun getBookmarksByPdf(filePath: String): Flow<List<BookmarkEntity>>
@Query("SELECT * FROM bookmarks WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber")
suspend fun getBookmarksByPage(pdfHash: String, pageNumber: Int): List<BookmarkEntity>
@Query("SELECT * FROM bookmarks WHERE filePath = :filePath AND pageNumber = :pageNumber")
suspend fun getBookmarksByPage(filePath: String, pageNumber: Int): List<BookmarkEntity>
@Query("SELECT COUNT(*) FROM bookmarks WHERE pdfHash = :pdfHash")
suspend fun getBookmarkCount(pdfHash: String): Int
@Query("SELECT COUNT(*) FROM bookmarks WHERE filePath = :filePath")
suspend fun getBookmarkCount(filePath: String): Int
@Query("DELETE FROM bookmarks WHERE pdfHash = :pdfHash")
suspend fun deleteAllByPdf(pdfHash: String)
@Query("DELETE FROM bookmarks WHERE filePath = :filePath")
suspend fun deleteAllByPdf(filePath: String)
@Query("DELETE FROM bookmarks WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber")
suspend fun deleteByPage(pdfHash: String, pageNumber: Int)
@Query("DELETE FROM bookmarks WHERE filePath = :filePath AND pageNumber = :pageNumber")
suspend fun deleteByPage(filePath: String, pageNumber: Int)
}

View File

@ -19,21 +19,21 @@ interface NoteDao {
@Query("SELECT * FROM notes WHERE id = :noteId")
suspend fun getById(noteId: Long): NoteEntity?
@Query("SELECT * FROM notes WHERE pdfHash = :pdfHash ORDER BY pageNumber ASC, createTime ASC")
fun getNotesByPdf(pdfHash: String): Flow<List<NoteEntity>>
@Query("SELECT * FROM notes WHERE filePath = :filePath ORDER BY pageNumber ASC, createTime ASC")
fun getNotesByPdf(filePath: String): Flow<List<NoteEntity>>
@Query("SELECT * FROM notes WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber")
suspend fun getNotesByPage(pdfHash: String, pageNumber: Int): List<NoteEntity>
@Query("SELECT * FROM notes WHERE filePath = :filePath AND pageNumber = :pageNumber")
suspend fun getNotesByPage(filePath: String, pageNumber: Int): List<NoteEntity>
@Query("SELECT * FROM notes WHERE pdfHash = :pdfHash AND noteType = :noteType")
fun getNotesByType(pdfHash: String, noteType: String): Flow<List<NoteEntity>>
@Query("SELECT * FROM notes WHERE filePath = :filePath AND noteType = :noteType")
fun getNotesByType(filePath: String, noteType: String): Flow<List<NoteEntity>>
@Query("SELECT COUNT(*) FROM notes WHERE pdfHash = :pdfHash")
suspend fun getNoteCount(pdfHash: String): Int
@Query("SELECT COUNT(*) FROM notes WHERE filePath = :filePath")
suspend fun getNoteCount(filePath: String): Int
@Query("DELETE FROM notes WHERE pdfHash = :pdfHash")
suspend fun deleteAllByPdf(pdfHash: String)
@Query("DELETE FROM notes WHERE filePath = :filePath")
suspend fun deleteAllByPdf(filePath: String)
@Query("DELETE FROM notes WHERE pdfHash = :pdfHash AND pageNumber = :pageNumber")
suspend fun deleteByPage(pdfHash: String, pageNumber: Int)
@Query("DELETE FROM notes WHERE filePath = :filePath AND pageNumber = :pageNumber")
suspend fun deleteByPage(filePath: String, pageNumber: Int)
}

View File

@ -34,6 +34,6 @@ interface PdfDocumentDao {
@Delete
suspend fun delete(document: PdfDocumentEntity)
@Query("DELETE FROM pdf_documents WHERE fileHash = :fileHash")
suspend fun deleteByHash(fileHash: String)
@Query("DELETE FROM pdf_documents WHERE filePath = :filePath")
suspend fun deleteByPath(filePath: String)
}

View File

@ -11,25 +11,25 @@ interface RecentReadDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(recentRead: RecentReadEntity)
@Query("SELECT * FROM recently_read WHERE pdfHash = :pdfHash")
suspend fun getByPdfHash(pdfHash: String): RecentReadEntity?
@Query("SELECT * FROM recently_read WHERE filePath = :filePath")
suspend fun getByPdfHash(filePath: String): RecentReadEntity?
@Query("""
SELECT pdf_documents.*
FROM pdf_documents
INNER JOIN recently_read ON pdf_documents.fileHash = recently_read.pdfHash
INNER JOIN recently_read ON pdf_documents.filePath = recently_read.filePath
ORDER BY recently_read.lastOpenedTime DESC
""")
fun getRecentReadDocuments(): Flow<List<PdfDocumentEntity>>
@Query("UPDATE recently_read SET lastOpenedTime = :time, openedCount = openedCount + 1 WHERE pdfHash = :pdfHash")
suspend fun updateOpenTime(pdfHash: String, time: Long = System.currentTimeMillis())
@Query("UPDATE recently_read SET lastOpenedTime = :time, openedCount = openedCount + 1 WHERE filePath = :filePath")
suspend fun updateOpenTime(filePath: String, time: Long = System.currentTimeMillis())
@Query("UPDATE recently_read SET totalReadTime = totalReadTime + :additionalTime WHERE pdfHash = :pdfHash")
suspend fun addReadTime(pdfHash: String, additionalTime: Long)
@Query("UPDATE recently_read SET totalReadTime = totalReadTime + :additionalTime WHERE filePath = :filePath")
suspend fun addReadTime(filePath: String, additionalTime: Long)
@Query("DELETE FROM recently_read WHERE pdfHash = :pdfHash")
suspend fun deleteByPdfHash(pdfHash: String)
@Query("DELETE FROM recently_read WHERE filePath = :filePath")
suspend fun deleteByPdfPath(filePath: String)
@Query("DELETE FROM recently_read WHERE lastOpenedTime < :cutoffTime")
suspend fun deleteOldRecents(cutoffTime: Long)

View File

@ -9,17 +9,17 @@ import androidx.room.PrimaryKey
tableName = "bookmarks",
foreignKeys = [ForeignKey(
entity = PdfDocumentEntity::class,
parentColumns = ["fileHash"],
childColumns = ["pdfHash"],
parentColumns = ["filePath"],
childColumns = ["filePath"],
onDelete = ForeignKey.CASCADE
)],
indices = [Index(value = ["pdfHash"])]
indices = [Index(value = ["filePath"])]
)
data class BookmarkEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val pdfHash: String, // 关联PdfDocumentEntity的fileHash
val filePath: String, // 关联PdfDocumentEntity的fileHash
val pageNumber: Int, // 页码(从0开始)
val label: String, // 书签标签
val positionX: Float = 0f, // 页面内X位置

View File

@ -9,17 +9,17 @@ import androidx.room.PrimaryKey
tableName = "notes",
foreignKeys = [ForeignKey(
entity = PdfDocumentEntity::class,
parentColumns = ["fileHash"],
childColumns = ["pdfHash"],
parentColumns = ["filePath"],
childColumns = ["filePath"],
onDelete = ForeignKey.CASCADE
)],
indices = [Index(value = ["pdfHash"])]
indices = [Index(value = ["filePath"])]
)
data class NoteEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val pdfHash: String, // 关联PdfDocumentEntity的fileHash
val filePath: String, // 关联PdfDocumentEntity的fileHash
val pageNumber: Int, // 页码(从0开始)
val noteType: String, // 注释类型: HIGHLIGHT, TEXT_NOTE, DRAWING
val content: String, // 注释内容(文本或序列化的绘制数据)

View File

@ -9,9 +9,9 @@ import kotlinx.parcelize.Parcelize
@Entity(tableName = "pdf_documents")
data class PdfDocumentEntity(
@PrimaryKey
val fileHash: String, // 文件内容哈希(MD5/SHA-1)
val filePath: String, // 当前文件路径
val fileHash: String? = null, // 文件内容哈希(MD5/SHA-1),扫描时不进行获取,需要做判定再单独获取
val fileName: String, // 文件名
val fileSize: Long, // 文件大小(字节)
val lastModified: Long, // 文件最后修改时间

View File

@ -9,17 +9,17 @@ import androidx.room.PrimaryKey
tableName = "recently_read",
foreignKeys = [ForeignKey(
entity = PdfDocumentEntity::class,
parentColumns = ["fileHash"],
childColumns = ["pdfHash"],
parentColumns = ["filePath"],
childColumns = ["filePath"],
onDelete = ForeignKey.CASCADE
)],
indices = [Index(value = ["pdfHash"])]
indices = [Index(value = ["filePath"])]
)
data class RecentReadEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val pdfHash: String, // 关联PdfDocumentEntity的fileHash
val filePath: String, // 关联PdfDocumentEntity的filePath
val lastOpenedTime: Long, // 最后打开时间
val openedCount: Int = 1, // 打开次数
val totalReadTime: Long = 0, // 总阅读时长(毫秒)

View File

@ -41,16 +41,16 @@ class PdfRepository private constructor(context: Context) {
fun searchDocuments(query: String): Flow<List<PdfDocumentEntity>> =
pdfDao.searchDocuments(query)
suspend fun updateFavoriteStatus(fileHash: String, isFavorite: Boolean) {
val document = pdfDao.getByPath(fileHash)?.copy(
suspend fun updateFavoriteStatus(filePath: String, isFavorite: Boolean) {
val document = pdfDao.getByPath(filePath)?.copy(
isFavorite = isFavorite,
addedToFavoriteTime = if (isFavorite) System.currentTimeMillis() else null
)
document?.let { pdfDao.update(it) }
}
suspend fun updateReadingProgress(fileHash: String, page: Int, progress: Float) {
val document = pdfDao.getByPath(fileHash)?.copy(
suspend fun updateReadingProgress(filePath: String, page: Int, progress: Float) {
val document = pdfDao.getByPath(filePath)?.copy(
lastOpenedTime = System.currentTimeMillis(),
lastReadPage = page,
readingProgress = progress
@ -58,29 +58,37 @@ class PdfRepository private constructor(context: Context) {
document?.let { pdfDao.update(it) }
}
suspend fun updatePasswordStatus(fileHash: String, isPassword: Boolean) {
val document = pdfDao.getByPath(fileHash)?.copy(
suspend fun updatePasswordStatus(filePath: String, isPassword: Boolean) {
val document = pdfDao.getByPath(filePath)?.copy(
isPassword = isPassword
)
document?.let { pdfDao.update(it) }
}
suspend fun updatePassword(fileHash: String, password: String?) {
val document = pdfDao.getByPath(fileHash)?.copy(
suspend fun updatePassword(filePath: String, password: String?) {
val document = pdfDao.getByPath(filePath)?.copy(
password = password
)
document?.let { pdfDao.update(it) }
}
suspend fun updateThumbnailPath(filePath: String, path: String) {
val document = pdfDao.getByPath(filePath)?.copy(
thumbnailPath = path,
)
document?.let { pdfDao.update(it) }
}
// 最近阅读相关操作
suspend fun addToRecent(pdfHash: String, page: Int = 0) {
val existing = recentDao.getByPdfHash(pdfHash)
suspend fun addToRecent(filePath: String, page: Int = 0) {
val existing = recentDao.getByPdfHash(filePath)
if (existing != null) {
recentDao.updateOpenTime(pdfHash)
recentDao.updateOpenTime(filePath)
} else {
recentDao.insertOrUpdate(
RecentReadEntity(
pdfHash = pdfHash,
filePath = filePath,
lastOpenedTime = System.currentTimeMillis()
)
)
@ -113,7 +121,7 @@ class PdfRepository private constructor(context: Context) {
// 组合查询
suspend fun getPdfWithDetails(pdfHash: String): Flow<PdfDetails> {
return combine(
pdfDao.getByPath(pdfHash)?.let { kotlinx.coroutines.flow.flowOf(it) }
pdfDao.getByHash(pdfHash)?.let { kotlinx.coroutines.flow.flowOf(it) }
?: kotlinx.coroutines.flow.flowOf(null),
bookmarkDao.getBookmarksByPdf(pdfHash),
noteDao.getNotesByPdf(pdfHash)
@ -144,11 +152,11 @@ class PdfRepository private constructor(context: Context) {
}
// 数据清理
suspend fun deleteDocument(fileHash: String) {
pdfDao.deleteByHash(fileHash)
recentDao.deleteByPdfHash(fileHash)
bookmarkDao.deleteAllByPdf(fileHash)
noteDao.deleteAllByPdf(fileHash)
suspend fun deleteDocument(filePath: String) {
pdfDao.deleteByPath(filePath)
recentDao.deleteByPdfPath(filePath)
bookmarkDao.deleteAllByPdf(filePath)
noteDao.deleteAllByPdf(filePath)
}
companion object {

View File

@ -10,16 +10,12 @@ import com.all.pdfreader.pro.app.PDFReaderApplication
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityMainBinding
import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment
import com.all.pdfreader.pro.app.ui.fragment.FavoriteFrag
import com.all.pdfreader.pro.app.ui.fragment.HomeFrag
import com.all.pdfreader.pro.app.ui.fragment.RecentlyFrag
import com.all.pdfreader.pro.app.ui.fragment.ToolsFrag
import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.model.SortField
import com.all.pdfreader.pro.app.model.SortDirection
import com.all.pdfreader.pro.app.ui.dialog.SortDialogFragment
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.FileChangeObserver
import com.all.pdfreader.pro.app.util.PdfScanner
import com.all.pdfreader.pro.app.util.StoragePermissionHelper
import com.gyf.immersionbar.ImmersionBar

View File

@ -123,7 +123,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
private fun saveReadingProgress() {
lifecycleScope.launch {
repository.updateReadingProgress(
pdfDocument.fileHash, pdfDocument.lastReadPage, pdfDocument.readingProgress
pdfDocument.filePath, pdfDocument.lastReadPage, pdfDocument.readingProgress
)
}
}

View File

@ -0,0 +1,128 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogListMoreBinding
import com.all.pdfreader.pro.app.databinding.DialogPermissionBinding
import com.all.pdfreader.pro.app.databinding.DialogSortBinding
import com.all.pdfreader.pro.app.model.SortConfig
import com.all.pdfreader.pro.app.model.SortDirection
import com.all.pdfreader.pro.app.model.SortField
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.sp.AppStore
import com.all.pdfreader.pro.app.ui.act.MainActivity.SortableFragment
import com.all.pdfreader.pro.app.util.AppUtils.dpToPx
import com.all.pdfreader.pro.app.util.AppUtils.setClickWithAnimation
import com.all.pdfreader.pro.app.util.FileUtils.toFormatFileSize
import com.all.pdfreader.pro.app.util.FileUtils.toSlashDate
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 kotlinx.coroutines.launch
class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() {
private lateinit var binding: DialogListMoreBinding
private val pdfRepository = PdfRepository.getInstance()
private val viewModel by lazy { ViewModelProvider(this)[PdfViewModel::class.java] }
private lateinit var pdfDocument: PdfDocumentEntity
private var isFavorite: Boolean = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = DialogListMoreBinding.inflate(layoutInflater)
return binding.root
}
override fun onStart() {
super.onStart()
dialog?.window?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
?.setBackgroundResource(R.drawable.dr_rounded_corner_12_bg_white)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.pdfDocument.observe(this) { document ->
document?.let {
pdfDocument = it
isFavorite = pdfDocument.isFavorite
initUi()
setupOnClick()
} ?: run {
showToast(getString(R.string.file_not))
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)
}
updateCollectUi(isFavorite)
}
private fun setupOnClick() {
binding.collectBtn.setClickWithAnimation(duration = 250) {
isFavorite = !isFavorite
updateCollectUi(isFavorite)
saveCollectState(isFavorite)
}
binding.renameFileBtn.setOnClickListener {
RenameDialogFragment(pdfDocument.filePath, onOkClick = {
}, onCancelClick = {
}).show(parentFragmentManager, "ListMoreDialogFragment")
dismiss()
}
}
private fun saveCollectState(b: Boolean) {
pdfDocument = pdfDocument.copy(
isFavorite = b
)
lifecycleScope.launch {
pdfRepository.updateFavoriteStatus(pdfDocument.filePath, pdfDocument.isFavorite)
}
if (b) {
showToast(getString(R.string.added_to_favorites))
} else {
showToast(getString(R.string.removed_from_favorites))
}
}
private fun updateCollectUi(b: Boolean) {
if (b) {
binding.collectIv.setImageResource(R.drawable.collected)
} else {
binding.collectIv.setImageResource(R.drawable.collect)
}
}
private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
}
}

View File

@ -2,7 +2,6 @@ package com.all.pdfreader.pro.app.ui.dialog
import android.graphics.Color
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -21,7 +20,6 @@ class PdfPasswordProtectionDialogFragment(
) : DialogFragment() {
private lateinit var binding: DialogPdfPasswordProtectionBinding
var isPasswordVisible = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@ -67,23 +65,5 @@ class PdfPasswordProtectionDialogFragment(
binding.tilPassword.error = getString(R.string.incorrect_password)
}
}
binding.showPasswordBtn.setOnClickListener {
isPasswordVisible = !isPasswordVisible
showOrHidePassword(isPasswordVisible)
}
}
private fun showOrHidePassword(b: Boolean) {
if (b) {
binding.etPassword.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
binding.showPasswordIv.setImageResource(R.drawable.show_password)
} else {
binding.etPassword.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
binding.showPasswordIv.setImageResource(R.drawable.hide_password)
}
// 保持光标在末尾
binding.etPassword.setSelection(binding.etPassword.text?.length ?: 0)
}
}

View File

@ -3,18 +3,15 @@ package com.all.pdfreader.pro.app.ui.dialog
import android.graphics.Color
import android.os.Bundle
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogPdfSetPasswordBinding
import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.google.android.material.textfield.TextInputEditText
class PdfSetPasswordDialog(
private val onCancelled: () -> Unit, private val onPasswordSet: (String) -> Unit
@ -22,8 +19,6 @@ class PdfSetPasswordDialog(
) {
private lateinit var binding: DialogPdfSetPasswordBinding
private var isEnterPasswordVisible = false
private var isConfirmPasswordVisible = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@ -66,23 +61,6 @@ class PdfSetPasswordDialog(
dismiss()
}
}
binding.enterShowPasswordBtn.setOnClickListener {
isEnterPasswordVisible = !isEnterPasswordVisible
showOrHidePassword(
binding.etPassword,
binding.enterShowPasswordIv,
isEnterPasswordVisible
)
}
binding.confirmShowPasswordBtn.setOnClickListener {
isConfirmPasswordVisible = !isConfirmPasswordVisible
showOrHidePassword(
binding.etConfirmPassword,
binding.confirmShowPasswordIv,
isConfirmPasswordVisible
)
}
}
private fun setupTextWatchers() {
@ -140,17 +118,4 @@ class PdfSetPasswordDialog(
return true
}
private fun showOrHidePassword(inputView: TextInputEditText, imageView: ImageView, b: Boolean) {
if (b) {
inputView.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
imageView.setImageResource(R.drawable.show_password)
} else {
inputView.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
imageView.setImageResource(R.drawable.hide_password)
}
// 保持光标在末尾
inputView.setSelection(inputView.text?.length ?: 0)
}
}

View File

@ -0,0 +1,135 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.graphics.Color
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogPdfPasswordProtectionBinding
import com.all.pdfreader.pro.app.databinding.DialogRenameFileBinding
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.all.pdfreader.pro.app.util.FileUtils.isPdfPasswordCorrect
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import java.io.File
class RenameDialogFragment(
private val filePath: String,
private val onOkClick: () -> Unit,
private val onCancelClick: () -> Unit
) : DialogFragment() {
private lateinit var binding: DialogRenameFileBinding
private val viewModel by lazy { ViewModelProvider(this)[PdfViewModel::class.java] }
private lateinit var pdfDocument: PdfDocumentEntity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = DialogRenameFileBinding.inflate(layoutInflater)
return binding.root
}
override fun onStart() {
super.onStart()
dialog?.window?.apply {
// 去掉系统默认的背景 padding
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
// 设置宽度为全屏减去 16dp
val margin = resources.getDimensionPixelSize(R.dimen.dialog_margin) // 16dp
val width = resources.displayMetrics.widthPixels - margin * 2
setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.pdfDocument.observe(this) { document ->
document?.let {
pdfDocument = it
initView()
setupOnClick()
} ?: run {
showToast(getString(R.string.file_not))
dismiss()
}
}
viewModel.getPDFDocument(filePath)
}
private fun initView() {
binding.etName.showKeyboard()
binding.etName.setText(pdfDocument.fileName)
// 保持光标在末尾
binding.etName.setSelection(binding.etName.text?.length ?: 0)
}
private fun setupOnClick() {
binding.tvCancel.setOnClickListener {
onCancelClick()
dismiss()
}
binding.tvConfirm.setOnClickListener {
val text = binding.etName.text.toString()
if (validateEnter(text)) {
}
}
}
private fun validateEnter(name: String): Boolean {
// 不允许为空
if (name.isBlank()) {
binding.tilName.error = getString(R.string.name_not_empty)
return false
}
// 名字未做修改
if (name == pdfDocument.fileName) {
binding.tilName.error = getString(R.string.name_not_changed)
return false
}
// 含有非法字符
val invalidChars = "[/\\\\:*?\"<>|]".toRegex()
if (invalidChars.containsMatchIn(name)) {
binding.tilName.error = getString(R.string.name_invalid_chars)
return false
}
// 长度过长
if (name.length > 255) {
binding.tilName.error = getString(R.string.name_too_long)
return false
}
// 与现有文件重名
val parentDir = File(pdfDocument.filePath).parentFile
if (parentDir != null && File(parentDir, name).exists()) {
binding.tilName.error = getString(R.string.name_already_exists)
return false
}
// 禁止开头/结尾空格
if (name != name.trim()) {
binding.tilName.error = getString(R.string.name_start_end_space)
return false
}
return true
}
private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
}
}

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogPermissionBinding
import com.all.pdfreader.pro.app.databinding.DialogSortBinding
import com.all.pdfreader.pro.app.model.SortConfig
@ -30,6 +31,12 @@ class SortDialogFragment(
return binding.root
}
override fun onStart() {
super.onStart()
dialog?.window?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
?.setBackgroundResource(R.drawable.dr_rounded_corner_12_bg_white)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val appStore = AppStore(requireActivity())

View File

@ -15,6 +15,7 @@ import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.ui.act.MainActivity
import com.all.pdfreader.pro.app.ui.act.PdfViewActivity
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment
import com.all.pdfreader.pro.app.util.PdfScanner
import kotlinx.coroutines.launch
@ -40,7 +41,7 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment {
val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath)
startActivity(intent)
}, onMoreClick = { pdf ->
ListMoreDialogFragment(pdf.filePath).show(parentFragmentManager, TAG)
})
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())

View File

@ -14,7 +14,11 @@ object AppUtils {
*
* @param onClick 点击后立即执行的逻辑
*/
fun View.setClickWithAnimation(onClick: () -> Unit) {
fun View.setClickWithAnimation(
scaleFactor: Float = 1.2f,
duration: Long = 150,
onClick: () -> Unit
) {
this.setOnClickListener {
// 禁用点击,防止动画未完成重复点击
this.isEnabled = false
@ -24,14 +28,14 @@ object AppUtils {
// 播放动画
this.animate()
.scaleX(1.2f)
.scaleY(1.2f)
.setDuration(150)
.scaleX(scaleFactor)
.scaleY(scaleFactor)
.setDuration(duration)
.withEndAction {
this.animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(150)
.setDuration(duration)
.withEndAction {
// 动画结束恢复点击
this.isEnabled = true

View File

@ -237,6 +237,9 @@ object FileUtils {
}
}
/**
* 计算文件哈希
*/
fun calculateFileHash(filePath: String): String? {
return try {
val file = File(filePath)

View File

@ -2,9 +2,7 @@ package com.all.pdfreader.pro.app.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.core.graphics.createBitmap
@ -12,7 +10,10 @@ import com.all.pdfreader.pro.app.PDFReaderApplication
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.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
@ -29,7 +30,9 @@ class PdfScanner(
suspend fun scanAndLoadPdfFiles(isNeedFullScan: Boolean, callback: (Boolean) -> Unit = {}) {
if (!StoragePermissionHelper.hasBasicStoragePermission(context)) {
LogUtil.logDebug(TAG, "权限不足")
withContext(Dispatchers.Main) {
callback.invoke(false)
}
return
}
scanMutex.withLock {// 保证同一时间只有一次扫描
@ -64,23 +67,30 @@ class PdfScanner(
LogUtil.logDebug(TAG, "密码状态变化 -> ${doc.fileName}")
updatedDoc = updatedDoc.copy(isPassword = currentIsPassword)
}
// 缩略图(仅非加密文件)
if (!currentIsPassword) {
val newThumbnail = generateThumbnail(context, file)
if (doc.thumbnailPath != newThumbnail) {
updatedDoc =
updatedDoc.copy(thumbnailPath = newThumbnail)
}
} else if (doc.thumbnailPath != null) {
updatedDoc = updatedDoc.copy(thumbnailPath = null)
}
pdfRepository.insertOrUpdateDocument(updatedDoc)
LogUtil.logDebug(TAG, "✅数据库已更新: ${doc.fileName}")
if (!currentIsPassword) {
// 异步生成缩略图,但要避免阻塞
launch(Dispatchers.IO) {
LogUtil.logDebug(TAG, "异步获取图片更新数据")
val newThumbnail = generateFastThumbnail(context, file)
if (newThumbnail != null && doc.thumbnailPath != newThumbnail) {
pdfRepository.updateThumbnailPath(doc.filePath, newThumbnail)
LogUtil.logDebug(TAG, "✅ 缩略图已更新")
}
}
} else if (doc.thumbnailPath != null) {
val updatedDocWithoutThumb =
updatedDoc.copy(thumbnailPath = null)
pdfRepository.insertOrUpdateDocument(updatedDocWithoutThumb)
LogUtil.logDebug(TAG, "✅图片为Null: ${doc.fileName}")
}
}
} else {
// 文件不存在 → 删除数据库记录,并触发全盘扫描
LogUtil.logDebug(TAG, "文件不存在 -> ${doc.fileName}, 删除记录")
pdfRepository.deleteDocument(doc.fileHash)
pdfRepository.deleteDocument(doc.filePath)
needFullScan = true
}
}
@ -103,12 +113,7 @@ class PdfScanner(
TAG,
"🔄处理文件 ${index + 1}/${allFiles.size}: ${file.name} - ${file.absolutePath}"
)
if (FileUtils.isPdfFile(file)) {
val fileHash = FileUtils.calculateFileHash(file.absolutePath)
LogUtil.logDebug(TAG, "🔑文件哈希: $fileHash")
if (fileHash != null) {
val existingDoc =
pdfRepository.getDocumentByPath(file.absolutePath)
@ -116,23 +121,18 @@ class PdfScanner(
LogUtil.logDebug(
TAG, "🆕发现新PDF文件: ${file.name}"
)
var thumbnailPath: String? = null
val isPassword = isPdfEncrypted(file)
LogUtil.logDebug(TAG, "isPassword->${isPassword}")
if (!isPassword) {//没有密码的情况下才去获取缩略图
thumbnailPath = generateThumbnail(context, file) ?: ""
}
LogUtil.logDebug(TAG, "thumbnailPath->${thumbnailPath}")
val metadata =
PdfMetadataExtractor.extractMetadata(file.absolutePath)
val document = PdfDocumentEntity(
fileHash = fileHash,
filePath = file.absolutePath,
fileName = file.name,
fileSize = file.length(),
lastModified = file.lastModified(),
pageCount = metadata?.pageCount ?: 0,
thumbnailPath = thumbnailPath,
thumbnailPath = null,
metadataTitle = metadata?.title,
metadataAuthor = metadata?.author,
metadataSubject = metadata?.subject,
@ -142,9 +142,17 @@ class PdfScanner(
isPassword = isPassword
)
pdfRepository.insertOrUpdateDocument(document)
LogUtil.logDebug(
TAG, " ✅ 已保存到数据库: ${file.name}"
)
LogUtil.logDebug(TAG, " ✅ 已保存到数据库: ${file.name}")
if (!isPassword) {//没有密码的情况下才去获取缩略图
launch(Dispatchers.IO){
val newThumbnail = generateFastThumbnail(context, file)
if (newThumbnail != null && document.thumbnailPath != newThumbnail) {
pdfRepository.updateThumbnailPath(document.filePath, newThumbnail)
LogUtil.logDebug(TAG, "✅ 缩略图已更新")
}
}
}
} else {
LogUtil.logDebug(TAG, " 📋 文件已存在: ${file.name}")
// 🔹 文件已存在,检查是否需要更新
@ -170,31 +178,27 @@ class PdfScanner(
needUpdate = true
}
if (!currentIsPassword) {
// 如果不是加密 PDF再生成缩略图
val newThumbnail = generateThumbnail(context, file)
if (existingDoc.thumbnailPath != newThumbnail) {
LogUtil.logDebug(TAG, "✅ 缩略图需要更新")
updatedDoc =
updatedDoc.copy(thumbnailPath = newThumbnail)
needUpdate = true
}
} else {
updatedDoc = updatedDoc.copy(thumbnailPath = null)
needUpdate = true
}
// 执行更新
if (needUpdate) {
pdfRepository.insertOrUpdateDocument(updatedDoc)
LogUtil.logDebug(
TAG, "✅ 数据库已更新: ${file.name}"
)
LogUtil.logDebug(TAG, "✅ 数据库已更新: ${file.name}")
} else {
LogUtil.logDebug(
TAG, "⏩ 无需更新: ${file.name}"
)
LogUtil.logDebug(TAG, "⏩ 无需更新: ${file.name}")
}
// 处理缩略图
if (!currentIsPassword) {
launch(Dispatchers.IO) {
LogUtil.logDebug(TAG, "异步获取图片更新数据")
val newThumbnail = generateFastThumbnail(context, file)
if (newThumbnail != null && existingDoc.thumbnailPath != newThumbnail) {
pdfRepository.updateThumbnailPath(existingDoc.filePath, newThumbnail)
LogUtil.logDebug(TAG, "✅ 缩略图已更新")
}
}
} else {
val noThumbDoc = updatedDoc.copy(thumbnailPath = null)
pdfRepository.insertOrUpdateDocument(noThumbDoc)
}
}
}
@ -202,7 +206,8 @@ class PdfScanner(
// 打印数据库中的总记录数
pdfRepository.getAllDocumentsOnce().forEach { doc ->
LogUtil.logDebug(
TAG, " 📖 ${doc.fileName} - ${doc.pageCount}页 - ${
TAG,
" 📖 ${doc.fileName} - ${doc.filePath} - ${doc.pageCount}页 - ${
FileUtils.formatFileSize(
doc.fileSize
)
@ -229,14 +234,18 @@ class PdfScanner(
TAG, "$string 本次扫描耗时: $scannerTime ms (${scannerTime / 1000.0} 秒)"
)
PDFReaderApplication.isNeedFullScan = false
withContext(Dispatchers.Main) {
callback.invoke(true)
}
} catch (e: Exception) {
Log.e(TAG, "❌ 扫描出错: ${e.message}", e)
withContext(Dispatchers.Main) {
callback.invoke(false)
}
}
}
}
}
fun shouldScan(): Boolean {
return ScanManager.shouldScan(context)
@ -252,40 +261,87 @@ class PdfScanner(
}
private fun generateThumbnail(context: Context, pdfFile: File): String? {
// private fun generateFastThumbnail(context: Context, pdfFile: File): String? {
// return try {
// val fileDescriptor =
// ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
// val pdfRenderer = PdfRenderer(fileDescriptor)
//
// if (pdfRenderer.pageCount > 0) {
// val page = pdfRenderer.openPage(0)
// // 创建 Bitmap
// val bitmap = createBitmap(page.width, page.height)
// val canvas = Canvas(bitmap)
// canvas.drawColor(Color.WHITE) // 填充白色背景
//
// page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
// page.close()
//
// // 保存到缓存目录
// val cacheDir = File(context.cacheDir, "thumbnails")
// if (!cacheDir.exists()) cacheDir.mkdirs()
// val thumbFile = File(cacheDir, pdfFile.nameWithoutExtension + ".jpg")
//
// FileOutputStream(thumbFile).use { out ->
// bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
// }
//
// pdfRenderer.close()
// fileDescriptor.close()
//
// thumbFile.absolutePath
// } else {
// pdfRenderer.close()
// fileDescriptor.close()
// null
// }
// } catch (e: Exception) {
// e.printStackTrace()
// null
// }
// }
private fun generateFastThumbnail(
context: Context,
pdfFile: File,
maxWidth: Int = 200,
maxHeight: Int = 300
): String? {
return try {
val fileDescriptor =
ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfRenderer = PdfRenderer(fileDescriptor)
val pdfiumCore = PdfiumCore(context)
val fd = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfDocument: PdfDocument = pdfiumCore.newDocument(fd)
if (pdfRenderer.pageCount > 0) {
val page = pdfRenderer.openPage(0)
// 创建 Bitmap
val bitmap = createBitmap(page.width, page.height)
val canvas = Canvas(bitmap)
canvas.drawColor(Color.WHITE) // 填充白色背景
// 打开第一页
pdfiumCore.openPage(pdfDocument, 0)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
page.close()
// 获取原始页宽高
val width = pdfiumCore.getPageWidthPoint(pdfDocument, 0)
val height = pdfiumCore.getPageHeightPoint(pdfDocument, 0)
// 保存到缓存目录
// 计算缩略图尺寸
val scale = minOf(maxWidth.toFloat() / width, maxHeight.toFloat() / height)
val thumbWidth = (width * scale).toInt()
val thumbHeight = (height * scale).toInt()
val bitmap = createBitmap(thumbWidth, thumbHeight)
bitmap.eraseColor(Color.WHITE) // 白底
// 渲染第一页到 Bitmap
pdfiumCore.renderPageBitmap(pdfDocument, bitmap, 0, 0, 0, thumbWidth, thumbHeight)
// 保存到缓存
val cacheDir = File(context.cacheDir, "thumbnails")
if (!cacheDir.exists()) cacheDir.mkdirs()
val thumbFile = File(cacheDir, pdfFile.nameWithoutExtension + ".jpg")
FileOutputStream(thumbFile).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
}
pdfRenderer.close()
fileDescriptor.close()
pdfiumCore.closeDocument(pdfDocument)
fd.close()
thumbFile.absolutePath
} else {
pdfRenderer.close()
fileDescriptor.close()
null
}
} catch (e: Exception) {
e.printStackTrace()
null

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="M859.7,253.9c-44.8,-44.8 -102.4,-70.4 -166.4,-70.4 -61.9,0 -121.6,25.6 -166.4,70.4l-14.9,17.1 -17.1,-17.1c-44.8,-44.8 -102.4,-70.4 -166.4,-70.4 -61.9,0 -121.6,25.6 -166.4,70.4 -91.7,91.7 -91.7,243.2 0,337.1l324.3,330.7c6.4,6.4 14.9,8.5 23.5,8.5s17.1,-4.3 23.5,-8.5l324.3,-330.7c44.8,-44.8 68.3,-104.5 68.3,-168.5s-21.3,-123.7 -66.1,-168.5zM814.9,544L512,853.3 209.1,544c-66.1,-68.3 -66.1,-179.2 0,-247.5 32,-32 74.7,-51.2 119.5,-51.2 44.8,0 87.5,17.1 119.5,51.2l38.4,40.5c12.8,12.8 34.1,12.8 44.8,0l38.4,-40.5c32,-32 74.7,-51.2 119.5,-51.2 44.8,0 87.5,17.1 119.5,51.2 32,32 49.1,76.8 49.1,123.7s-12.8,91.7 -42.7,123.7z"
android:fillColor="#666666"/>
</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="M669.8,130.8c71.6,-11.1 138.9,11.5 193.3,64.5 55.3,53.9 81.8,125 74.3,199.5 -7.5,73.6 -46.5,146.4 -112.3,210.5 -18.3,17.9 -67.7,66.2 -138.5,135.6 -31.8,31.2 -65.7,64.4 -99.8,98L553.6,871.5l-13.2,12.9a40.6,40.6 0,0 1,-56.8 0l-114.6,-112.6 -24.2,-23.7a677626.4,677626.4 0,0 0,-145.9 -142.8C133.1,541.2 94.1,468.5 86.6,394.8c-7.6,-74.5 18.9,-145.6 74.3,-199.5 54.4,-53.1 121.7,-75.6 193.3,-64.5 53.2,8.2 107.1,34.7 157.8,76.9 50.7,-42.2 104.6,-68.7 157.8,-76.9z"
android:fillColor="#F1494F"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M874.7,202.7L360.5,202.7c-21.3,0 -40.5,8.5 -55.5,23.5l-217.6,234.7c-25.6,27.7 -25.6,72.5 0,100.3l217.6,234.7c14.9,14.9 34.1,23.5 55.5,23.5L874.7,819.2c40.5,0 74.7,-34.1 74.7,-74.7L949.3,277.3c0,-40.5 -34.1,-74.7 -74.7,-74.7zM885.3,746.7c0,6.4 -4.3,10.7 -10.7,10.7L360.5,757.3c-2.1,0 -6.4,-2.1 -8.5,-4.3l-217.6,-234.7c-4.3,-4.3 -4.3,-10.7 0,-14.9l217.6,-234.7c2.1,-2.1 4.3,-4.3 8.5,-4.3L874.7,264.5c6.4,0 10.7,4.3 10.7,10.7L885.3,746.7z"
android:fillColor="#666666"/>
<path
android:pathData="M684.8,403.2c-12.8,-12.8 -32,-12.8 -44.8,0l-64,64 -61.9,-61.9c-12.8,-12.8 -32,-12.8 -44.8,0 -12.8,12.8 -12.8,32 0,44.8l61.9,61.9 -61.9,61.9c-12.8,12.8 -12.8,32 0,44.8 6.4,6.4 14.9,8.5 23.5,8.5s17.1,-2.1 23.5,-8.5l61.9,-61.9L640,618.7c6.4,6.4 14.9,8.5 23.5,8.5s17.1,-2.1 23.5,-8.5c12.8,-12.8 12.8,-32 0,-44.8L620.8,512l61.9,-61.9c12.8,-12.8 12.8,-34.1 2.1,-46.9z"
android:fillColor="#666666"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="4dp" />
<size
android:width="24dp"
android:height="4dp" />
<solid android:color="@color/line_color" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/show_password" android:state_checked="true"/>
<item android:drawable="@drawable/hide_password"/>
</selector>

View File

@ -0,0 +1,12 @@
<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="M853.3,224h-53.3L800,170.7c0,-40.5 -34.1,-74.7 -74.7,-74.7L170.7,96C130.1,96 96,130.1 96,170.7v554.7c0,40.5 34.1,74.7 74.7,74.7h53.3L224,853.3c0,40.5 34.1,74.7 74.7,74.7h554.7c40.5,0 74.7,-34.1 74.7,-74.7L928,298.7c0,-40.5 -34.1,-74.7 -74.7,-74.7zM160,725.3L160,170.7c0,-6.4 4.3,-10.7 10.7,-10.7h554.7c6.4,0 10.7,4.3 10.7,10.7v554.7c0,6.4 -4.3,10.7 -10.7,10.7L170.7,736c-6.4,0 -10.7,-4.3 -10.7,-10.7zM864,853.3c0,6.4 -4.3,10.7 -10.7,10.7L298.7,864c-6.4,0 -10.7,-4.3 -10.7,-10.7v-53.3L725.3,800c40.5,0 74.7,-34.1 74.7,-74.7L800,288L853.3,288c6.4,0 10.7,4.3 10.7,10.7v554.7z"
android:fillColor="#666666"/>
<path
android:pathData="M576,416h-96V320c0,-17.1 -14.9,-32 -32,-32s-32,14.9 -32,32v96H320c-17.1,0 -32,14.9 -32,32s14.9,32 32,32h96V576c0,17.1 14.9,32 32,32s32,-14.9 32,-32v-96H576c17.1,0 32,-14.9 32,-32s-14.9,-32 -32,-32z"
android:fillColor="#666666"/>
</vector>

View File

@ -0,0 +1,12 @@
<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="#666666"/>
<path
android:pathData="M640,586.7H384c-17.1,0 -32,14.9 -32,32s14.9,32 32,32h256c17.1,0 32,-14.9 32,-32s-14.9,-32 -32,-32z"
android:fillColor="#666666"/>
</vector>

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="366.8dp"
android:height="256dp"
android:width="24dp"
android:height="17dp"
android:viewportWidth="1467"
android:viewportHeight="1024">
<path
android:pathData="M299.1,18.5l917.2,917.2a34.1,34.1 0,0 1,-48.3 48.3L250.9,66.7A34.1,34.1 0,1 1,299.1 18.5zM324.6,188.7l50.6,50.6a918.1,918.1 0,0 0,-113.9 67.5,829.3 829.3,0 0,0 -93.1,75.2l-14.1,13.6c-4.5,4.5 -8.9,8.9 -13.1,13.3l-12,12.9A438.3,438.3 0,0 0,99.7 457.4l-7.3,10.6c-10,15.2 -15.5,27.6 -15.9,35.9 -0.6,15.4 23.2,51.4 64.1,93.3l12.7,12.8c28.7,27.9 64.2,57.8 104.4,85.5 140.2,96.5 308.7,155.6 475.9,155.6 75.1,0 150,-11 222.1,-31.2l54.7,54.7c-85.3,27.7 -178.8,44.7 -276.8,44.7 -256.6,0 -483.8,-126.3 -612.9,-246l-13.2,-12.6c-12.9,-12.6 -24.6,-25 -35.2,-37.1l-10.1,-12a468.4,468.4 0,0 1,-4.8 -5.9l-8.9,-11.7C21.6,557.3 7.3,524.7 8.3,501.2c0.9,-21.5 13.8,-50.7 37,-83.5l8.6,-11.7 9.5,-12 5.1,-6.1 10.7,-12.4 5.6,-6.2 11.9,-12.6 12.6,-12.8c4.4,-4.3 8.8,-8.6 13.4,-12.9l14.1,-12.9c12.1,-10.8 24.9,-21.6 38.4,-32.3l16.6,-12.8a976.1,976.1 0,0 1,132.7 -84.2zM733.6,83c400.6,0 725.3,284.8 725.3,418.1 0,42.4 -32.9,100.2 -90.6,159.2l-12.8,12.7 -6.7,6.3 -13.9,12.7c-4.7,4.2 -9.6,8.4 -14.6,12.6l-15.3,12.6c-5.2,4.2 -10.5,8.3 -16,12.5l-16.6,12.4a929.4,929.4 0,0 1,-125.5 76.1l-51,-50.9a880.5,880.5 0,0 0,112.4 -63.8c58.2,-39 106.9,-82.3 140.3,-123.6 28.1,-34.7 41.9,-64.1 41.9,-78.7 0,-14.6 -13.8,-44 -41.8,-78.7 -33.4,-41.3 -82.1,-84.7 -140.3,-123.6 -136.9,-91.6 -305.5,-147.6 -474.9,-147.6 -74.3,0 -148.5,11.7 -220.2,33.2l-54.1,-54.2c84.4,-29.1 177,-47.2 274.4,-47.2zM536.7,400.8l52,52a152.7,152.7 0,0 0,193.2 193.2l52,52.1a220.9,220.9 0,0 1,-297.2 -297.3zM733.6,280.2a220.9,220.9 0,0 1,196.9 321.3L878.5,549.5a152.7,152.7 0,0 0,-193.2 -193.2L633.2,304.3a220,220 0,0 1,100.4 -24.1z"
android:fillColor="#bfbfbf"/>
android:fillColor="#666666"/>
</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="M819.2,364.8h-44.8L774.4,128c0,-17.1 -14.9,-32 -32,-32L281.6,96c-17.1,0 -32,14.9 -32,32v236.8L204.8,364.8c-59.7,0 -108.8,49.1 -108.8,108.8v192c0,59.7 49.1,108.8 108.8,108.8h44.8L249.6,896c0,17.1 14.9,32 32,32h460.8c17.1,0 32,-14.9 32,-32v-121.6h44.8c59.7,0 108.8,-49.1 108.8,-108.8v-192c0,-59.7 -49.1,-108.8 -108.8,-108.8zM313.6,160h396.8v204.8L313.6,364.8L313.6,160zM710.4,864L313.6,864L313.6,620.8h396.8v243.2zM864,665.6c0,25.6 -19.2,44.8 -44.8,44.8h-44.8v-121.6c0,-17.1 -14.9,-32 -32,-32L281.6,556.8c-17.1,0 -32,14.9 -32,32v121.6L204.8,710.4c-25.6,0 -44.8,-19.2 -44.8,-44.8v-192c0,-25.6 19.2,-44.8 44.8,-44.8h614.4c25.6,0 44.8,19.2 44.8,44.8v192z"
android:fillColor="#666666"/>
</vector>

View File

@ -0,0 +1,15 @@
<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="M512,74.7C270.9,74.7 74.7,270.9 74.7,512S270.9,949.3 512,949.3 949.3,753.1 949.3,512 753.1,74.7 512,74.7zM512,885.3c-204.8,0 -373.3,-168.5 -373.3,-373.3S307.2,138.7 512,138.7 885.3,307.2 885.3,512 716.8,885.3 512,885.3z"
android:fillColor="#666666"/>
<path
android:pathData="M512,320m-42.7,0a42.7,42.7 0,1 0,85.3 0,42.7 42.7,0 1,0 -85.3,0Z"
android:fillColor="#666666"/>
<path
android:pathData="M512,437.3c-17.1,0 -32,14.9 -32,32v234.7c0,17.1 14.9,32 32,32s32,-14.9 32,-32V469.3c0,-17.1 -14.9,-32 -32,-32z"
android:fillColor="#666666"/>
</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="M853.3,138.7H170.7c-17.1,0 -32,14.9 -32,32v128c0,17.1 14.9,32 32,32s32,-14.9 32,-32V202.7h277.3v618.7H384c-17.1,0 -32,14.9 -32,32s14.9,32 32,32h256c17.1,0 32,-14.9 32,-32s-14.9,-32 -32,-32h-96v-618.7h277.3V298.7c0,17.1 14.9,32 32,32s32,-14.9 32,-32V170.7c0,-17.1 -14.9,-32 -32,-32z"
android:fillColor="#666666"/>
</vector>

View File

@ -0,0 +1,12 @@
<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="M874.7,544c-17.1,0 -32,14.9 -32,32v256c0,6.4 -4.3,10.7 -10.7,10.7H192c-6.4,0 -10.7,-4.3 -10.7,-10.7V192c0,-6.4 4.3,-10.7 10.7,-10.7h256c17.1,0 32,-14.9 32,-32s-14.9,-32 -32,-32H192C151.5,117.3 117.3,151.5 117.3,192v640c0,40.5 34.1,74.7 74.7,74.7h640c40.5,0 74.7,-34.1 74.7,-74.7V576c0,-17.1 -14.9,-32 -32,-32z"
android:fillColor="#666666"/>
<path
android:pathData="M874.7,117.3H640c-17.1,0 -32,14.9 -32,32s14.9,32 32,32h157.9L509.9,467.2c-12.8,12.8 -12.8,32 0,44.8 6.4,6.4 14.9,8.5 23.5,8.5s17.1,-2.1 23.5,-8.5l285.9,-285.9V384c0,17.1 14.9,32 32,32s32,-14.9 32,-32V149.3c0,-17.1 -14.9,-32 -32,-32z"
android:fillColor="#666666"/>
</vector>

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="433dp"
android:height="256dp"
android:width="24dp"
android:height="14dp"
android:viewportWidth="1732"
android:viewportHeight="1024">
<path
android:pathData="M872.8,34.5c462.2,0 836.9,328.7 836.9,482.5 0,49 -37.9,115.6 -104.5,183.7l-14.7,14.6 -7.7,7.3 -16,14.6c-5.5,4.9 -11.1,9.7 -16.8,14.6l-17.6,14.5c-6,4.8 -12.2,9.6 -18.4,14.4l-19.2,14.3c-153.2,110.8 -375.1,204.5 -622,204.5 -296,0 -558.2,-145.7 -707.2,-283.9l-15.3,-14.5c-14.8,-14.5 -28.4,-28.8 -40.6,-42.8l-11.7,-13.9a540.4,540.4 0,0 1,-5.5 -6.9l-10.3,-13.5C51.2,581.7 34.8,544.1 35.8,516.9c1,-24.9 16,-58.4 42.7,-96.4l10,-13.5 10.9,-13.9c1.9,-2.4 3.8,-4.7 5.8,-7.1l12.3,-14.3 6.5,-7.2 13.7,-14.6 14.6,-14.7c5,-4.9 10.2,-9.9 15.5,-14.8l16.3,-14.9c13.9,-12.4 28.8,-24.9 44.3,-37.3l19.1,-14.8c152.8,-115.4 375.5,-219.1 625.2,-219.1zM872.8,113.2c-192.4,0 -384.2,67.9 -544.9,179.4a957,957 0,0 0,-107.4 86.7l-16.3,15.6c-5.2,5.2 -10.3,10.3 -15.1,15.4l-13.9,14.9a505.7,505.7 0,0 0,-33.8 41.2l-8.4,12.2c-11.5,17.6 -18,31.9 -18.4,41.5 -0.7,17.7 26.9,59.2 74,107.6l14.7,14.7c33.1,32.2 74,66.6 120.5,98.6 161.8,111.3 356.2,179.6 549.1,179.6 195.5,0 390.1,-64.5 548,-170.2 67.2,-45 123.4,-95 161.9,-142.7 32.3,-40 48.3,-74 48.3,-90.8 0,-16.8 -16,-50.7 -48.2,-90.8 -38.5,-47.7 -94.7,-97.7 -161.9,-142.7 -157.9,-105.7 -352.5,-170.3 -548,-170.3zM872.8,262a254.9,254.9 0,1 1,0 509.9,254.9 254.9,0 0,1 0,-509.9zM872.8,340.8a176.2,176.2 0,1 0,0 352.4,176.2 176.2,0 0,0 0,-352.4z"
android:fillColor="#bfbfbf"/>
android:fillColor="#666666"/>
</vector>

View File

@ -0,0 +1,293 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center">
<View
android:layout_width="32dp"
android:layout_height="4dp"
android:background="@drawable/dr_dialog_indicator_bg" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/dr_item_img_frame">
<ImageView
android:id="@+id/tvFileImg"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@mipmap/ic_launcher_round" />
<LinearLayout
android:id="@+id/lock_layout"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/dr_item_lock_bg"
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/lock" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tvFileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:fontFamily="@font/poppins_medium"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/grey_text_color"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tvFileDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_regular"
android:text="@string/app_name"
android:textColor="@color/black_60"
android:textSize="14sp" />
<TextView
android:id="@+id/tvFileSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/app_name"
android:textColor="@color/black_60"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/collectBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:gravity="center">
<ImageView
android:id="@+id/collectIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/collect" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/renameFileBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/rename_text" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/rename_file"
android:textColor="@color/grey_text_color"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/prompt" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/file_details"
android:textColor="@color/grey_text_color"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/share" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/share_file"
android:textColor="@color/grey_text_color"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/print" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/print_pdf"
android:textColor="@color/grey_text_color"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="@color/line_color" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical">
<LinearLayout
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/duplicate" />
<TextView
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" />
</LinearLayout>
<LinearLayout
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/lock" />
<TextView
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" />
</LinearLayout>
<LinearLayout
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/file_delete" />
<TextView
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" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:hint="@string/enter_password"
app:endIconMode="password_toggle"
app:passwordToggleEnabled="true">
app:endIconDrawable="@drawable/dr_password_state">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
@ -50,21 +50,6 @@
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/showPasswordBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignEnd="@id/tilPassword"
android:layout_centerVertical="true"
android:gravity="center">
<ImageView
android:id="@+id/showPasswordIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/hide_password" />
</LinearLayout>
</RelativeLayout>

View File

@ -37,6 +37,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/enter_password"
app:endIconDrawable="@drawable/dr_password_state"
app:endIconMode="password_toggle"
app:passwordToggleEnabled="true">
@ -50,22 +51,6 @@
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/enterShowPasswordBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignEnd="@id/tilPassword"
android:layout_centerVertical="true"
android:gravity="center">
<ImageView
android:id="@+id/enterShowPasswordIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/hide_password" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
@ -78,6 +63,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/confirm_password"
app:endIconDrawable="@drawable/dr_password_state"
app:endIconMode="password_toggle"
app:passwordToggleEnabled="true">
@ -91,22 +77,6 @@
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/confirmShowPasswordBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignEnd="@id/tilConfirmPassword"
android:layout_centerVertical="true"
android:gravity="center">
<ImageView
android:id="@+id/confirmShowPasswordIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/hide_password" />
</LinearLayout>
</RelativeLayout>
<LinearLayout

View File

@ -0,0 +1,72 @@
<?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:background="@drawable/dr_rounded_corner_12_bg_white"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="@font/poppins_semibold"
android:text="@string/rename_file"
android:textColor="@color/black"
android:textSize="20sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/enter_name"
app:endIconDrawable="@drawable/delete"
app:endIconMode="clear_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_regular"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:fontFamily="@font/poppins_regular"
android:padding="12dp"
android:text="@string/cancel"
android:textColor="@color/black_80" />
<TextView
android:id="@+id/tvConfirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_medium"
android:padding="12dp"
android:text="@string/ok"
android:textColor="@color/black" />
</LinearLayout>
</LinearLayout>

View File

@ -1,9 +1,7 @@
<?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:background="@drawable/dr_rounded_corner_12_bg_white"
android:orientation="vertical">
<TextView

View File

@ -13,4 +13,5 @@
<color name="bg_color">#F6F6F6</color>
<color name="line_color">#E0E0E0</color>
<color name="black_img_color">#2c2c2c</color>
<color name="grey_text_color">#666666</color>
</resources>

View File

@ -39,4 +39,21 @@
<string name="pdf_loading_failed">PDF loading failed</string>
<string name="password_too_short">Password must be at least 4 characters</string>
<string name="password_not_match">Passwords do not match</string>
<string name="rename_file">Rename</string>
<string name="file_details">Details</string>
<string name="share_file">Share</string>
<string name="print_pdf">Print</string>
<string name="added_to_favorites">Added to Favorite</string>
<string name="removed_from_favorites">Removed from Favorites</string>
<string name="delete_file">Delete File</string>
<string name="set_password">Set Password</string>
<string name="remove_password">Remove Password</string>
<string name="duplicate_file">Duplicate File</string>
<string name="enter_name">Enter a name</string>
<string name="name_not_empty">File name cannot be empty</string>
<string name="name_not_changed">File name has not been changed</string>
<string name="name_invalid_chars">File name contains invalid characters: / \ : * ? " &lt; &gt; |</string>
<string name="name_too_long">File name is too long (max 255 characters)</string>
<string name="name_already_exists">A file with the same name already exists</string>
<string name="name_start_end_space">File name cannot start or end with space</string>
</resources>