添加删除文件操作,优化收藏文件操作,优化viewmodel数据共享。

This commit is contained in:
ocean 2025-09-11 11:46:14 +08:00
parent c5c9785a96
commit 82270adb07
14 changed files with 530 additions and 56 deletions

View File

@ -0,0 +1,17 @@
package com.all.pdfreader.pro.app.model
/**
* 文件删除结果封装类
*/
data class DeleteResult(
val success: Boolean,
val errorMessage: String? = null,
val deletedFiles: Int = 0,
val deletedSize: Long = 0
) {
companion object {
fun success(deletedFiles: Int = 1, deletedSize: Long = 0) =
DeleteResult(true, null, deletedFiles, deletedSize)
fun failure(message: String) = DeleteResult(false, message, 0, 0)
}
}

View File

@ -2,4 +2,6 @@ package com.all.pdfreader.pro.app.model
sealed class FileActionEvent { sealed class FileActionEvent {
data class Rename(val renameResult: RenameResult) : FileActionEvent() data class Rename(val renameResult: RenameResult) : FileActionEvent()
data class Delete(val deleteResult: DeleteResult) : FileActionEvent()
data class Favorite(val isFavorite: Boolean) : FileActionEvent()
} }

View File

@ -66,7 +66,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
updateSelectedNav(activeFragment) updateSelectedNav(activeFragment)
} }
private fun initObserve(){ private fun initObserve() {
//观察其余操作 //观察其余操作
viewModel.fileActionEvent.observe(this) { event -> viewModel.fileActionEvent.observe(this) { event ->
when (event) { when (event) {
@ -77,6 +77,22 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
showToast(event.renameResult.errorMessage.toString()) showToast(event.renameResult.errorMessage.toString())
} }
} }
is FileActionEvent.Delete -> {
if (event.deleteResult.success) {
showToast(getString(R.string.delete_successfully))
} else {
showToast(event.deleteResult.errorMessage.toString())
}
}
is FileActionEvent.Favorite -> {
if (event.isFavorite) {
showToast(getString(R.string.added_to_favorites))
} else {
showToast(getString(R.string.removed_from_favorites))
}
}
} }
} }
} }

View File

@ -59,19 +59,6 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
finish() finish()
} }
} }
//观察其余操作
viewModel.fileActionEvent.observe(this) { event ->
logDebug("fileActionEvent响应 $event")
when (event) {
is FileActionEvent.Rename -> {
if (event.renameResult.success) {
showToast(getString(R.string.rename_successfully))
} else {
showToast(event.renameResult.errorMessage.toString())
}
}
}
}
} }
private fun loadPdf() { private fun loadPdf() {

View File

@ -0,0 +1,67 @@
package com.all.pdfreader.pro.app.ui.dialog
import android.graphics.Color
import android.os.Bundle
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.fragment.app.activityViewModels
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.DialogDeleteBinding
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
class DeleteDialogFragment() : DialogFragment() {
private lateinit var binding: DialogDeleteBinding
private val viewModel: PdfViewModel by activityViewModels()
private lateinit var pdfDocument: PdfDocumentEntity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = DialogDeleteBinding.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.value?.let {
pdfDocument = it
setupOnClick()
} ?: run {
showToast(getString(R.string.file_not))
dismiss()
}
}
private fun setupOnClick() {
binding.okBtn.setOnClickListener {
viewModel.deleteFile(pdfDocument.filePath)
dismiss()
}
binding.cancelBtn.setOnClickListener {
dismiss()
}
}
private fun showToast(message: String) {
Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
}
}

View File

@ -28,7 +28,6 @@ import java.io.File
class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() { class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment() {
private lateinit var binding: DialogListMoreBinding private lateinit var binding: DialogListMoreBinding
private val pdfRepository = PdfRepository.getInstance()
private val viewModel: PdfViewModel by activityViewModels() private val viewModel: PdfViewModel by activityViewModels()
private lateinit var pdfDocument: PdfDocumentEntity private lateinit var pdfDocument: PdfDocumentEntity
private var isFavorite: Boolean = false private var isFavorite: Boolean = false
@ -54,7 +53,6 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
isFavorite = pdfDocument.isFavorite isFavorite = pdfDocument.isFavorite
initUi() initUi()
setupOnClick() setupOnClick()
} ?: run { } ?: run {
showToast(getString(R.string.file_not)) showToast(getString(R.string.file_not))
dismiss() dismiss()
@ -84,28 +82,16 @@ class ListMoreDialogFragment(val filePath: String) : BottomSheetDialogFragment()
binding.collectBtn.setClickWithAnimation(duration = 250) { binding.collectBtn.setClickWithAnimation(duration = 250) {
isFavorite = !isFavorite isFavorite = !isFavorite
updateCollectUi(isFavorite) updateCollectUi(isFavorite)
saveCollectState(isFavorite) viewModel.saveCollectState(pdfDocument.filePath, isFavorite)
}
binding.renameFileBtn.setOnClickListener {
RenameDialogFragment(pdfDocument.filePath).show(
parentFragmentManager,
"ListMoreDialogFragment"
)
dismiss() dismiss()
} }
binding.renameFileBtn.setOnClickListener {
RenameDialogFragment().show(parentFragmentManager, "ListMoreDialogFragment")
dismiss()
} }
binding.deleteFileBtn.setOnClickListener {
private fun saveCollectState(b: Boolean) { DeleteDialogFragment().show(parentFragmentManager, "DeleteDialogFragment")
pdfDocument = pdfDocument.copy( dismiss()
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))
} }
} }

View File

@ -19,9 +19,7 @@ import com.all.pdfreader.pro.app.util.FileUtils
import com.all.pdfreader.pro.app.viewmodel.PdfViewModel import com.all.pdfreader.pro.app.viewmodel.PdfViewModel
import java.io.File import java.io.File
class RenameDialogFragment( class RenameDialogFragment() : DialogFragment() {
private val filePath: String
) : DialogFragment() {
private lateinit var binding: DialogRenameFileBinding private lateinit var binding: DialogRenameFileBinding
private val viewModel: PdfViewModel by activityViewModels() private val viewModel: PdfViewModel by activityViewModels()
@ -49,9 +47,7 @@ class RenameDialogFragment(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.pdfDocument.value?.let {
viewModel.pdfDocument.observe(this) { document ->
document?.let {
pdfDocument = it pdfDocument = it
initView() initView()
setupOnClick() setupOnClick()
@ -60,8 +56,6 @@ class RenameDialogFragment(
dismiss() dismiss()
} }
} }
viewModel.getPDFDocument(filePath)
}
private fun initView() { private fun initView() {
binding.etName.showKeyboard() binding.etName.showKeyboard()

View File

@ -0,0 +1,264 @@
package com.all.pdfreader.pro.app.util
import android.content.Context
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import com.all.pdfreader.pro.app.PRApp
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.model.DeleteResult
import com.all.pdfreader.pro.app.util.FileUtils.formatFileSize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.collections.forEach
object FileDeleteUtil {
/**
* 删除文件同步版本- 功能完善的删除方法
*
* @param file 要删除的文件
* @param deleteEmptyParentDirs 是否删除空的父目录默认false
* @param dryRun 是否只模拟删除不实际执行默认false
* @return DeleteResult对象包含删除结果和详细信息
*/
fun deleteFile(
file: File,
deleteEmptyParentDirs: Boolean = false,
dryRun: Boolean = false
): DeleteResult {
if (!file.exists()) {
Log.e("ocean", "❌ File does not exist: ${file.path}")
return DeleteResult.failure(PRApp.getStringRes(R.string.error_file_not_exist))
}
if (!file.canWrite()) {
Log.e("ocean", "❌ No write permission for file: ${file.path}")
return DeleteResult.failure(PRApp.getStringRes(R.string.error_no_write_permission))
}
// 安全检查:防止删除重要系统目录
if (isProtectedPath(file)) {
Log.e("ocean", "❌ Cannot delete protected path: ${file.path}")
return DeleteResult.failure(PRApp.getStringRes(R.string.error_cannot_delete_protected_path))
}
// 如果是目录,检查是否为空(避免误删非空目录)
if (file.isDirectory) {
val files = file.listFiles()
if (!files.isNullOrEmpty()) {
Log.e("ocean", "❌ Directory is not empty: ${file.path} (${files.size} items)")
return DeleteResult.failure(PRApp.getStringRes(R.string.error_directory_not_empty))
}
}
// 计算要删除的文件大小
val fileSize = file.length()
if (dryRun) {
Log.d("ocean", "🔍 Dry run - would delete: ${file.path} (${formatFileSize(fileSize)})")
return DeleteResult.success(1, fileSize)
}
return try {
val success = file.delete()
if (success) {
Log.d(
"ocean",
"✅ File deleted successfully: ${file.path} (${formatFileSize(fileSize)})"
)
// 如果需要,删除空的父目录
if (deleteEmptyParentDirs) {
deleteEmptyParentDirectories(file.parentFile)
}
DeleteResult.success(1, fileSize)
} else {
Log.e("ocean", "❌ Failed to delete file: ${file.path}")
DeleteResult.failure(PRApp.getStringRes(R.string.error_failed_delete_file))
}
} catch (e: SecurityException) {
Log.e("ocean", "❌ Security exception while deleting: ${e.message}")
DeleteResult.failure(PRApp.getStringRes(R.string.error_permission_delete_file))
} catch (e: Exception) {
Log.e("ocean", "❌ Exception while deleting file: ${e.message}")
DeleteResult.failure(
PRApp.getStringRes(
R.string.error_file_delete_failed,
e.message.toString()
)
)
}
}
/**
* 删除文件异步版本- 功能完善的删除方法
*
* @param file 要删除的文件
* @param deleteEmptyParentDirs 是否删除空的父目录默认false
* @param dryRun 是否只模拟删除不实际执行默认false
* @return DeleteResult对象包含删除结果和详细信息
*/
suspend fun deleteFileAsync(
file: File,
deleteEmptyParentDirs: Boolean = false,
dryRun: Boolean = false
): DeleteResult =
withContext(Dispatchers.IO) {
deleteFile(file, deleteEmptyParentDirs, dryRun)
}
/**
* 批量删除文件
*
* @param files 要删除的文件列表
* @param deleteEmptyParentDirs 是否删除空的父目录默认false
* @param dryRun 是否只模拟删除不实际执行默认false
* @return DeleteResult对象包含总体删除结果
*/
fun deleteFiles(
files: List<File>,
deleteEmptyParentDirs: Boolean = false,
dryRun: Boolean = false
): DeleteResult {
if (files.isEmpty()) {
return DeleteResult.success(0, 0)
}
var totalDeleted = 0
var totalSize = 0L
val errors = mutableListOf<String>()
files.forEach { file ->
val result = deleteFile(file, deleteEmptyParentDirs, dryRun)
if (result.success) {
totalDeleted += result.deletedFiles
totalSize += result.deletedSize
} else {
errors.add("${file.name}: ${result.errorMessage}")
}
}
return if (errors.isEmpty()) {
DeleteResult.success(totalDeleted, totalSize)
} else {
val error = "\n${errors.joinToString("\n")}"
DeleteResult.failure(PRApp.getStringRes(R.string.error_some_files_not_deleted, error))
}
}
/**
* 删除目录及其所有内容递归删除
*
* @param directory 要删除的目录
* @param dryRun 是否只模拟删除不实际执行默认false
* @return DeleteResult对象包含删除结果
*/
fun deleteDirectoryRecursive(directory: File, dryRun: Boolean = false): DeleteResult {
if (!directory.exists()) {
return DeleteResult.failure(PRApp.getStringRes(R.string.error_directory_does_not_exist))
}
if (!directory.isDirectory) {
return DeleteResult.failure(PRApp.getStringRes(R.string.error_path_not_directory))
}
if (isProtectedPath(directory)) {
return DeleteResult.failure(PRApp.getStringRes(R.string.error_cannot_delete_protected_directory))
}
var deletedCount = 0
var deletedSize = 0L
// 先删除目录下的所有文件和子目录
directory.listFiles()?.forEach { file ->
val result = if (file.isDirectory) {
deleteDirectoryRecursive(file, dryRun)
} else {
deleteFile(file, false, dryRun)
}
if (result.success) {
deletedCount += result.deletedFiles
deletedSize += result.deletedSize
}
}
// 最后删除空目录本身
val dirResult = deleteFile(directory, false, dryRun)
if (dirResult.success) {
deletedCount += 1
}
return DeleteResult.success(deletedCount, deletedSize)
}
/**
* 检查是否为受保护的路径防止误删系统文件
*/
private fun isProtectedPath(file: File): Boolean {
val protectedPaths = listOf(
"/system",
"/data",
"/vendor",
"/sbin",
"/etc",
"/dev",
"/proc",
"/sys",
"/root",
"/acct",
"/mnt",
"/storage/emulated/0/Android",
"/storage/emulated/0/.android_secure"
)
val absolutePath = file.absolutePath
return protectedPaths.any { protectedPath ->
absolutePath.startsWith(protectedPath)
}
}
/**
* 删除空的父目录向上递归
*/
private fun deleteEmptyParentDirectories(directory: File?) {
directory?.let { dir ->
if (dir.exists() && dir.isDirectory && dir.listFiles()?.isEmpty() == true) {
val parent = dir.parentFile
if (dir.delete()) {
Log.d("ocean", "✅ Deleted empty parent directory: ${dir.path}")
deleteEmptyParentDirectories(parent)
}
}
}
}
/**
* 安全删除文件到回收站如果系统支持
*
* @param context 上下文
* @param file 要删除的文件
* @return DeleteResult对象
*/
fun moveToTrash(context: Context, file: File): DeleteResult {
return try {
// Android 10+ 支持系统回收站
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
MediaStore.createTrashRequest(
context.contentResolver,
listOf(Uri.fromFile(file)),
true
)
// 注意这里需要Activity来启动IntentSender
DeleteResult.success(1, file.length())
} else {
// 低版本直接删除
deleteFile(file)
}
} catch (e: Exception) {
DeleteResult.failure("Failed to move file to trash: ${e.message}")
}
}
}

View File

@ -35,7 +35,6 @@ object FileUtils {
// 外部存储根目录(需要权限) // 外部存储根目录(需要权限)
if (StoragePermissionHelper.hasBasicStoragePermission(context)) { if (StoragePermissionHelper.hasBasicStoragePermission(context)) {
Log.d("ocean", "📂 扫描外部存储目录...") Log.d("ocean", "📂 扫描外部存储目录...")
// 扫描常见的PDF存储目录 // 扫描常见的PDF存储目录
val externalStorage = android.os.Environment.getExternalStorageDirectory() val externalStorage = android.os.Environment.getExternalStorageDirectory()
scanCommonDirectories(externalStorage, pdfFiles) scanCommonDirectories(externalStorage, pdfFiles)
@ -431,7 +430,11 @@ object FileUtils {
RenameResult.failure(PRApp.getStringRes(R.string.error_insufficient_permission)) RenameResult.failure(PRApp.getStringRes(R.string.error_insufficient_permission))
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ocean", "❌ File rename exception: ${e.message}") Log.e("ocean", "❌ File rename exception: ${e.message}")
RenameResult.failure(PRApp.getStringRes(R.string.error_file_rename_exception, e.message.toString())) RenameResult.failure(
PRApp.getStringRes(
R.string.error_file_rename_exception, e.message.toString()
)
)
} }
} }
@ -516,7 +519,7 @@ object FileUtils {
} }
tempFile tempFile
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ocean", "无法获取文件: ${e.message}", e) // Log.e("ocean", "无法获取文件: ${e.message}", e)
null null
} }
} }

View File

@ -8,9 +8,12 @@ import androidx.lifecycle.viewModelScope
import com.all.pdfreader.pro.app.model.FileActionEvent import com.all.pdfreader.pro.app.model.FileActionEvent
import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity
import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.room.repository.PdfRepository
import com.all.pdfreader.pro.app.util.FileDeleteUtil
import com.all.pdfreader.pro.app.util.FileUtils import com.all.pdfreader.pro.app.util.FileUtils
import com.all.pdfreader.pro.app.util.LogUtil import com.all.pdfreader.pro.app.util.LogUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class PdfViewModel : ViewModel() { class PdfViewModel : ViewModel() {
@ -23,7 +26,6 @@ class PdfViewModel : ViewModel() {
private val _fileActionEvent = MutableLiveData<FileActionEvent>() private val _fileActionEvent = MutableLiveData<FileActionEvent>()
val fileActionEvent: LiveData<FileActionEvent> = _fileActionEvent val fileActionEvent: LiveData<FileActionEvent> = _fileActionEvent
fun getPDFDocument(filePath: String) { fun getPDFDocument(filePath: String) {
viewModelScope.launch { viewModelScope.launch {
val document = pdfRepository.getDocumentByPath(filePath) val document = pdfRepository.getDocumentByPath(filePath)
@ -37,7 +39,10 @@ class PdfViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
val oldFile = File(filePath) val oldFile = File(filePath)
val renameResult = FileUtils.renameFile(oldFile, newName) val renameResult = FileUtils.renameFile(oldFile, newName)
Log.d("ocean", "renamePdf->oldFile: $oldFile, newName=$newName, renameResult=$renameResult") Log.d(
"ocean",
"renamePdf->oldFile: $oldFile, newName=$newName, renameResult=$renameResult"
)
if (renameResult.success) {//修改成功更新数据库 if (renameResult.success) {//修改成功更新数据库
val finalName = if (newName.contains('.')) { val finalName = if (newName.contains('.')) {
// 用户提供了后缀,使用用户的 // 用户提供了后缀,使用用户的
@ -56,7 +61,34 @@ class PdfViewModel : ViewModel() {
Log.d("ocean", "renamePdf->newFilePath: $newFilePath, finalName=$finalName") Log.d("ocean", "renamePdf->newFilePath: $newFilePath, finalName=$finalName")
pdfRepository.updateFilePathAndFileName(filePath, newFilePath, finalName) pdfRepository.updateFilePathAndFileName(filePath, newFilePath, finalName)
} }
withContext(Dispatchers.Main) {
_fileActionEvent.postValue(FileActionEvent.Rename(renameResult)) _fileActionEvent.postValue(FileActionEvent.Rename(renameResult))
} }
} }
}
fun deleteFile(filePath: String) {
viewModelScope.launch {
val file = File(filePath)
val deleteResult = FileDeleteUtil.deleteFile(file)
Log.d("ocean", "deleteFile->file: $file, deleteResult=$deleteResult")
if (deleteResult.success) {
Log.d("ocean", "文件已删除,清除数据库数据")
pdfRepository.deleteDocument(filePath)
}
withContext(Dispatchers.Main) {
_fileActionEvent.postValue(FileActionEvent.Delete(deleteResult))
}
}
}
fun saveCollectState(filePath: String, isFavorite: Boolean) {
viewModelScope.launch {
pdfRepository.updateFavoriteStatus(filePath, isFavorite)
withContext(Dispatchers.Main) {
_fileActionEvent.postValue(FileActionEvent.Favorite(isFavorite))
}
}
}
} }

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按下状态 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#ED343B"/> <!-- 按下颜色:深一点 -->
<corners android:radius="12dp"/>
</shape>
</item>
<!-- 默认状态 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#F1494F"/> <!-- 默认颜色 -->
<corners android:radius="12dp"/>
</shape>
</item>
</selector>

View File

@ -0,0 +1,72 @@
<?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:background="@drawable/dr_rounded_corner_12_bg_white"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:fontFamily="@font/poppins_semibold"
android:text="@string/delete_file_title"
android:textColor="@color/black"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="8dp"
android:fontFamily="@font/poppins_regular"
android:text="@string/delete_file_desc"
android:textColor="@color/black_80"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="24dp">
<LinearLayout
android:id="@+id/cancelBtn"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="@drawable/dr_cancel_btn_bg"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semibold"
android:text="@string/cancel"
android:textColor="@color/black_60"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/okBtn"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:background="@drawable/dr_click_btn_red_bg"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semibold"
android:text="@string/delete"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -269,6 +269,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/deleteFileBtn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:gravity="center_vertical" android:gravity="center_vertical"

View File

@ -46,6 +46,7 @@
<string name="added_to_favorites">Added to Favorite</string> <string name="added_to_favorites">Added to Favorite</string>
<string name="removed_from_favorites">Removed from Favorites</string> <string name="removed_from_favorites">Removed from Favorites</string>
<string name="delete_file">Delete File</string> <string name="delete_file">Delete File</string>
<string name="delete">Delete</string>
<string name="set_password">Set Password</string> <string name="set_password">Set Password</string>
<string name="remove_password">Remove Password</string> <string name="remove_password">Remove Password</string>
<string name="duplicate_file">Duplicate File</string> <string name="duplicate_file">Duplicate File</string>
@ -57,6 +58,7 @@
<string name="name_already_exists">A file with the same name already exists</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> <string name="name_start_end_space">File name cannot start or end with space</string>
<string name="rename_successfully">Rename successfully</string> <string name="rename_successfully">Rename successfully</string>
<string name="delete_successfully">Delete successfully</string>
<string name="error_file_not_exist">File does not exist</string> <string name="error_file_not_exist">File does not exist</string>
<string name="error_no_write_permission">No write permission for file</string> <string name="error_no_write_permission">No write permission for file</string>
<string name="error_invalid_file_name">Invalid file name</string> <string name="error_invalid_file_name">Invalid file name</string>
@ -65,4 +67,15 @@
<string name="error_file_rename_failed">File rename failed</string> <string name="error_file_rename_failed">File rename failed</string>
<string name="error_insufficient_permission">Insufficient permission to rename file</string> <string name="error_insufficient_permission">Insufficient permission to rename file</string>
<string name="error_file_rename_exception">File rename exception: %1$s</string> <string name="error_file_rename_exception">File rename exception: %1$s</string>
<string name="error_cannot_delete_protected_path">Cannot delete protected system path</string>
<string name="error_directory_not_empty">Directory is not empty</string>
<string name="error_failed_delete_file">Failed to delete file</string>
<string name="error_permission_delete_file">Insufficient permission to delete file</string>
<string name="error_file_delete_failed">Delete operation failed: %1$s</string>
<string name="error_some_files_not_deleted">Some files could not be deleted: %1$s</string>
<string name="error_directory_does_not_exist">Directory does not exist</string>
<string name="error_path_not_directory">Path is not a directory</string>
<string name="error_cannot_delete_protected_directory">Cannot delete protected system directory</string>
<string name="delete_file_title">Delete this file permanently?</string>
<string name="delete_file_desc">Deleting this file will remove it permanently from your device.</string>
</resources> </resources>