V1.2(3)fragment的无参构造,dialogFragment的安全显示,恢复中和删除中的调用避免内存泄漏

This commit is contained in:
litingting 2026-01-06 18:50:16 +08:00
parent 0c1980da94
commit 0f0a3301d2
22 changed files with 230 additions and 101 deletions

View File

@ -19,8 +19,8 @@ android {
applicationId = "com.ux.video.file.filerecovery" applicationId = "com.ux.video.file.filerecovery"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 2 versionCode = 3
versionName = "1.1" versionName = "1.2"
project.setProperty("archivesBaseName", "File Recovery Tool" + versionName + "(${versionCode})_$timestamp") project.setProperty("archivesBaseName", "File Recovery Tool" + versionName + "(${versionCode})_$timestamp")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -74,6 +74,12 @@
-keep class kotlinx.android.parcel.Parcelize -keep class kotlinx.android.parcel.Parcelize
-keep class kotlin.Metadata { *; } -keep class kotlin.Metadata { *; }
# 保留所有 Fragment 空构造函数
-keep class * extends androidx.fragment.app.Fragment {
public <init>();
}

View File

@ -22,9 +22,11 @@
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".settings.PrivacyPolicyActivity" android:name=".settings.PrivacyPolicyActivity"
android:screenOrientation="portrait"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".settings.SetupActivity" android:name=".settings.SetupActivity"
android:screenOrientation="portrait"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".welcome.SplashActivity" android:name=".welcome.SplashActivity"

View File

@ -18,10 +18,14 @@ import com.ux.video.file.filerecovery.databinding.DialogRecoveringBinding
*/ */
abstract class BaseIngDialogFragment() : DialogFragment() { abstract class BaseIngDialogFragment() : DialogFragment() {
var total: Int = 0 var total: Int = 0
var completeListener: ((number: Int) -> Unit)? = null
private lateinit var binding: DialogRecoveringBinding
private var _binding: DialogRecoveringBinding? = null
protected val binding get() = _binding!!
private var countDownTimer: CountDownTimer? = null
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
dialog?.setCanceledOnTouchOutside(false) dialog?.setCanceledOnTouchOutside(false)
@ -40,15 +44,15 @@ abstract class BaseIngDialogFragment() : DialogFragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = DialogRecoveringBinding.inflate(inflater) _binding = DialogRecoveringBinding.inflate(inflater)
initUi(binding) initUi(binding)
binding.run { binding.run {
if (total < 20) { if (total < 20) {
val defaultTimer = 2000L val defaultTimer = 2000L
object : CountDownTimer(defaultTimer, 200) { countDownTimer = object : CountDownTimer(defaultTimer, 200) {
override fun onFinish() { override fun onFinish() {
progressBar.progress = 100 progressBar.progress = 100
completeListener?.invoke(total) // completeListener?.invoke(total)
} }
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
@ -57,7 +61,8 @@ abstract class BaseIngDialogFragment() : DialogFragment() {
progressBar.progress = i progressBar.progress = i
} }
}.start() }.also { it.start() }
} }
} }
@ -69,6 +74,7 @@ abstract class BaseIngDialogFragment() : DialogFragment() {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun updateProgress(number: Int) { fun updateProgress(number: Int) {
val b = _binding ?: return
binding.tvRecoverNumber.text = "${number}/" binding.tvRecoverNumber.text = "${number}/"
if (total < 20) { if (total < 20) {
return return
@ -81,9 +87,15 @@ abstract class BaseIngDialogFragment() : DialogFragment() {
binding.progressBar.progress = progress binding.progressBar.progress = progress
if (progress == 100) { if (progress == 100) {
completeListener?.invoke(number) // completeListener?.invoke(number)
} }
} }
override fun onDestroyView() {
countDownTimer?.cancel()
countDownTimer = null
_binding = null
super.onDestroyView()
}
} }

View File

@ -103,8 +103,7 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>() {
myData?.let { myData -> myData?.let { myData ->
RecoverOrDeleteManager.showConfirmDeleteDialog( RecoverOrDeleteManager.showConfirmDeleteDialog(
true, true,
supportFragmentManager, this@DetailsActivity,
lifecycleScope,
setOf(myData) setOf(myData)
) { count -> ) { count ->
complete(count, 1) complete(count, 1)
@ -128,8 +127,7 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>() {
text = getString(R.string.recover) text = getString(R.string.recover)
setOnClickListener { setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog( RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager, this@DetailsActivity,
lifecycleScope,
setOf(myData) setOf(myData)
) { count -> ) { count ->
complete(count, 0) complete(count, 0)

View File

@ -19,6 +19,7 @@ import com.ux.video.file.filerecovery.databinding.ActivityMainBinding
import com.ux.video.file.filerecovery.recovery.RecoveryActivity import com.ux.video.file.filerecovery.recovery.RecoveryActivity
import com.ux.video.file.filerecovery.settings.SetupActivity import com.ux.video.file.filerecovery.settings.SetupActivity
import com.ux.video.file.filerecovery.utils.Common import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.safeShow
import com.ux.video.file.filerecovery.utils.FileType import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.utils.ScanManager
@ -168,7 +169,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun requestPermission() { private fun requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
dialogPermission = dialogPermission ?: PermissionDialogFragment { dialogPermission = dialogPermission ?: PermissionDialogFragment()
dialogPermission?.onClickAllow = {
try { try {
val intent = val intent =
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
@ -181,7 +183,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
isRequestPermission = true isRequestPermission = true
} }
dialogPermission?.show(supportFragmentManager, "") dialogPermission?.safeShow(supportFragmentManager, Common.TAG_DIALOG_PERMISSION)
} else { } else {
requestPermissionLauncher.launch( requestPermissionLauncher.launch(
arrayOf( arrayOf(

View File

@ -10,9 +10,11 @@ import androidx.fragment.app.DialogFragment
import com.ux.video.file.filerecovery.databinding.DialogPermissionBinding import com.ux.video.file.filerecovery.databinding.DialogPermissionBinding
class PermissionDialogFragment(val onClickAllow: () -> Unit) : DialogFragment() { class PermissionDialogFragment() : DialogFragment() {
private lateinit var binding: DialogPermissionBinding private lateinit var binding: DialogPermissionBinding
lateinit var onClickAllow: () -> Unit
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
dialog?.window?.apply { dialog?.window?.apply {
@ -27,7 +29,7 @@ class PermissionDialogFragment(val onClickAllow: () -> Unit) : DialogFragment()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
binding = DialogPermissionBinding.inflate(inflater) binding = DialogPermissionBinding.inflate(inflater)
binding.run { binding.run {
cancel.setOnClickListener { dismiss() } cancel.setOnClickListener { dismiss() }

View File

@ -250,8 +250,7 @@ class RecoveredFragment : BaseFragment<FragmentRecoveryPhotoBinding>() {
layoutBottom.tvLeft.setOnClickListener { layoutBottom.tvLeft.setOnClickListener {
selectedList?.let { selectedList?.let {
RecoverOrDeleteManager.showConfirmDeleteDialog( RecoverOrDeleteManager.showConfirmDeleteDialog(
fragmentManager = requireActivity().supportFragmentManager, activity =requireActivity(),
scope = lifecycleScope,
selectedSetList = it selectedSetList = it
) { count -> ) { count ->
val removeSelectedFromSizeList = val removeSelectedFromSizeList =

View File

@ -11,9 +11,10 @@ import com.ux.video.file.filerecovery.databinding.DialogExitBinding
import com.ux.video.file.filerecovery.databinding.DialogPermissionBinding import com.ux.video.file.filerecovery.databinding.DialogPermissionBinding
class ExitDialogFragment(val onClickExit: () -> Unit) : DialogFragment() { class ExitDialogFragment() : DialogFragment() {
private lateinit var binding: DialogExitBinding private lateinit var binding: DialogExitBinding
lateinit var onClickExit: () -> Unit
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
dialog?.window?.apply { dialog?.window?.apply {

View File

@ -12,6 +12,7 @@ import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.sort.SortingActivity import com.ux.video.file.filerecovery.sort.SortingActivity
import com.ux.video.file.filerecovery.utils.Common import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.safeShow
import com.ux.video.file.filerecovery.utils.FileType import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanType import com.ux.video.file.filerecovery.utils.ScanType
@ -49,7 +50,7 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
binding.run { binding.run {
val myAdapter = when (scanType.mediaType) { val myAdapter = when (scanType.mediaType) {
FileType.AUDIO, FileType.DOCUMENT -> { FileType.AUDIO, FileType.DOCUMENT -> {
bottomLayout.setBackgroundResource(R.drawable.bg_rectangle_white_top_20) bottomLayout.setBackgroundResource(R.drawable.bg_rectangle_white_top_20)
ScanResultDocumentsAudioAdapter( ScanResultDocumentsAudioAdapter(
this@ScanResultDisplayActivity, this@ScanResultDisplayActivity,
@ -70,7 +71,7 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
} }
} }
viewModel.scanData.observe(this@ScanResultDisplayActivity){ data-> viewModel.scanData.observe(this@ScanResultDisplayActivity) { data ->
list = data list = data
list.let { data -> list.let { data ->
textDirCount.text = data.size.toString() textDirCount.text = data.size.toString()
@ -85,11 +86,15 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
} }
} }
} }
private fun dealExit() { private fun dealExit() {
exitDialog = exitDialog ?: ExitDialogFragment { exitDialog = exitDialog ?: ExitDialogFragment()
exitDialog?.onClickExit = {
finish() finish()
} }
exitDialog?.show(supportFragmentManager, "") exitDialog?.safeShow(supportFragmentManager, Common.TAG_DIALOG_EXIT)
} }
private fun setSelectTypeTitle(fileType: FileType) { private fun setSelectTypeTitle(fileType: FileType) {
@ -100,7 +105,7 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
textFileType.text = getString(R.string.text_photos) textFileType.text = getString(R.string.text_photos)
} }
FileType.VIDEO-> { FileType.VIDEO -> {
title.text = getString(R.string.video_title) title.text = getString(R.string.video_title)
textFileType.text = getString(R.string.text_videos) textFileType.text = getString(R.string.text_videos)
} }

View File

@ -1,15 +1,30 @@
package com.ux.video.file.filerecovery.sort package com.ux.video.file.filerecovery.sort
import androidx.fragment.app.FragmentManager
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseIngDialogFragment import com.ux.video.file.filerecovery.base.BaseIngDialogFragment
import com.ux.video.file.filerecovery.databinding.DialogRecoveringBinding import com.ux.video.file.filerecovery.databinding.DialogRecoveringBinding
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.safeShow
/** /**
* 删除中弹窗 * 删除中弹窗
*/ */
class DeletingDialogFragment() : BaseIngDialogFragment() { class DeletingDialogFragment() : BaseIngDialogFragment() {
companion object {
val TAG = Common.TAG_DIALOG_DELETEING
fun show(
fm: FragmentManager,
total: Int
): DeletingDialogFragment {
val dialog = DeletingDialogFragment()
dialog.total = total
dialog.safeShow(fm, TAG)
return dialog
}
}
override fun initUi(binding: DialogRecoveringBinding) { override fun initUi(binding: DialogRecoveringBinding) {
binding.run { binding.run {
relativeLayout.setBackgroundResource(R.drawable.bg_rectangle_fdad00_top_20) relativeLayout.setBackgroundResource(R.drawable.bg_rectangle_fdad00_top_20)

View File

@ -1,10 +1,13 @@
package com.ux.video.file.filerecovery.sort package com.ux.video.file.filerecovery.sort
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import com.ux.video.file.filerecovery.db.ObjectBoxManager import com.ux.video.file.filerecovery.db.ObjectBoxManager
import com.ux.video.file.filerecovery.db.ResultDataFiles import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.Common import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.safeShow
import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync
import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync
@ -12,9 +15,9 @@ import kotlinx.coroutines.CoroutineScope
object RecoverOrDeleteManager { object RecoverOrDeleteManager {
private var dialogRecovering: RecoveringDialogFragment? = null // private var dialogRecovering: RecoveringDialogFragment? = null
private var dialogDeleting: DeletingDialogFragment? = null // private var dialogDeleting: DeletingDialogFragment? = null
private var dialogConfirmDelete: ConfirmDeleteDialogFragment? = null private var dialogConfirmDelete: ConfirmDeleteDialogFragment? = null
//详情页面进行删除操作的监听 //详情页面进行删除操作的监听
@ -29,45 +32,45 @@ object RecoverOrDeleteManager {
* 显示恢复中弹窗 * 显示恢复中弹窗
*/ */
fun showRecoveringDialog( fun showRecoveringDialog(
fragmentManager: FragmentManager, activity: FragmentActivity,
scope: CoroutineScope,
selectedSetList: Set<ResultDataFiles>, selectedSetList: Set<ResultDataFiles>,
onComplete: (number: Int) -> Unit onComplete: (number: Int) -> Unit
) { ) {
val dialog = RecoveringDialogFragment.show(
dialogRecovering = dialogRecovering ?: RecoveringDialogFragment() activity.supportFragmentManager,
dialogRecovering?.run { selectedSetList.size
total = selectedSetList.size )
completeListener = { number -> activity.copySelectedFilesAsync(
onComplete(number)
dialogRecovering?.dismiss()
}
show(fragmentManager, "")
}
scope.copySelectedFilesAsync(
selectedSet = selectedSetList, selectedSet = selectedSetList,
folder = Common.recoveryPhotoDir, folder = Common.recoveryPhotoDir,
onProgress = { currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean -> onProgress = { currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean ->
if(success){ if (success && !activity.isFinishing) {
ScanManager.showLog("--------恢复图片 ", "----------${currentCounts} ${fileName}") ScanManager.showLog(
dialogRecovering?.updateProgress(currentCounts) "--------恢复图片 ",
"----------${currentCounts} ${fileName}"
)
dialog.updateProgress(currentCounts)
ObjectBoxManager.addRecoveryFile(data) ObjectBoxManager.addRecoveryFile(data)
} }
}) { counts -> }) { counts ->
dialogRecovering?.updateProgress(counts) if (!activity.isFinishing) {
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}") dialog.updateProgress(counts)
onComplete(counts)
dialog.dismissAllowingStateLoss()
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}")
}
} }
} }
/** /**
* 显示删除弹窗 * 显示确认删除弹窗
*/ */
fun showConfirmDeleteDialog( fun showConfirmDeleteDialog(
isInfoDelete: Boolean = false, isInfoDelete: Boolean = false,
fragmentManager: FragmentManager, activity: FragmentActivity,
scope: CoroutineScope,
selectedSetList: Set<ResultDataFiles>, selectedSetList: Set<ResultDataFiles>,
onComplete: (number: Int) -> Unit onComplete: (number: Int) -> Unit
) { ) {
@ -76,48 +79,48 @@ object RecoverOrDeleteManager {
onClickDelete = { onClickDelete = {
showDeletingDialog( showDeletingDialog(
isInfoDelete, isInfoDelete,
fragmentManager, activity,
scope,
selectedSetList, selectedSetList,
onComplete onComplete
) )
} }
show(fragmentManager, "")
safeShow(activity.supportFragmentManager, Common.TAG_DIALOG_CONFIRM_DELETE)
} }
} }
/**
* 显示删除中弹窗
*/
private fun showDeletingDialog( private fun showDeletingDialog(
isInfoDelete: Boolean = false, isInfoDelete: Boolean = false,
fragmentManager: FragmentManager, activity: FragmentActivity,
scope: CoroutineScope,
selectedSetList: Set<ResultDataFiles>, selectedSetList: Set<ResultDataFiles>,
onComplete: (number: Int) -> Unit onComplete: (number: Int) -> Unit
) { ) {
val dialog = DeletingDialogFragment.show(
dialogDeleting = dialogDeleting ?: DeletingDialogFragment() activity.supportFragmentManager,
dialogDeleting?.run { selectedSetList.size
total = selectedSetList.size )
completeListener = { number -> activity.deleteFilesAsync(
dialogDeleting?.dismiss()
onComplete(number)
if (isInfoDelete && selectedSetList.size == 1) {
onSingleDeletedCompleteListener?.invoke(selectedSetList.first())
}
}
show(fragmentManager, "")
}
scope.deleteFilesAsync(
selectedSet = selectedSetList, selectedSet = selectedSetList,
onProgress = { currentCounts: Int, data: ResultDataFiles, path: String, success: Boolean -> onProgress = { currentCounts: Int, data: ResultDataFiles, path: String, success: Boolean ->
if (success){ if (success) {
ScanManager.showLog("--------删除图片 ", "----------${currentCounts} ${path}") ScanManager.showLog("--------删除图片 ", "----------${currentCounts} ${path}")
dialogDeleting?.updateProgress(currentCounts) dialog.updateProgress(currentCounts)
} }
}) { counts -> }) { counts ->
dialogDeleting?.updateProgress(counts) if (isInfoDelete && selectedSetList.size == 1) {
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}") onSingleDeletedCompleteListener?.invoke(selectedSetList.first())
}
onComplete(counts)
dialog.updateProgress(counts)
dialog.dismissAllowingStateLoss()
ScanManager.showLog("--------删除图片 ", "----------删除完成 ${counts}")
} }
} }
} }

View File

@ -1,14 +1,33 @@
package com.ux.video.file.filerecovery.sort package com.ux.video.file.filerecovery.sort
import androidx.fragment.app.FragmentManager
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseIngDialogFragment import com.ux.video.file.filerecovery.base.BaseIngDialogFragment
import com.ux.video.file.filerecovery.databinding.DialogRecoveringBinding import com.ux.video.file.filerecovery.databinding.DialogRecoveringBinding
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.safeShow
/** /**
* 恢复中弹窗 * 恢复中弹窗
*/ */
class RecoveringDialogFragment() : BaseIngDialogFragment() { class RecoveringDialogFragment() : BaseIngDialogFragment() {
companion object {
val TAG = Common.TAG_DIALOG_RECOVERING
fun show(
fm: FragmentManager,
total: Int
): RecoveringDialogFragment {
val dialog = RecoveringDialogFragment()
dialog.total = total
dialog.safeShow(fm, TAG)
return dialog
}
}
override fun initUi(binding: DialogRecoveringBinding) { override fun initUi(binding: DialogRecoveringBinding) {
binding.run { binding.run {
relativeLayout.setBackgroundResource(R.drawable.bg_rectangle_0048fd_top_20) relativeLayout.setBackgroundResource(R.drawable.bg_rectangle_0048fd_top_20)

View File

@ -11,14 +11,18 @@ import androidx.fragment.app.DialogFragment
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.databinding.CommonLayoutSortItemBinding import com.ux.video.file.filerecovery.databinding.CommonLayoutSortItemBinding
import com.ux.video.file.filerecovery.databinding.DialogSortBinding import com.ux.video.file.filerecovery.databinding.DialogSortBinding
import com.ux.video.file.filerecovery.recovery.ui.recoveryphoto.RecoveredFragment
class SortDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFragment() { class SortDialogFragment() : DialogFragment() {
private lateinit var binding: DialogSortBinding private lateinit var binding: DialogSortBinding
private var clickType = SortingActivity.SORT_DESC_DATE private var clickType = SortingActivity.SORT_DESC_DATE
private lateinit var LayoutList: List<CommonLayoutSortItemBinding> private lateinit var LayoutList: List<CommonLayoutSortItemBinding>
lateinit var onClickSort: (type: Int) -> Unit
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
dialog?.window?.apply { dialog?.window?.apply {

View File

@ -34,6 +34,7 @@ import com.ux.video.file.filerecovery.utils.ExtendFunctions.kbToBytes
import com.ux.video.file.filerecovery.utils.ExtendFunctions.mbToBytes import com.ux.video.file.filerecovery.utils.ExtendFunctions.mbToBytes
import com.ux.video.file.filerecovery.utils.ExtendFunctions.minutesToMillisecond import com.ux.video.file.filerecovery.utils.ExtendFunctions.minutesToMillisecond
import com.ux.video.file.filerecovery.utils.ExtendFunctions.removeItem import com.ux.video.file.filerecovery.utils.ExtendFunctions.removeItem
import com.ux.video.file.filerecovery.utils.ExtendFunctions.safeShow
import com.ux.video.file.filerecovery.utils.FileType import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
import com.ux.video.file.filerecovery.utils.ScanType import com.ux.video.file.filerecovery.utils.ScanType
@ -250,8 +251,7 @@ class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
tvRecover.setOnClickListener { tvRecover.setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog( RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager, this@SortingActivity,
lifecycleScope,
filterSelectedSetList filterSelectedSetList
) { count -> ) { count ->
complete(count, 0) complete(count, 0)
@ -260,8 +260,7 @@ class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
tvDelete.setOnClickListener { tvDelete.setOnClickListener {
RecoverOrDeleteManager.showConfirmDeleteDialog( RecoverOrDeleteManager.showConfirmDeleteDialog(
fragmentManager = supportFragmentManager, activity = this@SortingActivity,
scope = lifecycleScope,
selectedSetList = filterSelectedSetList selectedSetList = filterSelectedSetList
) { count -> ) { count ->
complete(count, 1) complete(count, 1)
@ -269,7 +268,8 @@ class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
imSort.setOnClickListener { imSort.setOnClickListener {
sortDialogFragment = sortDialogFragment ?: SortDialogFragment { sortDialogFragment = sortDialogFragment ?: SortDialogFragment()
sortDialogFragment?.onClickSort = {
when (it) { when (it) {
SORT_ASC_DATE -> { SORT_ASC_DATE -> {
setDateAdapter() setDateAdapter()
@ -334,7 +334,7 @@ class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
} }
} }
sortDialogFragment?.show(supportFragmentManager, "") sortDialogFragment?.safeShow(supportFragmentManager, Common.TAG_DIALOG_SORT)
} }
//全选按钮 只对当前显示的数据有效 //全选按钮 只对当前显示的数据有效
@ -806,7 +806,7 @@ class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
onClickCancel = { onClickCancel = {
} }
show(supportFragmentManager, "") safeShow(supportFragmentManager, Common.TAG_DIALOG_DATE_START)
} }
@ -842,9 +842,9 @@ class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
onClickCancel = { onClickCancel = {
// filterDatePopupWindows?.dismiss()
} }
show(supportFragmentManager, "") safeShow(supportFragmentManager, Common.TAG_DIALOG_DATE_END)
} }
} }

View File

@ -3,6 +3,7 @@ package com.ux.video.file.filerecovery.success
import android.content.Intent import android.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.bumptech.glide.Glide
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityRecoverOrDeletedSuccessBinding import com.ux.video.file.filerecovery.databinding.ActivityRecoverOrDeletedSuccessBinding
@ -55,7 +56,10 @@ class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBind
} }
when (successType) { when (successType) {
0 -> { 0 -> {
imageCenter.setImageResource(R.drawable.image_recover_success) // imageCenter.setImageResource(R.drawable.image_recover_success)
Glide.with(this@RecoverySuccessActivity)
.load(R.drawable.image_recover_success)
.into(imageCenter)
tvNumber.setTextColor( tvNumber.setTextColor(
Common.getColorInt( Common.getColorInt(
this@RecoverySuccessActivity, this@RecoverySuccessActivity,
@ -80,7 +84,10 @@ class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBind
} }
1 -> { 1 -> {
imageCenter.setImageResource(R.drawable.image_deleted_success) // imageCenter.setImageResource(R.drawable.image_deleted_success)
Glide.with(this@RecoverySuccessActivity)
.load(R.drawable.image_deleted_success)
.into(imageCenter)
tvNumber.setTextColor( tvNumber.setTextColor(
Common.getColorInt( Common.getColorInt(
this@RecoverySuccessActivity, this@RecoverySuccessActivity,

View File

@ -32,6 +32,29 @@ object Common {
val KEY_FILE_TYPE = "key_file_type" val KEY_FILE_TYPE = "key_file_type"
//恢复中弹窗tag
val TAG_DIALOG_RECOVERING = "Dialog_Recovering"
//删除中弹窗tag
val TAG_DIALOG_DELETEING = "Dialog_Deleting"
//确认删除弹窗tag
val TAG_DIALOG_CONFIRM_DELETE = "Dialog_Confirm_Delete"
//请求管理所有文件弹窗tag
val TAG_DIALOG_PERMISSION = "Dialog_Permission"
//推出扫描结果弹窗tag
val TAG_DIALOG_EXIT = "Dialog_Exit"
//排序弹窗tag
val TAG_DIALOG_SORT = "Dialog_Sort"
//筛选开始日期弹窗tag
val TAG_DIALOG_DATE_START = "Dialog_start_date"
//筛选结束日期弹窗tag
val TAG_DIALOG_DATE_END = "Dialog_end_date"
val rootDir = Environment.getExternalStorageDirectory() val rootDir = Environment.getExternalStorageDirectory()

View File

@ -6,6 +6,8 @@ import android.icu.util.Calendar
import android.os.Build import android.os.Build
import android.os.Parcelable import android.os.Parcelable
import android.util.TypedValue import android.util.TypedValue
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.ux.video.file.filerecovery.db.ResultDataFiles import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.duration import com.ux.video.file.filerecovery.db.duration
@ -80,9 +82,6 @@ object ExtendFunctions {
} }
fun List<Pair<String, List<ResultDataFiles>>>.filterWithinDateRange( fun List<Pair<String, List<ResultDataFiles>>>.filterWithinDateRange(
months: Int = -1, months: Int = -1,
startDate: Date? = null, startDate: Date? = null,
@ -119,6 +118,7 @@ object ExtendFunctions {
} }
} }
} }
/** /**
* 按文件大小筛选区间 [minSize, maxSize] * 按文件大小筛选区间 [minSize, maxSize]
*/ */
@ -217,4 +217,21 @@ object ExtendFunctions {
} }
fun DialogFragment.safeShow(fragmentManager: FragmentManager, tag: String) {
if (tag.isBlank()) throw IllegalArgumentException("Fragment tag cannot be blank")
val existingFragment = fragmentManager.findFragmentByTag(tag)
if (existingFragment != null) {
return
}
if (!this.isAdded && !this.isVisible) {
try {
this.show(fragmentManager, tag)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}
} }

View File

@ -10,6 +10,9 @@ import android.provider.MediaStore
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.text.format.Formatter import android.text.format.Formatter
import android.util.Log import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.ux.video.file.filerecovery.db.ResultData import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles import com.ux.video.file.filerecovery.db.ResultDataFiles
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -65,7 +68,7 @@ object ScanManager {
if (file.isDirectory) { if (file.isDirectory) {
scanDocuments(file, depth + 1) scanDocuments(file, depth + 1)
} else { } else {
val fileCheckBoolean: Boolean = checkFileFormat(file,fileType) val fileCheckBoolean: Boolean = checkFileFormat(file, fileType)
if (fileCheckBoolean) { if (fileCheckBoolean) {
val dirName = file.parentFile?.name ?: "Unknown" val dirName = file.parentFile?.name ?: "Unknown"
@ -135,7 +138,7 @@ object ScanManager {
fun scanHiddenPhotoAsync( fun scanHiddenPhotoAsync(
context: Context, context: Context,
root: File, maxDepth: Int = 5, root: File, maxDepth: Int = 5,
maxFiles: Int = 5000, fileType: FileType maxFiles: Int = 5000, fileType: FileType
): Flow<ScanState> = flow { ): Flow<ScanState> = flow {
// scanRecycler(context) // scanRecycler(context)
val result = mutableMapOf<String, MutableList<File>>() val result = mutableMapOf<String, MutableList<File>>()
@ -161,7 +164,7 @@ object ScanManager {
val isHidden = file.name.startsWith(".") val isHidden = file.name.startsWith(".")
scanDir(file, depth + 1, insideHidden = insideHidden || isHidden) scanDir(file, depth + 1, insideHidden = insideHidden || isHidden)
} else { } else {
val fileCheckBoolean: Boolean = checkFileFormat(file,fileType) val fileCheckBoolean: Boolean = checkFileFormat(file, fileType)
if (insideHidden) { if (insideHidden) {
if (fileCheckBoolean) { if (fileCheckBoolean) {
val dirName = file.parentFile?.name ?: "Unknown" val dirName = file.parentFile?.name ?: "Unknown"
@ -269,7 +272,6 @@ object ScanManager {
} }
private fun getFileSizeByMediaStore(context: Context, file: File): Long { private fun getFileSizeByMediaStore(context: Context, file: File): Long {
val uri = Uri.fromFile(file) val uri = Uri.fromFile(file)
context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null) context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
@ -329,19 +331,25 @@ object ScanManager {
* 做批量恢复 * 做批量恢复
* @param folder "AllRecovery/Photo" * @param folder "AllRecovery/Photo"
*/ */
fun CoroutineScope.copySelectedFilesAsync( fun LifecycleOwner.copySelectedFilesAsync(
selectedSet: Set<ResultDataFiles>, selectedSet: Set<ResultDataFiles>,
rootDir: File = Common.rootDir, rootDir: File = Common.rootDir,
folder: String, folder: String,
onProgress: (currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean) -> Unit, onProgress: (currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean) -> Unit,
onComplete: (currentCounts: Int) -> Unit onComplete: (currentCounts: Int) -> Unit
) { ) {
launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
var recoveryCount = 0 var recoveryCount = 0
val targetDir = File(rootDir, folder) val targetDir = File(rootDir, folder)
if (!targetDir.exists()) targetDir.mkdirs() if (!targetDir.exists()) targetDir.mkdirs()
selectedSet.forEachIndexed { index, resultPhotosFiles -> selectedSet.forEachIndexed { index, resultPhotosFiles ->
val srcFile = File(resultPhotosFiles.path!!) // 页面已销毁,直接停
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Common.showLog("----------------页面已销毁,直接停")
return@launch
}
Common.showLog("----------------resultPhotosFiles.path=${resultPhotosFiles.path}")
val srcFile = File(resultPhotosFiles.path)
if (srcFile.exists() && srcFile.isFile) { if (srcFile.exists() && srcFile.isFile) {
val destFile = File(targetDir, srcFile.name) val destFile = File(targetDir, srcFile.name)
var success = false var success = false
@ -351,12 +359,17 @@ object ScanManager {
input.copyTo(output) input.copyTo(output)
} }
} }
success = destFile.exists() && destFile.length() > 0
Common.showLog("------------success------${success}")
success = true success = true
recoveryCount++ recoveryCount++
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onProgress(index + 1, resultPhotosFiles, srcFile.name, success) if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED))
onProgress(index + 1, resultPhotosFiles, srcFile.name, success)
} }
} catch (e: Exception) { } catch (e: Exception) {
Common.showLog("------------Exception------${e.message}")
e.printStackTrace() e.printStackTrace()
} }
@ -364,7 +377,8 @@ object ScanManager {
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onComplete(recoveryCount) if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED))
onComplete(recoveryCount)
} }
} }
} }
@ -374,12 +388,12 @@ object ScanManager {
* 做批量删除文件 * 做批量删除文件
* *
*/ */
fun CoroutineScope.deleteFilesAsync( fun LifecycleOwner.deleteFilesAsync(
selectedSet: Set<ResultDataFiles>, selectedSet: Set<ResultDataFiles>,
onProgress: (currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean) -> Unit, onProgress: (currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean) -> Unit,
onComplete: (currentCounts: Int) -> Unit onComplete: (currentCounts: Int) -> Unit
) { ) {
launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
var deletedCount = 0 var deletedCount = 0
selectedSet.forEachIndexed { index, resultPhotosFiles -> selectedSet.forEachIndexed { index, resultPhotosFiles ->
try { try {

View File

@ -69,8 +69,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
setOnClickListener { setOnClickListener {
RecoverOrDeleteManager.showConfirmDeleteDialog( RecoverOrDeleteManager.showConfirmDeleteDialog(
true, true,
supportFragmentManager, this@VideoPlayActivity,
lifecycleScope,
setOf(resultPhotosFiles) setOf(resultPhotosFiles)
) { count -> ) { count ->
complete(count, 1) complete(count, 1)
@ -82,8 +81,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
text = resources.getString(R.string.recover) text = resources.getString(R.string.recover)
setOnClickListener { setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog( RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager, this@VideoPlayActivity,
lifecycleScope,
setOf(resultPhotosFiles) setOf(resultPhotosFiles)
) { count -> ) { count ->
complete(count, 0) complete(count, 0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 25 KiB