Эх сурвалжийг харах

refactor(更新)
- 备份与还原功能优化:
- 导出文件时,采用复制文件替代移动文件,确保原始文件保留。
- 修复全选状态在列表为空时的显示问题。
- 新增文件操作工具函数 `copyFileToDir`,用于将文件复制到指定目录。

周文健 2 сар өмнө
parent
commit
51ad9b1318

+ 5 - 5
app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/BackupAndRestoreFragment.kt

@@ -1,6 +1,5 @@
 package com.grkj.iscs.features.main.fragment.data_manage
 package com.grkj.iscs.features.main.fragment.data_manage
 
 
-import androidx.activity.result.ActivityResultCallback
 import androidx.fragment.app.viewModels
 import androidx.fragment.app.viewModels
 import com.drake.brv.BindingAdapter
 import com.drake.brv.BindingAdapter
 import com.drake.brv.utils.linear
 import com.drake.brv.utils.linear
@@ -8,7 +7,6 @@ import com.drake.brv.utils.models
 import com.drake.brv.utils.setup
 import com.drake.brv.utils.setup
 import com.grkj.data.data.EventConstants
 import com.grkj.data.data.EventConstants
 import com.grkj.data.database.BackupScheduler
 import com.grkj.data.database.BackupScheduler
-import com.grkj.data.database.ISCSDatabase
 import com.grkj.data.database.RoomBackupManager
 import com.grkj.data.database.RoomBackupManager
 import com.grkj.data.enums.BackupFrequencyWeekEnum
 import com.grkj.data.enums.BackupFrequencyWeekEnum
 import com.grkj.data.utils.event.BackupCompleteEvent
 import com.grkj.data.utils.event.BackupCompleteEvent
@@ -19,6 +17,7 @@ import com.grkj.iscs.features.main.dialog.TextDropDownDialog
 import com.grkj.iscs.features.main.viewmodel.data_manage.BackupAndRestoreViewModel
 import com.grkj.iscs.features.main.viewmodel.data_manage.BackupAndRestoreViewModel
 import com.grkj.shared.model.EventBean
 import com.grkj.shared.model.EventBean
 import com.grkj.shared.utils.FilePickerUtils
 import com.grkj.shared.utils.FilePickerUtils
+import com.grkj.shared.utils.SAFHelper.copyFileToDir
 import com.grkj.shared.utils.SAFHelper.moveFileToDir
 import com.grkj.shared.utils.SAFHelper.moveFileToDir
 import com.grkj.shared.utils.i18n.I18nManager
 import com.grkj.shared.utils.i18n.I18nManager
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.base.BaseFragment
@@ -132,7 +131,7 @@ class BackupAndRestoreFragment : BaseFragment<FragmentBackupAndRestoreBinding>()
             filePickerUtils.pickDirectory { treeUri ->
             filePickerUtils.pickDirectory { treeUri ->
                 viewModel.backupItemDatas.filter { it.isSelected }.forEach {
                 viewModel.backupItemDatas.filter { it.isSelected }.forEach {
                     treeUri?.let { treeUri ->
                     treeUri?.let { treeUri ->
-                        requireContext().moveFileToDir(treeUri, it.file)
+                        requireContext().copyFileToDir(treeUri, it.file)
                     }
                     }
                 }
                 }
                 viewModel.backupItemDatas.forEach {
                 viewModel.backupItemDatas.forEach {
@@ -157,7 +156,8 @@ class BackupAndRestoreFragment : BaseFragment<FragmentBackupAndRestoreBinding>()
      */
      */
     private fun checkAndSetSelectAllListener() {
     private fun checkAndSetSelectAllListener() {
         binding.selectAll.setOnCheckedChangeListener(null)
         binding.selectAll.setOnCheckedChangeListener(null)
-        binding.selectAll.isChecked = viewModel.backupItemDatas.all { it.isSelected }
+        binding.selectAll.isChecked =
+            viewModel.backupItemDatas.isNotEmpty() && viewModel.backupItemDatas.all { it.isSelected }
         binding.selectAll.setOnCheckedChangeListener { v, isChecked ->
         binding.selectAll.setOnCheckedChangeListener { v, isChecked ->
             viewModel.backupItemDatas.forEach { it.isSelected = isChecked }
             viewModel.backupItemDatas.forEach { it.isSelected = isChecked }
             binding.listRv.adapter?.notifyDataSetChanged()
             binding.listRv.adapter?.notifyDataSetChanged()
@@ -237,7 +237,7 @@ class BackupAndRestoreFragment : BaseFragment<FragmentBackupAndRestoreBinding>()
         itemBinding.export.setDebouncedClickListener {
         itemBinding.export.setDebouncedClickListener {
             filePickerUtils.pickDirectory { treeUri ->
             filePickerUtils.pickDirectory { treeUri ->
                 treeUri?.let { treeUri ->
                 treeUri?.let { treeUri ->
-                    requireContext().moveFileToDir(treeUri, item.file)
+                    requireContext().copyFileToDir(treeUri, item.file)
                 }
                 }
                 showToast(I18nManager.t("export_success"))
                 showToast(I18nManager.t("export_success"))
             }
             }

+ 32 - 0
shared/src/main/java/com/grkj/shared/utils/SAFHelper.kt

@@ -62,6 +62,38 @@ object SAFHelper {
         return target.uri
         return target.uri
     }
     }
 
 
+    /**
+     * 把 [file] 复制到 [treeUri] 指向的目录下。
+     *
+     * 规则:
+     * - 目标文件名用 file.name;若同名存在,自动重命名为 "name (1).ext"。
+     * - MIME 基于扩展名简单判断。
+     *
+     * @return 新文件的 Uri,失败返回 null
+     */
+    fun Context.copyFileToDir(treeUri: Uri, file: File): Uri? {
+        // 尝试持久化读写权限(若已授权会静默成功)
+        takePersistedRW(treeUri)
+
+        val parent = DocumentFile.fromTreeUri(this, treeUri) ?: return null
+        if (!parent.isDirectory) return null
+
+        val displayName = uniqueName(parent, file.name)
+        val mime = guessMimeFromName(file.name)
+
+        // 目标占位
+        val target = parent.createFile(mime, displayName) ?: return null
+
+        runCatching {
+            copyStreams(FileInputStream(file), contentResolver.openOutputStream(target.uri, "w")!!)
+        }.onFailure {
+            // 复制失败,清理占位文件
+            runCatching { target.delete() }
+            return null
+        }
+        return target.uri
+    }
+
     /**
     /**
      * 将任意 [uri] 转换为可直接访问的临时 [File]。
      * 将任意 [uri] 转换为可直接访问的临时 [File]。
      * - file:// 直接返回对应 File
      * - file:// 直接返回对应 File