This commit is contained in:
litingting 2025-12-26 13:58:57 +08:00
parent b069402b22
commit ca7a7f9da6
68 changed files with 2192 additions and 1314 deletions

BIN
app/FileRecovery Normal file

Binary file not shown.

View File

@ -1,11 +1,16 @@
import java.text.SimpleDateFormat
import java.util.Date
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id ("kotlin-kapt")
id ("kotlin-parcelize")
id("io.objectbox")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
}
val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date())
android {
namespace = "com.ux.video.file.filerecovery"
compileSdk = 36
@ -16,13 +21,13 @@ android {
targetSdk = 36
versionCode = 1
versionName = "1.0"
project.setProperty("archivesBaseName", "File Recovery Tool" + versionName + "(${versionCode})_$timestamp")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@ -56,9 +61,17 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
implementation (libs.glide)
kapt (libs.compiler)
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
implementation ("com.google.android.material:material:1.13.0")
implementation(project(":pickerview"))
implementation ("androidx.media3:media3-exoplayer:1.8.0")
implementation ("androidx.media3:media3-ui:1.8.0")
// implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.browser:browser:1.8.0")
implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-config")
}

29
app/google-services.json Normal file
View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "1034840485270",
"project_id": "file-recovery-3d900",
"storage_bucket": "file-recovery-3d900.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1034840485270:android:6d9c203fa586c78cde81b7",
"android_client_info": {
"package_name": "com.ux.video.file.filerecovery"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyD6LDj1f2IQeA0oS6TaanB0-_B334k6PBI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -43,11 +43,6 @@
"id": "7:8305655849151310709",
"name": "lastModified",
"type": 6
},
{
"id": "8:5134665607056785092",
"name": "resolution",
"type": 9
}
],
"relations": []
@ -71,7 +66,8 @@
3831579248666795267,
4445203567182319472,
6858261029979256233,
2835789057594891780
2835789057594891780,
5134665607056785092
],
"retiredRelationUids": [],
"version": 1

View File

@ -19,3 +19,62 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# ========= Kotlin Parcelize =========
-keep class kotlinx.parcelize.Parcelize
-keep class kotlinx.android.parcel.Parcelize
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# ResultDataFiles Parcelable
-keep class com.ux.video.file.filerecovery.db.ResultDataFiles implements android.os.Parcelable {
<fields>;
<methods>;
}
-keepclassmembers class com.ux.video.file.filerecovery.db.ResultDataFiles {
public static final android.os.Parcelable$Creator CREATOR;
}
-keep class com.ux.video.file.filerecovery.db.ResultDataFiles { *; }
-keep class com.ux.video.file.filerecovery.db.ResultData { *; }
-keep enum com.ux.video.file.filerecovery.utils.FileType { *; }
-keep class com.ux.video.file.filerecovery.db.FileTypeConverter { *; }
-keep class io.objectbox.** { *; }
-keep @io.objectbox.annotation.Entity class * { *; }
# ResultDataFiles Parcelable
-keep class com.ux.video.file.filerecovery.db.ResultDataFiles implements android.os.Parcelable {
<fields>;
<methods>;
}
-keepclassmembers class com.ux.video.file.filerecovery.db.ResultDataFiles {
public static final android.os.Parcelable$Creator CREATOR;
}
# FileType enum
-keep enum com.ux.video.file.filerecovery.utils.FileType { *; }
# ObjectBox Converter
-keep class com.ux.video.file.filerecovery.db.FileTypeConverter { *; }
# ObjectBox
-keep class io.objectbox.** { *; }
-keep @io.objectbox.annotation.Entity class * { *; }
# Kotlin Parcelize support
-keep class kotlinx.parcelize.Parcelize
-keep class kotlinx.android.parcel.Parcelize
-keep class kotlin.Metadata { *; }

View File

@ -13,49 +13,76 @@
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:icon="@mipmap/recovery"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/recovery"
android:supportsRtl="true"
android:theme="@style/Theme.FileRecovery"
tools:targetApi="31">
<activity
android:name=".recovery.RecoveryActivity"
android:name=".settings.PrivacyPolicyActivity"
android:exported="false" />
<activity
android:name=".video.VideoPlayActivity"
android:name=".settings.SetupActivity"
android:exported="false" />
<activity
android:name=".sort.PhotoInfoActivity"
android:exported="false" />
<activity
android:name=".sort.PhotoSortingActivity"
android:exported="false" />
<activity
android:name=".result.ScanResultDisplayActivity"
android:exported="false" />
<activity
android:name=".result.ScanningActivity"
android:exported="false" />
<activity
android:name=".main.ScanSelectTypeActivity"
android:exported="false" />
<activity
android:name=".documents.DocumentsScanResultActivity"
android:exported="false" />
<activity
android:name=".success.RecoverySuccessActivity"
android:exported="false" />
<activity
android:name=".main.MainActivity"
android:exported="true">
android:name=".welcome.SplashActivity"
android:exported="true"
android:screenOrientation="portrait"
tools:ignore="SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".recovery.RecoveryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".video.VideoPlayActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".detail.DetailsActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".sort.SortingActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".result.ScanResultDisplayActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".result.ScanningActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".main.ScanSelectTypeActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".success.RecoverySuccessActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".main.MainActivity"
android:exported="false"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@ -13,6 +13,9 @@ import androidx.fragment.app.DialogFragment
import com.ux.video.file.filerecovery.databinding.DialogRecoveringBinding
/**
* 删除/恢复中父类弹窗
*/
abstract class BaseIngDialogFragment() : DialogFragment() {
var total: Int = 0
var completeListener: ((number: Int) -> Unit)? = null

View File

@ -0,0 +1,15 @@
package com.ux.video.file.filerecovery.db
import com.ux.video.file.filerecovery.utils.FileType
import io.objectbox.converter.PropertyConverter
class FileTypeConverter : PropertyConverter<FileType, Int> {
override fun convertToDatabaseValue(entityProperty: FileType?): Int? {
return entityProperty?.value
}
override fun convertToEntityProperty(databaseValue: Int?): FileType? {
return FileType.entries.find { it.value == databaseValue }
}
}

View File

@ -1,13 +1,17 @@
package com.ux.video.file.filerecovery.db
import android.content.Context
import androidx.lifecycle.LifecycleCoroutineScope
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.config.DebugFlags
import io.objectbox.reactive.DataSubscription
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.jvm.java
@ -92,6 +96,21 @@ object ObjectBoxManager {
}
}
fun deleteRecoveryFilesAsyncById(lifecycleCoroutineScope: LifecycleCoroutineScope,list: List<ResultDataFiles>, onComplete: () -> Unit) {
lifecycleCoroutineScope.launch(Dispatchers.IO) {
val objectBox = getDataBox()
val ids = list.map { it.id }
objectBox.removeByIds(ids)
withContext(Dispatchers.Main) {
onComplete()
}
}
}
//
// fun queryIsLike(id: Long): Boolean {
// val likeBox: Box<LikeWallpaper>? = getLikeBox()
@ -113,10 +132,10 @@ object ObjectBoxManager {
return list
}
suspend fun queryRecoveryFileAsync(type: Int): List<ResultDataFiles> =
suspend fun queryRecoveryFileAsync(type: FileType): List<ResultDataFiles> =
withContext(Dispatchers.IO) {
val dataBox = getDataBox()
dataBox.query(ResultDataFiles_.fileType.equal(type))
dataBox.query(ResultDataFiles_.fileType.equal(type.value))
.build()
.find()
}

View File

@ -4,6 +4,8 @@ import java.io.File
import android.os.Parcelable
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
import io.objectbox.annotation.Convert
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import kotlinx.parcelize.Parcelize
@ -13,35 +15,40 @@ import kotlinx.parcelize.Parcelize
data class ResultDataFiles(
@Id
var id: Long = 0,
// 0-3 photo\video\audio\documents
var fileType: Int,
@Convert(converter = FileTypeConverter::class, dbType = Int::class)
var fileType: FileType,
val name: String,
val path: String,
val size: Long, // 字节
val sizeString: String,
val lastModified: Long, // 时间戳
var resolution: String // 尺寸
) : Parcelable {
val targetFile: File?
get() = path?.let { File(it) }
// val targetFile: File
// get() = path.let { File(it) }
//尺寸
// val resolution: String
// get() = Common.getResolution(fileType, targetFile)
//是否为缩略图文件(宽高任一小于 256
val isThumbnail: Boolean
get() {
val parts = resolution.lowercase().split("*").mapNotNull {
it.trim().toIntOrNull()
}
if (parts.size == 2) {
val (width, height) = parts
return width < 256 || height < 256
}
return false
}
// val isThumbnail: Boolean
// get() {
// val parts = resolution.lowercase().split("*").mapNotNull {
// it.trim().toIntOrNull()
// }
// if (parts.size == 2) {
// val (width, height) = parts
// return width < 256 || height < 256
// }
// return false
// }
//音视频时长
val duration: Long
get() {
return Common.getMediaDuration(path.toString()) }
// val duration: Long
// get() {
// return Common.getMediaDuration(path.toString())
// }
}

View File

@ -0,0 +1,37 @@
package com.ux.video.file.filerecovery.db
import com.ux.video.file.filerecovery.utils.Common
import java.io.File
/**
* 目标文件
*/
fun ResultDataFiles.targetFile(): File =
File(this.path)
/**
* 分辨率图片 / 视频
*/
fun ResultDataFiles.resolution(): String =
Common.getResolution(this.fileType, targetFile())
/**
* 是否缩略图宽或高 < 256
*/
fun ResultDataFiles.isThumbnail(): Boolean {
val parts = resolution()
.lowercase()
.split("*")
.mapNotNull { it.trim().toIntOrNull() }
if (parts.size == 2) {
val (width, height) = parts
return width < 256 || height < 256
}
return false
}
/**
* 音视频时长
*/
fun ResultDataFiles.duration(): Long =
Common.getMediaDuration(this.path)

View File

@ -0,0 +1,300 @@
package com.ux.video.file.filerecovery.detail
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityDetailsBinding
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.duration
import com.ux.video.file.filerecovery.db.resolution
import com.ux.video.file.filerecovery.db.targetFile
import com.ux.video.file.filerecovery.sort.RecoverOrDeleteManager
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanType
import com.ux.video.file.filerecovery.video.PlayMediaManager
import com.ux.video.file.filerecovery.video.VideoPlayActivity
import java.io.File
class DetailsActivity : BaseActivity<ActivityDetailsBinding>() {
companion object {
val KEY_CLICK_ITEM = "click_item"
val KEY_SHOW_SHARE = "show_share"
}
private var playMediaManager: PlayMediaManager? = null
private lateinit var fileType: FileType
private var myData: ResultDataFiles? = null
private var showShare = false
override fun inflateBinding(inflater: LayoutInflater): ActivityDetailsBinding =
ActivityDetailsBinding.inflate(inflater)
override fun initView() {
super.initView()
myData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(KEY_CLICK_ITEM, ResultDataFiles::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(KEY_CLICK_ITEM)
}
val intExtra = intent.getIntExtra(Common.KEY_FILE_TYPE, FileType.PHOTO.value)
myData?.fileType.let {
if (it != null) {
fileType = it
Common.showLog("------fileType=${fileType}")
}
}
showShare = intent.getBooleanExtra(KEY_SHOW_SHARE, false)
setView()
}
override fun initData() {
super.initData()
binding.run {
imageViewBack.setOnClickListener { finish() }
myData?.let { resultPhotosFiles ->
tvName.text = resultPhotosFiles.name
tvPath.text = resultPhotosFiles.path
tvSize.text = resultPhotosFiles.sizeString
tvDate.text = Common.getFormatDate(resultPhotosFiles.lastModified)
tvResolution.text = resultPhotosFiles.resolution()
tvDuration.text = Common.formatDuration(resultPhotosFiles.duration())
resultPhotosFiles.targetFile().let {
tvType.text = Common.getMimeTypeParts(it)
}
}
}
}
private fun setView() {
binding.run {
layoutBottom.tvLeft.run {
text = resources.getString(R.string.delete)
setOnClickListener {
myData?.let { myData ->
RecoverOrDeleteManager.showConfirmDeleteDialog(
true,
supportFragmentManager,
lifecycleScope,
setOf(myData)
) { count ->
complete(count, 1)
}
}
}
}
layoutBottom.tvRight.run {
myData?.let { myData ->
if (showShare) {
text = getString(R.string.share)
setOnClickListener {
Common.shareSingleFile(
this@DetailsActivity,
myData.targetFile(),
myData.fileType
)
}
} else {
text = getString(R.string.recover)
setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager,
lifecycleScope,
setOf(myData)
) { count ->
complete(count, 0)
}
}
}
}
}
myData?.let {
when (it.fileType) {
FileType.PHOTO -> {
layoutName.isVisible = true
layoutPath.isVisible = true
layoutResolution.isVisible = true
layoutDate.isVisible = true
frameImage.setBackgroundResource(0)
layoutSeekbar.isVisible = false
layoutType.isVisible = false
layoutSize.isVisible = false
layoutDuration.isVisible = false
imPlay.isVisible = false
myData?.targetFile()?.let { loadImage(image, it) }
}
FileType.VIDEO -> {
layoutName.isVisible = true
layoutPath.isVisible = true
layoutResolution.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = true
frameImage.setBackgroundResource(0)
layoutSeekbar.isVisible = false
layoutType.isVisible = false
layoutSize.isVisible = false
imPlay.isVisible = true
myData?.let { data ->
loadImage(image, data.targetFile())
frameImage.setOnClickListener {
startActivity(
Intent(
this@DetailsActivity,
VideoPlayActivity::class.java
).apply {
putExtra(VideoPlayActivity.KEY_DATA, data)
})
}
}
}
FileType.AUDIO -> {
Common.showLog("----------音频")
layoutName.isVisible = true
layoutPath.isVisible = true
layoutSize.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = true
layoutSeekbar.isVisible = true
imPlay.isVisible = true
frameImage.setBackgroundResource(R.drawable.bg_info_music_f2f2f7_8)
loadCenterImage(image, R.drawable.image_info_music)
initPlayAudio()
layoutResolution.isVisible = false
layoutType.isVisible = false
}
FileType.DOCUMENT -> {
layoutName.isVisible = true
layoutType.isVisible = true
layoutPath.isVisible = true
layoutSize.isVisible = true
layoutDate.isVisible = true
frameImage.setBackgroundResource(R.drawable.bg_info_music_f2f2f7_8)
myData?.targetFile()?.let {
loadCenterImage(image, Common.getFileIconRes(it))
}
imPlay.isVisible = false
layoutSeekbar.isVisible = false
layoutResolution.isVisible = false
layoutDuration.isVisible = false
}
}
}
}
}
private fun loadCenterImage(image: ImageView, drawableId: Int) {
image.setImageResource(drawableId)
val params = image.layoutParams ?: ViewGroup.LayoutParams(
180.dpToPx(this@DetailsActivity),
180.dpToPx(this@DetailsActivity)
)
params.width = 180.dpToPx(this@DetailsActivity)
params.height = 180.dpToPx(this@DetailsActivity)
image.layoutParams = params
}
private fun loadImage(image: ImageView, file: File) {
Glide.with(this@DetailsActivity)
.load(file)
.error(R.drawable.photo_place_holder)
.apply(
RequestOptions().transform(
CenterCrop(),
RoundedCorners(8.dpToPx(this@DetailsActivity))
)
)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable?>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(image)
}
private fun initPlayAudio() {
myData?.targetFile()?.let {
binding.run {
playMediaManager = PlayMediaManager(
context = this@DetailsActivity, mediaFile = it,
seekBar = seekBar, playBtn = imPlay, onUpdateProgress = { current, total ->
textTimeCurrent.text = current
textTimeTotal.text = total
})
}
}
}
private fun complete(number: Int, type: Int) {
finish()
startActivity(Intent(this@DetailsActivity, RecoverySuccessActivity::class.java).apply {
putExtra(RecoverySuccessActivity.Companion.KEY_SUCCESS_COUNT, number)
putExtra(RecoverySuccessActivity.Companion.KEY_SUCCESS_TYPE, type)
putExtra(Common.KEY_FILE_TYPE, fileType.value)
})
}
override fun onDestroy() {
super.onDestroy()
playMediaManager?.stopPlayAudio()
}
}

View File

@ -1,33 +0,0 @@
package com.ux.video.file.filerecovery.documents
import android.view.LayoutInflater
import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityDocumentsScanResultBinding
import com.ux.video.file.filerecovery.utils.ScanRepository
class DocumentsScanResultActivity : BaseActivity<ActivityDocumentsScanResultBinding>() {
private var resultAdapter: DocumentsScanResultAdapter? = null
override fun inflateBinding(inflater: LayoutInflater): ActivityDocumentsScanResultBinding =
ActivityDocumentsScanResultBinding.inflate(layoutInflater)
override fun initView() {
super.initView()
resultAdapter = DocumentsScanResultAdapter(this@DocumentsScanResultActivity)
binding.recyclerView.run {
adapter = resultAdapter
layoutManager = LinearLayoutManager(this@DocumentsScanResultActivity)
}
}
override fun initData() {
super.initData()
// ScanRepository.instance.photoResults.observe(this@DocumentsScanResultActivity) {
// resultAdapter?.setData(it)
// }
}
}

View File

@ -1,32 +0,0 @@
package com.ux.video.file.filerecovery.documents
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.databinding.DocumentsScanResultAdapterBinding
import com.ux.video.file.filerecovery.db.ResultData
class DocumentsScanResultAdapter(mContext: Context) :
BaseAdapter<ResultData, DocumentsScanResultAdapterBinding>(mContext) {
override fun getViewBinding(parent: ViewGroup): DocumentsScanResultAdapterBinding =
DocumentsScanResultAdapterBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
override fun bindItem(
holder: VHolder<DocumentsScanResultAdapterBinding>,
item: ResultData
) {
holder.vb.run {
item.run {
tvDirName.text = dirName
tvFileCount.text = allFiles.size.toString()
}
}
}
}

View File

@ -10,25 +10,25 @@ import android.os.Build
import android.os.Environment
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.isVisible
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityMainBinding
import com.ux.video.file.filerecovery.recovery.RecoveryActivity
import com.ux.video.file.filerecovery.sort.DatePickerDialogFragment
import com.ux.video.file.filerecovery.settings.SetupActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanManager
class MainActivity : BaseActivity<ActivityMainBinding>() {
private var dialogCustomerDateStart:DatePickerDialogFragment? = null
private var pendingAction: NavigateAction? = null
private var dialogPermission: PermissionDialogFragment? = null
//是否正确引导用户打开所有文件管理权限
private var isRequestPermission = false
private var currentGoType = ScanSelectTypeActivity.Companion.VALUE_PHOTO
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
var isAllOK = true
@ -54,47 +54,49 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initView() {
super.initView()
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
ScanManager.showLog("权限", "====0000000")
if (binding.layoutPermission.isVisible) {
ScanManager.showLog("权限", "====111111")
binding.layoutPermission.visibility = View.GONE
} else {
ScanManager.showLog("权限", "====222222222222")
finish()
}
}
})
// onBackPressedDispatcher.addCallback(
// this,
// object : OnBackPressedCallback(true) {
// override fun handleOnBackPressed() {
// ScanManager.showLog("权限", "====0000000")
// if (binding.layoutPermission.isVisible) {
// ScanManager.showLog("权限", "====111111")
//// binding.layoutPermission.visibility = View.GONE
// } else {
// ScanManager.showLog("权限", "====222222222222")
// finish()
// }
// }
// })
}
override fun initData() {
super.initData()
initTitleColor()
binding.run {
allow.setOnClickListener {
imSetting.setOnClickListener {
startActivity(Intent(this@MainActivity, SetupActivity::class.java))
}
layoutPhoto.setOnClickListener {
currentGoType = ScanSelectTypeActivity.Companion.VALUE_PHOTO
pendingAction = NavigateAction.Scan(FileType.PHOTO)
intentCheck()
}
layoutVideo.setOnClickListener {
currentGoType = ScanSelectTypeActivity.Companion.VALUE_VIDEO
pendingAction = NavigateAction.Scan(FileType.VIDEO)
intentCheck()
}
layoutAudio.setOnClickListener {
currentGoType = ScanSelectTypeActivity.Companion.VALUE_AUDIO
pendingAction = NavigateAction.Scan(FileType.AUDIO)
intentCheck()
}
layoutDocument.setOnClickListener {
currentGoType = ScanSelectTypeActivity.Companion.VALUE_DOCUMENT
pendingAction = NavigateAction.Scan(FileType.DOCUMENT)
intentCheck()
}
layoutRecovery.setOnClickListener {
startActivity(Intent(this@MainActivity,RecoveryActivity::class.java))
pendingAction = NavigateAction.Recovered
intentCheck()
}
}
binding.tvTitle.setOnClickListener {
@ -102,53 +104,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
showDatePicker()
}
binding.btnScanAllPhoto.setOnClickListener {
startActivity(Intent(this@MainActivity, ScanSelectTypeActivity::class.java).apply {
putExtra(
ScanSelectTypeActivity.Companion.KEY_FILE_TYPE,
ScanSelectTypeActivity.Companion.VALUE_PHOTO
)
})
}
binding.btnScanAllVideo.setOnClickListener {
startActivity(Intent(this@MainActivity, ScanSelectTypeActivity::class.java).apply {
putExtra(
ScanSelectTypeActivity.Companion.KEY_FILE_TYPE,
ScanSelectTypeActivity.Companion.VALUE_VIDEO
)
})
}
binding.btnScanAllAudio.setOnClickListener {
startActivity(Intent(this@MainActivity, ScanSelectTypeActivity::class.java).apply {
putExtra(
ScanSelectTypeActivity.Companion.KEY_FILE_TYPE,
ScanSelectTypeActivity.Companion.VALUE_AUDIO
)
})
}
binding.btnScanAllFile.setOnClickListener {
// val results = mutableListOf<ResultPhotos>()
val root = Environment.getExternalStorageDirectory()
// ScanManager.scanAllDocuments(root, results)
// ScanRepository.instance.setResults(results)
// startActivity(Intent(this@MainActivity,DocumentsScanResultActivity::class.java))
//
// results.forEach { doc ->
// ScanManager.showLog("FileScan", "目录: ${doc.dirName}, 文件数: ${doc.allFiles.size}")
// doc.allFiles.forEach { file ->
// ScanManager.showLog("FileScan", " -> 文件: ${file.targetFile.isHidden} ")
// }
// }
}
}
private fun initTitleColor() {
@ -158,7 +113,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val width = paint.measureText(text.toString())
val shader = LinearGradient(
0f, 0f, width, textSize,
intArrayOf(getColor(R.color.color_title_start_color),getColor(R.color.color_title_end_color)),
intArrayOf(
getColor(R.color.color_title_start_color),
getColor(R.color.color_title_end_color)
),
null,
Shader.TileMode.CLAMP
)
@ -177,7 +135,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
isRequestPermission = false
ScanManager.showLog("--", "-------onResume")
startScan()
binding.layoutPermission.visibility = View.GONE
}
}
@ -236,14 +193,31 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
private fun startScan() {
startActivity(Intent(this@MainActivity, ScanSelectTypeActivity::class.java).apply {
putExtra(ScanSelectTypeActivity.Companion.KEY_FILE_TYPE, currentGoType)
when (val action = pendingAction) {
is NavigateAction.Scan -> startActivity(
Intent(
this@MainActivity,
ScanSelectTypeActivity::class.java
).apply {
putExtra(Common.KEY_FILE_TYPE, action.fileType.value)
})
NavigateAction.Recovered -> startActivity(
Intent(
this@MainActivity,
RecoveryActivity::class.java
)
)
else -> Unit
}
pendingAction = null
}
override fun onDestroy() {
super.onDestroy()
binding.layoutPermission.isVisible = false
// binding.layoutPermission.isVisible = false
}

View File

@ -0,0 +1,8 @@
package com.ux.video.file.filerecovery.main
import com.ux.video.file.filerecovery.utils.FileType
sealed class NavigateAction {
data class Scan(val fileType: FileType) : NavigateAction()
object Recovered : NavigateAction()
}

View File

@ -6,15 +6,9 @@ import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityScanSelectTypeBinding
import com.ux.video.file.filerecovery.result.ScanningActivity
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanType
import kotlin.properties.Delegates
@ -23,14 +17,6 @@ import kotlin.properties.Delegates
*/
class ScanSelectTypeActivity : BaseActivity<ActivityScanSelectTypeBinding>() {
companion object {
val KEY_FILE_TYPE = "file_type"
val VALUE_PHOTO = 0
val VALUE_VIDEO = 1
val VALUE_AUDIO = 2
val VALUE_DOCUMENT = 3
}
private var allType by Delegates.notNull<Int>()
private var deletedType by Delegates.notNull<Int>()
override fun inflateBinding(inflater: LayoutInflater): ActivityScanSelectTypeBinding =
@ -39,8 +25,10 @@ class ScanSelectTypeActivity : BaseActivity<ActivityScanSelectTypeBinding>() {
override fun initView() {
super.initView()
val type = intent.getIntExtra(KEY_FILE_TYPE, VALUE_PHOTO)
setSelectType(type)
val intExtra = intent.getIntExtra(Common.KEY_FILE_TYPE, FileType.PHOTO.value)
val fileType = FileType.from(intExtra)
fileType?.let { setSelectType(it) }
}
@ -50,40 +38,40 @@ class ScanSelectTypeActivity : BaseActivity<ActivityScanSelectTypeBinding>() {
binding.scanAllFile.setOnClickListener {
startActivity(Intent(this@ScanSelectTypeActivity, ScanningActivity::class.java).apply {
putExtra(KEY_SCAN_TYPE, allType)
putExtra(Common.KEY_SCAN_TYPE, allType)
})
}
binding.scanDeletedFile.setOnClickListener {
startActivity(Intent(this@ScanSelectTypeActivity, ScanningActivity::class.java).apply {
putExtra(KEY_SCAN_TYPE, deletedType)
putExtra(Common.KEY_SCAN_TYPE, deletedType)
})
}
}
private fun setSelectType(fileType: Int) {
private fun setSelectType(fileType: FileType) {
when (fileType) {
VALUE_PHOTO -> {
allType = VALUE_SCAN_TYPE_photo
deletedType = VALUE_SCAN_TYPE_deleted_photo
FileType.PHOTO -> {
allType = ScanType.ALL_PHOTO.value
deletedType = ScanType.DELETED_PHOTO.value
binding.title.text = getString(R.string.photo_title)
}
VALUE_VIDEO -> {
allType = VALUE_SCAN_TYPE_video
deletedType = VALUE_SCAN_TYPE_deleted_video
FileType.VIDEO -> {
allType = ScanType.ALL_VIDEO.value
deletedType = ScanType.DELETED_VIDEO.value
binding.title.text = getString(R.string.video_title)
}
VALUE_AUDIO -> {
allType = VALUE_SCAN_TYPE_audio
deletedType = VALUE_SCAN_TYPE_deleted_audio
FileType.AUDIO -> {
allType = ScanType.ALL_AUDIO.value
deletedType = ScanType.DELETED_AUDIO.value
binding.title.text = getString(R.string.audio_title)
}
VALUE_DOCUMENT -> {
allType = VALUE_SCAN_TYPE_documents
deletedType = VALUE_SCAN_TYPE_deleted_documents
FileType.DOCUMENT -> {
allType = ScanType.ALL_DOCUMENT.value
deletedType = ScanType.DELETED_DOCUMENT.value
binding.title.text = getString(R.string.document_title)
}
}

View File

@ -11,26 +11,32 @@ import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityRecoveryBinding
import com.ux.video.file.filerecovery.db.ObjectBoxManager
import com.ux.video.file.filerecovery.recovery.ui.MyViewPage2Adapter
import com.ux.video.file.filerecovery.recovery.ui.recoveryphoto.RecoveryPhotoFragment
import com.ux.video.file.filerecovery.recovery.ui.recoveryphoto.RecoveredFragment
import com.ux.video.file.filerecovery.recovery.ui.recoveryphoto.RecoveryPhotoViewModel
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.CustomTextView
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanManager
import kotlinx.coroutines.launch
class RecoveryActivity : BaseActivity<ActivityRecoveryBinding>() {
val sharedViewModel: RecoveryPhotoViewModel by viewModels()
private lateinit var fileType: FileType
override fun inflateBinding(inflater: LayoutInflater): ActivityRecoveryBinding =
ActivityRecoveryBinding.inflate(inflater)
override fun initView() {
super.initView()
val intExtra = intent.getIntExtra(Common.KEY_FILE_TYPE, FileType.PHOTO.value)
fileType = FileType.from(intExtra)!!
binding.run {
val fragments = listOf(
RecoveryPhotoFragment.newInstance(Common.FILE_TYPE_PHOTO),
RecoveryPhotoFragment.newInstance(Common.FILE_TYPE_VIDEO),
RecoveryPhotoFragment.newInstance(Common.FILE_TYPE_AUDIO),
RecoveryPhotoFragment.newInstance(Common.FILE_TYPE_DOCUMENTS)
RecoveredFragment.newInstance(FileType.PHOTO.value),
RecoveredFragment.newInstance(FileType.VIDEO.value),
RecoveredFragment.newInstance(FileType.AUDIO.value),
RecoveredFragment.newInstance(FileType.DOCUMENT.value)
)
viewPage2.adapter = MyViewPage2Adapter(this@RecoveryActivity, fragments)
TabLayoutMediator(tabLayout, viewPage2) { tab, position ->
@ -74,6 +80,17 @@ class RecoveryActivity : BaseActivity<ActivityRecoveryBinding>() {
})
ScanManager.showLog("-----tt-------", "-------fileType=${fileType}")
fileType.run {
when (this) {
FileType.PHOTO -> viewPage2.currentItem = 0
FileType.VIDEO -> viewPage2.currentItem = 1
FileType.AUDIO -> viewPage2.currentItem = 2
FileType.DOCUMENT -> viewPage2.currentItem = 3
}
}
}
}
@ -82,27 +99,48 @@ class RecoveryActivity : BaseActivity<ActivityRecoveryBinding>() {
binding.imageBack.setOnClickListener {
finish()
}
sharedViewModel.photoFiles.observe(this) {
setTabCount( FileType.PHOTO, it.size)
}
sharedViewModel.videoFiles.observe(this) {
setTabCount( FileType.VIDEO, it.size)
}
sharedViewModel.audioFiles.observe(this) {
setTabCount( FileType.AUDIO, it.size)
}
sharedViewModel.documentsFiles.observe(this) {
setTabCount(FileType.DOCUMENT, it.size)
}
lifecycleScope.launch {
val recoveryFilePhoto = ObjectBoxManager.queryRecoveryFileAsync(Common.FILE_TYPE_PHOTO)
sharedViewModel.setPhotosRecoveredFiles(recoveryFilePhoto)
val recoveryFileVideo = ObjectBoxManager.queryRecoveryFileAsync(Common.FILE_TYPE_VIDEO)
sharedViewModel.setVideosRecoveredFiles(recoveryFileVideo)
val recoveryFileAudio = ObjectBoxManager.queryRecoveryFileAsync(Common.FILE_TYPE_AUDIO)
sharedViewModel.setAudiosRecoveredFiles(recoveryFileAudio)
val recoveryFileDocuments =
ObjectBoxManager.queryRecoveryFileAsync(Common.FILE_TYPE_DOCUMENTS)
sharedViewModel.setDocumentsRecoveredFiles(recoveryFileDocuments)
binding.tabLayout.run {
getTabAt(0)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
getString(R.string.text_counts, recoveryFilePhoto.size)
getTabAt(1)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
getString(R.string.text_counts, recoveryFileVideo.size)
getTabAt(2)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
getString(R.string.text_counts, recoveryFileAudio.size)
getTabAt(3)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
getString(R.string.text_counts, recoveryFileDocuments.size)
FileType.entries.forEach{ fileType->
val recoveryFilePhoto = ObjectBoxManager.queryRecoveryFileAsync(fileType)
sharedViewModel.setRecoveredFiles(fileType, recoveryFilePhoto)
setTabCount(fileType, recoveryFilePhoto.size)
}
}
}
private fun setTabCount(fileType: FileType, number: Int) {
binding.tabLayout.run {
getTabAt(fileType.tabIndex)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
getString(R.string.text_counts, number)
// when (fileType) {
// FileType.PHOTO -> getTabAt(0)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
// getString(R.string.text_counts, number)
//
// FileType.VIDEO -> getTabAt(1)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
// getString(R.string.text_counts, number)
//
// FileType.AUDIO -> getTabAt(2)?.customView?.findViewById<CustomTextView>(R.id.tab_item_count)?.text =
// getString(R.string.text_counts, number)
//
// FileType.DOCUMENT -> getTabAt(3)?.customView?.findViewById<CustomTextView>(
// R.id.tab_item_count
// )?.text =
// getString(R.string.text_counts, number)
// }
}
}
}

View File

@ -0,0 +1,43 @@
package com.ux.video.file.filerecovery.recovery.ui.recoveryphoto
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment
import com.ux.video.file.filerecovery.databinding.DialogDeleteSuccessBinding
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
class DeleteSuccessDialog(): DialogFragment() {
private lateinit var binding: DialogDeleteSuccessBinding
override fun onStart() {
super.onStart()
dialog?.setCanceledOnTouchOutside(false)
isCancelable = false
dialog?.window?.apply {
setLayout(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
val params = attributes
params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
params.y = 100.dpToPx(context)
attributes = params
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DialogDeleteSuccessBinding.inflate(inflater)
return binding.root
}
}

View File

@ -0,0 +1,297 @@
package com.ux.video.file.filerecovery.recovery.ui.recoveryphoto
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseFragment
import com.ux.video.file.filerecovery.databinding.FragmentRecoveryPhotoBinding
import com.ux.video.file.filerecovery.db.ObjectBoxManager
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.targetFile
import com.ux.video.file.filerecovery.detail.DetailsActivity
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity
import com.ux.video.file.filerecovery.sort.SortDisplayChildAdapter
import com.ux.video.file.filerecovery.sort.RecoverOrDeleteManager
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.FileType
class RecoveredFragment : BaseFragment<FragmentRecoveryPhotoBinding>() {
companion object {
private const val ARG_FILE_TYPE = "arg_file_type"
fun newInstance(fileType: Int): RecoveredFragment {
val fragment = RecoveredFragment()
val args = Bundle().apply {
putInt(ARG_FILE_TYPE, fileType)
}
fragment.arguments = args
return fragment
}
}
private var fileType: FileType? = null
private var mAdapter: SortDisplayChildAdapter? = null
private var selectedList: Set<ResultDataFiles>? = null
private val sharedViewModel: RecoveryPhotoViewModel by activityViewModels()
private lateinit var recoveredList: List<ResultDataFiles>
override fun initBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentRecoveryPhotoBinding =
FragmentRecoveryPhotoBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val i = arguments?.getInt(ARG_FILE_TYPE) ?: FileType.PHOTO.value
fileType = FileType.from(i)
binding.run {
fileType?.let {
when (it) {
FileType.PHOTO -> {
layoutEmpty.tvScan.text = getString(R.string.scan_photos)
sharedViewModel.photoFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, FileType.PHOTO)
}
sharedViewModel.selectedPhoto.observe(viewLifecycleOwner) { displaySet ->
selectedList = displaySet
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
selectedList?.let {
updateSelectStatus(it.size)
}
}
}
FileType.VIDEO -> {
layoutEmpty.tvScan.text = getString(R.string.scan_videos)
sharedViewModel.videoFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, FileType.VIDEO )
}
sharedViewModel.selectedVideo.observe(viewLifecycleOwner) { displaySet ->
selectedList = displaySet
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
selectedList?.let {
updateSelectStatus(it.size)
}
}
}
FileType.AUDIO -> {
layoutEmpty.tvScan.text = getString(R.string.scan_audios)
sharedViewModel.audioFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, FileType.AUDIO )
}
sharedViewModel.selectedAudio.observe(viewLifecycleOwner) { displaySet ->
selectedList = displaySet
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
selectedList?.let {
updateSelectStatus(it.size)
}
}
}
FileType.DOCUMENT -> {
layoutEmpty.tvScan.text = getString(R.string.scan_documents)
sharedViewModel.documentsFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, FileType.DOCUMENT)
}
sharedViewModel.selectedDocuments.observe(viewLifecycleOwner) { displaySet ->
selectedList = displaySet
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
selectedList?.let {
updateSelectStatus(it.size)
}
}
}
}
}
}
updateSelectStatus(0)
initClick()
}
private fun initViewVisible(list: List<ResultDataFiles>) {
recoveredList = list
binding.run {
recyclerView.isVisible = list.isNotEmpty()
layoutEmpty.relativeEmptyMain.isVisible = !list.isNotEmpty()
layoutBottom.run {
bottomMainLayout.isVisible = list.isNotEmpty()
// tvRight.text = getString(R.string.share)
}
tvAllSelect.isVisible = list.isNotEmpty()
}
}
private fun updateSelectStatus(selectedCounts: Int) {
binding.run {
selectedCounts.let {
tvAllSelect.isSelected = it == mAdapter?.itemCount
if (it <= 0) {
layoutBottom.tvLeft.isEnabled = false
layoutBottom.tvRight.isEnabled = false
} else {
layoutBottom.tvLeft.isEnabled = true
layoutBottom.tvRight.isEnabled = true
}
layoutBottom.tvLeft.text = getString(R.string.delete_placeholder, it)
layoutBottom.tvRight.text = getString(R.string.share_placeholder, it)
}
}
}
private fun setRecoveredData(list: List<ResultDataFiles>, fileType: FileType) {
mAdapter = SortDisplayChildAdapter(
mContext = requireContext(),
fileType = fileType,
mColumns = 2,
viewModel = sharedViewModel,
onSelectedUpdate = { resultPhotosFiles, isAdd, allSelected ->
fileType?.let { sharedViewModel.toggleSelection(it, isAdd, resultPhotosFiles) }
},
hideThumbnailsUpdate = { hide ->
}) { item ->
startActivity(
Intent(
requireContext(),
DetailsActivity::class.java
).apply {
putExtra(KEY_SCAN_TYPE, fileType.value)
putExtra(DetailsActivity.KEY_CLICK_ITEM, item)
putExtra(DetailsActivity.KEY_SHOW_SHARE, true)
})
}.apply {
setData(list)
}
binding.recyclerView.run {
adapter = mAdapter
when (fileType) {
FileType.PHOTO, FileType.VIDEO -> {
layoutManager = GridLayoutManager(requireContext(), 2)
val bPx = 6.dpToPx(context)
val aPx = 16.dpToPx(context)
val bottom = 75.dpToPx(context)
setPadding(aPx, 0, bPx, bottom)
}
FileType.AUDIO , FileType.DOCUMENT -> {
layoutManager = LinearLayoutManager(requireContext())
val bPx = 6.dpToPx(context)
val aPx = 16.dpToPx(context)
val bottom = 75.dpToPx(context)
setPadding(aPx, 0, bPx, bottom)
}
}
}
}
private fun initClick() {
binding.run {
fileType?.let { fileType ->
layoutEmpty.tvScan.setOnClickListener {
startActivity(
Intent(
requireActivity(),
ScanSelectTypeActivity::class.java
).apply {
putExtra(Common.KEY_FILE_TYPE, fileType.value)
})
requireActivity().finish()
}
tvAllSelect.setOnClickListener {
it.isSelected = !it.isSelected
mAdapter?.setAllSelected(it.isSelected)
}
layoutBottom.tvLeft.setOnClickListener {
selectedList?.let {
RecoverOrDeleteManager.showConfirmDeleteDialog(
fragmentManager = requireActivity().supportFragmentManager,
scope = lifecycleScope,
selectedSetList = it
) { count ->
val removeSelectedFromSizeList =
sharedViewModel.removeSelectedFromSizeList(recoveredList, it)
ObjectBoxManager.deleteRecoveryFilesAsyncById(
lifecycleScope,
it.toList()
) {}
sharedViewModel.setRecoveredFiles(fileType, removeSelectedFromSizeList)
sharedViewModel.clearSelected(fileType)
showCustomToast(requireContext())
}
}
}
layoutBottom.tvRight.setOnClickListener {
selectedList?.let { list->
if (list.size == 1) {
list.first().targetFile().let { file ->
Common.shareSingleFile(requireActivity(), file, fileType)
}
} else if (list.isNotEmpty()) {
val toList = list.map { it.targetFile() }.toList()
Common.shareMultipleFiles(requireActivity(), toList, fileType)
}
}
}
}
}
}
fun showCustomToast(context: Context) {
val inflater = LayoutInflater.from(context)
Toast(context).apply {
duration = Toast.LENGTH_SHORT
this.view = inflater.inflate(R.layout.dialog_delete_success, null)
setGravity(Gravity.CENTER_HORIZONTAL or Gravity.TOP, 0, 150.dpToPx(context))
show()
}
}
}

View File

@ -1,192 +0,0 @@
package com.ux.video.file.filerecovery.recovery.ui.recoveryphoto
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseFragment
import com.ux.video.file.filerecovery.databinding.FragmentRecoveryPhotoBinding
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.sort.PhotoDisplayDateAdapter
import com.ux.video.file.filerecovery.sort.PhotoDisplayDateChildAdapter
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
class RecoveryPhotoFragment : BaseFragment<FragmentRecoveryPhotoBinding>() {
companion object {
private const val ARG_FILE_TYPE = "arg_file_type"
fun newInstance(fileType: Int): RecoveryPhotoFragment {
val fragment = RecoveryPhotoFragment()
val args = Bundle().apply {
putInt(ARG_FILE_TYPE, fileType)
}
fragment.arguments = args
return fragment
}
}
// 0-3 photo\video\audio\documents
private var fileType: Int = Common.FILE_TYPE_PHOTO
private lateinit var mAdapter: PhotoDisplayDateChildAdapter
private var selectedList: List<ResultDataFiles>? = null
private val sharedViewModel: RecoveryPhotoViewModel by activityViewModels()
override fun initBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentRecoveryPhotoBinding =
FragmentRecoveryPhotoBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fileType = arguments?.getInt(ARG_FILE_TYPE) ?: Common.FILE_TYPE_PHOTO
binding.run {
when (fileType) {
Common.FILE_TYPE_PHOTO -> {
layoutEmpty.tvScan.text = getString(R.string.scan_photos)
sharedViewModel.photoFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, Common.VALUE_SCAN_TYPE_photo)
}
}
Common.FILE_TYPE_VIDEO -> {
layoutEmpty.tvScan.text = getString(R.string.scan_videos)
sharedViewModel.videoFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, Common.VALUE_SCAN_TYPE_video)
}
}
Common.FILE_TYPE_AUDIO -> {
layoutEmpty.tvScan.text = getString(R.string.scan_audios)
sharedViewModel.audioFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, Common.VALUE_SCAN_TYPE_audio)
}
}
Common.FILE_TYPE_DOCUMENTS -> {
layoutEmpty.tvScan.text = getString(R.string.scan_documents)
sharedViewModel.documentsFiles.observe(viewLifecycleOwner) {
initViewVisible(it)
setRecoveredData(it, Common.VALUE_SCAN_TYPE_documents)
}
}
}
}
sharedViewModel.selectedLiveData.observe(viewLifecycleOwner) { displaySet ->
selectedList = displaySet.toList()
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
selectedList?.let {
updateSelectStatus(it.size)
}
}
initClick()
}
private fun initViewVisible(list: List<ResultDataFiles>) {
binding.run {
recyclerView.isVisible = list.isNotEmpty()
layoutEmpty.relativeEmptyMain.isVisible = !list.isNotEmpty()
layoutBottom.run {
bottomMainLayout.isVisible = list.isNotEmpty()
// tvRight.text = getString(R.string.share)
}
tvAllSelect.isVisible = list.isNotEmpty()
}
}
private fun updateSelectStatus(selectedCounts: Int) {
binding.run {
selectedCounts.let {
tvAllSelect.isSelected = it == mAdapter.itemCount
if (it <= 0) {
layoutBottom.tvLeft.isEnabled = false
layoutBottom.tvRight.isEnabled = false
} else {
layoutBottom.tvLeft.isEnabled = true
layoutBottom.tvRight.isEnabled = true
}
layoutBottom.tvLeft.text = getString(R.string.delete_placeholder, it)
layoutBottom.tvRight.text = getString(R.string.share_placeholder, it)
}
}
}
private fun setRecoveredData(list: List<ResultDataFiles>, scanTye: Int) {
mAdapter = PhotoDisplayDateChildAdapter(
mContext = requireContext(),
scanType = scanTye,
mColumns = 2,
viewModel = sharedViewModel,
onSelectedUpdate = { resultPhotosFiles, isAdd, allSelected ->
sharedViewModel.toggleSelection(isAdd, resultPhotosFiles)
},
hideThumbnailsUpdate = { hide ->
}) { item ->
}.apply {
setData(list)
}
updateSelectStatus(0)
binding.recyclerView.run {
adapter = mAdapter
when (scanTye) {
Common.VALUE_SCAN_TYPE_photo, Common.VALUE_SCAN_TYPE_video -> {
layoutManager = GridLayoutManager(requireContext(), 2)
val bPx = 6.dpToPx(context)
val aPx = 16.dpToPx(context)
val bottom = 75.dpToPx(context)
setPadding(aPx, 0, bPx, bottom)
}
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_documents -> {
layoutManager = LinearLayoutManager(requireContext())
val bPx = 6.dpToPx(context)
val aPx = 16.dpToPx(context)
val bottom = 75.dpToPx(context)
setPadding(aPx, 0, bPx, bottom)
}
}
}
}
private fun initClick() {
binding.tvAllSelect.setOnClickListener {
it.isSelected = !it.isSelected
mAdapter.setAllSelected(it.isSelected)
}
}
}

View File

@ -5,8 +5,15 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
/**
*
* RecoveryActivity RecoveredFragment 共用
*/
class RecoveryPhotoViewModel : ViewModel() {
//用于及时更新tab 显示恢复文件的数量
private val _photoFiles = MutableLiveData<List<ResultDataFiles>>(emptyList())
val photoFiles: LiveData<List<ResultDataFiles>> = _photoFiles
@ -19,24 +26,34 @@ class RecoveryPhotoViewModel : ViewModel() {
private val _documentsFiles = MutableLiveData<List<ResultDataFiles>>(emptyList())
val documentsFiles: LiveData<List<ResultDataFiles>> = _documentsFiles
private val _selectedLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedLiveData: LiveData<Set<ResultDataFiles>> = _selectedLiveData
fun setPhotosRecoveredFiles(photos:List<ResultDataFiles>){
_photoFiles.value = photos
}
fun setVideosRecoveredFiles(videos:List<ResultDataFiles>){
_videoFiles.value = videos
}
fun setAudiosRecoveredFiles(audios:List<ResultDataFiles>){
_audioFiles.value = audios
}
fun setDocumentsRecoveredFiles(documents:List<ResultDataFiles>){
_documentsFiles.value = documents
private val _selectedPhotoLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedPhoto: LiveData<Set<ResultDataFiles>> = _selectedPhotoLiveData
private val _selectedVideoLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedVideo: LiveData<Set<ResultDataFiles>> = _selectedVideoLiveData
private val _selectedAudioLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedAudio: LiveData<Set<ResultDataFiles>> = _selectedAudioLiveData
private val _selectedDocumentsLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedDocuments: LiveData<Set<ResultDataFiles>> = _selectedDocumentsLiveData
fun setRecoveredFiles(fileType: FileType, data: List<ResultDataFiles>) {
when (fileType) {
FileType.PHOTO -> _photoFiles.value = data
FileType.VIDEO -> _videoFiles.value = data
FileType.AUDIO -> _audioFiles.value = data
FileType.DOCUMENT -> _documentsFiles.value = data
}
fun toggleSelection(isAdd: Boolean, resultDataFiles: ResultDataFiles) {
val current = _selectedLiveData.value?.toMutableSet() ?: mutableSetOf()
}
fun toggleSelection(fileType: FileType, isAdd: Boolean, resultDataFiles: ResultDataFiles) {
getSelectedList(fileType).let { selectList ->
val current = selectList.value?.toMutableSet() ?: mutableSetOf()
resultDataFiles.let {
if (isAdd) {
current.add(it)
@ -44,11 +61,46 @@ class RecoveryPhotoViewModel : ViewModel() {
current.remove(it)
}
}
_selectedLiveData.value = current.toSet()
selectList.value = current.toSet()
}
fun checkIsSelect(resultDataFiles: ResultDataFiles): Boolean {
val current = _selectedLiveData.value
return current?.contains(resultDataFiles) == true
}
fun clearSelected(fileType: FileType) {
getSelectedList(fileType).let {
it.value = it.value?.toMutableSet()?.apply {
clear()
} ?: emptySet()
}
}
fun removeSelectedFromSizeList(
list: List<ResultDataFiles>,
selectedLiveData: Set<ResultDataFiles>
): List<ResultDataFiles> {
return list.filterNot { it in selectedLiveData }
}
fun checkIsSelect(fileType: FileType, resultDataFiles: ResultDataFiles): Boolean {
return when (fileType) {
FileType.PHOTO -> _selectedPhotoLiveData
FileType.VIDEO-> _selectedVideoLiveData
FileType.AUDIO -> _selectedAudioLiveData
FileType.DOCUMENT -> _selectedDocumentsLiveData
}.let {
val current = it.value
current?.contains(resultDataFiles) == true
}
}
private fun getSelectedList(fileType: FileType): MutableLiveData<Set<ResultDataFiles>> {
return when (fileType) {
FileType.PHOTO -> _selectedPhotoLiveData
FileType.VIDEO -> _selectedVideoLiveData
FileType.AUDIO -> _selectedAudioLiveData
FileType.DOCUMENT -> _selectedDocumentsLiveData
}
}
}

View File

@ -3,48 +3,37 @@ package com.ux.video.file.filerecovery.result
import android.content.Intent
import android.view.LayoutInflater
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityScanResultDisplayBinding
import com.ux.video.file.filerecovery.sort.PhotoSortingActivity
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat
import com.ux.video.file.filerecovery.sort.SortingActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanType
/**
* 扫描结果汇总展示
*/
class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>() {
private var scanType: Int = VALUE_SCAN_TYPE_photo
private lateinit var scanType: ScanType
private var exitDialog: ExitDialogFragment? = null
private var list: ArrayList<ResultData>? = null
private lateinit var list: List<ResultData>
private val viewModel: ScanningResultDisplayViewModel by viewModels()
companion object {
val KEY_SCAN_RESULT = "scan_result"
}
override fun inflateBinding(inflater: LayoutInflater): ActivityScanResultDisplayBinding =
ActivityScanResultDisplayBinding.inflate(inflater)
override fun initView() {
super.initView()
list = intent.getParcelableArrayListExtraCompat(KEY_SCAN_RESULT)
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
setSelectTypeTitle(scanType)
val intExtra = intent.getIntExtra(Common.KEY_SCAN_TYPE, ScanType.ALL_PHOTO.value)
scanType = ScanType.from(intExtra)!!
setSelectTypeTitle(scanType.mediaType)
}
override fun initData() {
@ -59,12 +48,12 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
})
binding.run {
val myAdapter = when (scanType) {
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
val myAdapter = when (scanType.mediaType) {
FileType.AUDIO, FileType.DOCUMENT -> {
bottomLayout.setBackgroundResource(R.drawable.bg_rectangle_white_top_20)
ScanResultDocumentsAdapter(
ScanResultDocumentsAudioAdapter(
this@ScanResultDisplayActivity,
scanType
scanType.mediaType
) { folderLists ->
goSort(folderLists)
}
@ -72,60 +61,56 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
else -> {
bottomLayout.setBackgroundResource(0)
ScanResultPhotoAdapter(
ScanResultPhotoVideoAdapter(
this@ScanResultDisplayActivity,
scanType
scanType.mediaType
) { folderLists ->
goSort(folderLists)
}
}
}.apply {
list?.let {
textDirCount.text = it.size.toString()
val sumOf = it.sumOf { it.allFiles.size }
}
viewModel.scanData.observe(this@ScanResultDisplayActivity){ data->
list = data
list.let { data ->
textDirCount.text = data.size.toString()
val sumOf = data.sumOf { it.allFiles.size }
textAllCounts.text = sumOf.toString()
setData(it)
myAdapter.setData(data)
}
}
recyclerResult.run {
adapter = myAdapter
layoutManager = LinearLayoutManager(this@ScanResultDisplayActivity)
}
}
}
private fun dealExit() {
exitDialog = exitDialog ?: ExitDialogFragment() {
exitDialog = exitDialog ?: ExitDialogFragment {
finish()
}
exitDialog?.show(supportFragmentManager, "")
}
private fun setSelectTypeTitle(fileType: Int) {
private fun setSelectTypeTitle(fileType: FileType) {
binding.run {
when (fileType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
FileType.PHOTO -> {
title.text = getString(R.string.photo_title)
textFileType.text = getString(R.string.text_photos)
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
FileType.VIDEO-> {
title.text = getString(R.string.video_title)
textFileType.text = getString(R.string.text_videos)
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
FileType.AUDIO -> {
title.text = getString(R.string.audio_title)
textFileType.text = getString(R.string.text_audios)
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
FileType.DOCUMENT -> {
title.text = getString(R.string.document_title)
textFileType.text = getString(R.string.text_documents)
}
@ -137,16 +122,13 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
private fun goSort(list: ArrayList<ResultDataFiles>) {
viewModel.updateSortData(list)
startActivity(
Intent(
this@ScanResultDisplayActivity,
PhotoSortingActivity::class.java
SortingActivity::class.java
).apply {
putExtra(KEY_SCAN_TYPE, scanType)
putParcelableArrayListExtra(
PhotoSortingActivity.KEY_PHOTO_FOLDER_FILE,
list
)
putExtra(Common.KEY_FILE_TYPE, scanType.mediaType.value)
})
}
}

View File

@ -10,13 +10,14 @@ import com.ux.video.file.filerecovery.databinding.ScanResultDocumentsAdapterBind
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
/**
* 文件或者音频的扫描结果汇总适配器
*/
class ScanResultDocumentsAdapter(
class ScanResultDocumentsAudioAdapter(
mContext: Context,
var type: Int,
var fileType: FileType,
var onClickItem: (allFiles: ArrayList<ResultDataFiles>) -> Unit
) :
BaseAdapter<ResultData, ScanResultDocumentsAdapterBinding>(mContext) {
@ -38,13 +39,15 @@ class ScanResultDocumentsAdapter(
relativeLayout.setOnClickListener { onClickItem(allFiles) }
textDirName.text = dirName
textFileCounts.text = allFiles.size.toString()
when(type){
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio->{
when(fileType){
FileType.AUDIO ->{
icon.setImageResource(R.drawable.icon_folder_audio)
}
Common.VALUE_SCAN_TYPE_documents, Common.VALUE_SCAN_TYPE_deleted_documents->{
FileType.DOCUMENT->{
icon.setImageResource(R.drawable.icon_folder_documents)
}
else -> Unit
}
}

View File

@ -2,6 +2,7 @@ package com.ux.video.file.filerecovery.result
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
@ -9,16 +10,23 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.databinding.ScanResultAdapterBinding
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.targetFile
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanManager
import java.io.File
class ScanResultPhotoAdapter(
/**
* 图片或者视频的扫描结果汇总适配器
*/
class ScanResultPhotoVideoAdapter(
mContext: Context,
var type: Int,
var fileType: FileType,
var onClickItem: (allFiles: ArrayList<ResultDataFiles>) -> Unit
) :
BaseAdapter<ResultData, ScanResultAdapterBinding>(mContext) {
@ -45,7 +53,7 @@ class ScanResultPhotoAdapter(
val takeFiles = allFiles.take(3)
imageViews.forEachIndexed { index, imageView ->
if (index < takeFiles.size) {
takeFiles[index].targetFile?.let {
takeFiles[index].targetFile().let {
loadImageView(mContext, it, imageView)
}
} else {
@ -61,12 +69,17 @@ class ScanResultPhotoAdapter(
}
private fun loadImageView(context: Context, file: File, imageView: ImageView) {
ScanManager.showLog(
"加载图片",
"-----loadImageView--path = ${file.path}"
)
Glide.with(context)
.load(file)
.apply(
RequestOptions()
.transform(CenterCrop(), RoundedCorners(8.dpToPx(context)))
)
.error(R.drawable.photo_place_holder)
.into(imageView)
}
}

View File

@ -0,0 +1,24 @@
package com.ux.video.file.filerecovery.result
import androidx.lifecycle.MutableLiveData
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
object ScanResultRepository {
//扫描数据
val scanData = MutableLiveData<List<ResultData>>()
//扫描用于排序显示的数据
val sortingData = MutableLiveData<List<ResultDataFiles>>()
fun setScanResult(data: List<ResultData>) {
scanData.postValue(data)
}
fun setSortResult(data: List<ResultDataFiles>) {
sortingData.postValue(data)
}
}

View File

@ -4,103 +4,83 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.os.Environment
import android.view.LayoutInflater
import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityScanningBinding
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_AUDIO
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_DOCUMENT
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_PHOTO
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_VIDEO
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanRepository
import com.ux.video.file.filerecovery.utils.ScanState
import com.ux.video.file.filerecovery.utils.ScanType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import androidx.lifecycle.repeatOnLifecycle
class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
private var scanType: Int = VALUE_SCAN_TYPE_photo
private val viewModel: ScanningActivityViewModel by viewModels()
private lateinit var scanType: ScanType
override fun inflateBinding(inflater: LayoutInflater): ActivityScanningBinding =
ActivityScanningBinding.inflate(inflater)
override fun initData() {
super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
val typeValue = intent.getIntExtra(Common.KEY_SCAN_TYPE, ScanType.ALL_PHOTO.value)
scanType = ScanType.from(typeValue)!!
setSelectType(scanType)
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_documents -> scanAll()
VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_deleted_video, VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_deleted_documents -> scanDeleted()
if (scanType.isDeletedScan) {
scanDeleted()
} else {
scanAll()
}
binding.imageViewBack.setOnClickListener { finish() }
}
private fun setSelectType(fileType: Int) {
private fun setSelectType(scanType: ScanType) {
binding.run {
when (fileType) {
VALUE_SCAN_TYPE_photo -> {
when (scanType.mediaType) {
FileType.PHOTO->{
title.text = getString(R.string.photo_title)
tvScanDescribe.text = getString(R.string.describe_photos)
scanProgress.setCenterImage(R.drawable.im_photo_center_image)
}
VALUE_SCAN_TYPE_deleted_photo -> {
title.text = getString(R.string.photo_title)
if(scanType.isDeletedScan){
tvScanDescribe.text = getString(R.string.describe_delete_photos)
scanProgress.setCenterImage(R.drawable.im_photo_center_image)
}else{
tvScanDescribe.text = getString(R.string.describe_photos)
}
VALUE_SCAN_TYPE_video -> {
}
FileType.VIDEO->{
title.text = getString(R.string.video_title)
tvScanDescribe.text = getString(R.string.describe_videos)
scanProgress.setCenterImage(R.drawable.im_video_center_image)
}
VALUE_SCAN_TYPE_deleted_video -> {
title.text = getString(R.string.video_title)
if(scanType.isDeletedScan){
tvScanDescribe.text = getString(R.string.describe_delete_videos)
scanProgress.setCenterImage(R.drawable.im_video_center_image)
}else{
tvScanDescribe.text = getString(R.string.describe_videos)
}
}
VALUE_SCAN_TYPE_audio -> {
FileType.AUDIO->{
title.text = getString(R.string.audio_title)
tvScanDescribe.text = getString(R.string.describe_audios)
scanProgress.setCenterImage(R.drawable.im_audio_center_image)
}
VALUE_SCAN_TYPE_deleted_audio -> {
title.text = getString(R.string.audio_title)
if(scanType.isDeletedScan){
tvScanDescribe.text = getString(R.string.describe_delete_audios)
scanProgress.setCenterImage(R.drawable.im_audio_center_image)
}else{
tvScanDescribe.text = getString(R.string.describe_audios)
}
VALUE_SCAN_TYPE_documents -> {
}
FileType.DOCUMENT->{
title.text = getString(R.string.document_title)
tvScanDescribe.text = getString(R.string.describe_documents)
scanProgress.setCenterImage(R.drawable.im_documents_center_image)
}
VALUE_SCAN_TYPE_deleted_documents -> {
title.text = getString(R.string.document_title)
if(scanType.isDeletedScan){
tvScanDescribe.text = getString(R.string.describe_delete_documents)
scanProgress.setCenterImage(R.drawable.im_documents_center_image)
}else{
tvScanDescribe.text = getString(R.string.describe_documents)
}
}
}
}
@ -113,7 +93,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
val root = Environment.getExternalStorageDirectory()
ScanManager.scanAllDocuments(this@ScanningActivity, root, type = scanType)
ScanManager.scanAllDocuments(this@ScanningActivity, root, fileType = scanType.mediaType)
.flowOn(Dispatchers.IO).collect {
when (it) {
is ScanState.Progress -> {
@ -126,8 +106,6 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
}
}
}
}
}
@ -135,7 +113,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
val root = Environment.getExternalStorageDirectory()
ScanManager.scanHiddenPhotoAsync(this@ScanningActivity, root, type = scanType)
ScanManager.scanHiddenPhotoAsync(this@ScanningActivity, root, fileType = scanType.mediaType)
.flowOn(Dispatchers.IO).collect {
when (it) {
is ScanState.Progress -> {
@ -188,12 +166,13 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
linearCounts.isVisible = false
} else {
finish()
startActivity(Intent(this@ScanningActivity, ScanResultDisplayActivity::class.java).apply {
putParcelableArrayListExtra(
ScanResultDisplayActivity.KEY_SCAN_RESULT,
it.result
)
putExtra(KEY_SCAN_TYPE, scanType)
viewModel.updateData(it.result)
startActivity(
Intent(
this@ScanningActivity,
ScanResultDisplayActivity::class.java
).apply {
putExtra(Common.KEY_SCAN_TYPE, scanType.value)
})
}
ScanManager.showLog("HiddenScan", "完成: ${it.result.size}")

View File

@ -0,0 +1,16 @@
package com.ux.video.file.filerecovery.result
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ux.video.file.filerecovery.db.ResultData
class ScanningActivityViewModel : ViewModel() {
val scanData: MutableLiveData<List<ResultData>> = ScanResultRepository.scanData
fun updateData(data: List<ResultData>) {
ScanResultRepository.setScanResult(data)
}
}

View File

@ -0,0 +1,19 @@
package com.ux.video.file.filerecovery.result
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
class ScanningResultDisplayViewModel : ViewModel() {
val scanData: MutableLiveData<List<ResultData>> = ScanResultRepository.scanData
fun updateData(data: List<ResultData>) {
ScanResultRepository.setScanResult(data)
}
fun updateSortData(data: List<ResultDataFiles>) {
ScanResultRepository.setSortResult(data)
}
}

View File

@ -0,0 +1,29 @@
package com.ux.video.file.filerecovery.settings
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityPrivacyPolicyBinding
import androidx.core.net.toUri
class PrivacyPolicyActivity : BaseActivity<ActivityPrivacyPolicyBinding>() {
override fun inflateBinding(inflater: LayoutInflater): ActivityPrivacyPolicyBinding =
ActivityPrivacyPolicyBinding.inflate(layoutInflater)
override fun initView() {
super.initView()
}
}

View File

@ -0,0 +1,39 @@
package com.ux.video.file.filerecovery.settings
import android.content.Context
import android.view.LayoutInflater
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.net.toUri
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivitySetUpBinding
import com.ux.video.file.filerecovery.utils.Common
class SetupActivity : BaseActivity<ActivitySetUpBinding>() {
override fun inflateBinding(inflater: LayoutInflater): ActivitySetUpBinding = ActivitySetUpBinding.inflate(layoutInflater)
override fun initView() {
super.initView()
}
override fun initData() {
super.initData()
binding.run {
imageViewBack.setOnClickListener { finish() }
layoutShareApp.setOnClickListener {
Common.shareApp(this@SetupActivity)
}
layoutPrivacyPolicy.setOnClickListener {
openWebPage(this@SetupActivity,getString(R.string.privacy_policy_url ))
}
}
}
fun openWebPage(context: Context, url: String) {
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
customTabsIntent.launchUrl(context, url.toUri())
}
}

View File

@ -1,252 +0,0 @@
package com.ux.video.file.filerecovery.sort
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityPhotoInfoBinding
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.video.PlayMediaManager
import com.ux.video.file.filerecovery.video.VideoPlayActivity
import java.io.File
class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
companion object {
val KEY_CLICK_ITEM = "click_item"
}
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var myData: ResultDataFiles? = null
override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoInfoBinding =
ActivityPhotoInfoBinding.inflate(inflater)
override fun initView() {
super.initView()
myData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(KEY_CLICK_ITEM, ResultDataFiles::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(KEY_CLICK_ITEM)
}
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
setView()
}
override fun initData() {
super.initData()
binding.run {
imageViewBack.setOnClickListener { finish() }
myData?.let { resultPhotosFiles ->
tvName.text = resultPhotosFiles.name
tvPath.text = resultPhotosFiles.path
tvSize.text = resultPhotosFiles.sizeString
tvDate.text = Common.getFormatDate(resultPhotosFiles.lastModified)
tvResolution.text = resultPhotosFiles.resolution
tvDuration.text = Common.formatDuration(resultPhotosFiles.duration)
resultPhotosFiles.targetFile?.let {
tvType.text = Common.getMimeTypeParts(it)
}
layoutBottom.tvLeft.run {
text = resources.getString(R.string.delete)
setOnClickListener {
RecoverOrDeleteManager.showConfirmDeleteDialog(
true,
supportFragmentManager,
lifecycleScope,
setOf(resultPhotosFiles)
) { count ->
complete(count, 1)
}
}
}
layoutBottom.tvRight.run {
text = resources.getString(R.string.recover)
setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager,
lifecycleScope,
setOf(resultPhotosFiles)
) { count ->
complete(count, 0)
}
}
}
}
}
}
private fun setView() {
binding.run {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
layoutName.isVisible = true
layoutPath.isVisible = true
layoutResolution.isVisible = true
layoutDate.isVisible = true
frameImage.setBackgroundResource(0)
layoutSeekbar.isVisible = false
layoutType.isVisible = false
layoutSize.isVisible = false
layoutDuration.isVisible = false
imPlay.isVisible = false
myData?.targetFile?.let { loadImage(image,it) }
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
layoutName.isVisible = true
layoutPath.isVisible = true
layoutResolution.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = true
frameImage.setBackgroundResource(0)
layoutSeekbar.isVisible = false
layoutType.isVisible = false
layoutSize.isVisible = false
imPlay.isVisible = true
myData?.let { data->
data.targetFile?.let { loadImage(image,it) }
frameImage.setOnClickListener {
startActivity(Intent(this@PhotoInfoActivity, VideoPlayActivity::class.java).apply {
putExtra(VideoPlayActivity.KEY_DATA, data)
})
}
}
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
Common.showLog("----------音频")
layoutName.isVisible = true
layoutPath.isVisible = true
layoutSize.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = true
layoutSeekbar.isVisible = true
imPlay.isVisible = true
frameImage.setBackgroundResource(R.drawable.bg_info_music_f2f2f7_8)
loadCenterImage(image,R.drawable.image_info_music)
initPlayAudio()
layoutResolution.isVisible = false
layoutType.isVisible = false
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
layoutName.isVisible = true
layoutType.isVisible = true
layoutPath.isVisible = true
layoutSize.isVisible = true
layoutDate.isVisible = true
frameImage.setBackgroundResource(R.drawable.bg_info_music_f2f2f7_8)
myData?.targetFile?.let {
loadCenterImage(image, Common.getFileIconRes(it))
}
imPlay.isVisible = false
layoutSeekbar.isVisible = false
layoutResolution.isVisible = false
layoutDuration.isVisible = false
}
}
}
}
private fun loadCenterImage(image: ImageView,drawableId: Int){
image.setImageResource(drawableId)
val params = image.layoutParams ?: ViewGroup.LayoutParams(
180.dpToPx(this@PhotoInfoActivity),
180.dpToPx(this@PhotoInfoActivity)
)
params.width = 180.dpToPx(this@PhotoInfoActivity)
params.height = 180.dpToPx(this@PhotoInfoActivity)
image.layoutParams = params
}
private fun loadImage(image: ImageView,file: File){
Glide.with(this@PhotoInfoActivity)
.load(file)
.apply(RequestOptions().transform(CenterCrop(), RoundedCorners(8.dpToPx(this@PhotoInfoActivity))))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable?>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(image)
}
private fun initPlayAudio(){
myData?.targetFile?.let {
binding.run {
PlayMediaManager(context = this@PhotoInfoActivity, mediaFile = it,
seekBar = seekBar, playBtn = imPlay, onUpdateProgress = { current,total->
textTimeCurrent.text = current
textTimeTotal.text = total
} )
}
}
}
private fun complete(number: Int, type: Int) {
finish()
startActivity(Intent(this@PhotoInfoActivity, RecoverySuccessActivity::class.java).apply {
putExtra(RecoverySuccessActivity.KEY_SUCCESS_COUNT, number)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_TYPE, type)
})
}
}

View File

@ -16,7 +16,7 @@ import com.ux.video.file.filerecovery.databinding.DialogSortBinding
class SortDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFragment() {
private lateinit var binding: DialogSortBinding
private var clickType = PhotoSortingActivity.SORT_DESC_DATE
private var clickType = SortingActivity.SORT_DESC_DATE
private lateinit var LayoutList: List<CommonLayoutSortItemBinding>
override fun onStart() {

View File

@ -10,22 +10,22 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.ScanRepository
import com.ux.video.file.filerecovery.db.isThumbnail
class PhotoDisplayDateAdapter(
import com.ux.video.file.filerecovery.utils.FileType
/**
* 排序页面显示
*/
class SortDisplayAdapter(
mContext: Context,
var scanType: Int,
var fileType: FileType,
var mColumns: Int,
var viewModel: ScanRepository,
var viewModel: SortingViewModel,
var onSelectedUpdate: (resultDataFiles: ResultDataFiles, isAdd: Boolean) -> Unit,
var clickItem: (item: ResultDataFiles) -> Unit
) :
BaseAdapter<Pair<String, List<ResultDataFiles>>, PhotoDisplayDateAdapterBinding>(mContext) {
private var allSelected: Boolean? = null
override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateAdapterBinding =
@ -41,7 +41,7 @@ class PhotoDisplayDateAdapter(
*/
fun getTotalChildCount(hideThumbnails: Boolean): Int {
if (hideThumbnails) {
return data.sumOf { it.second.filter { !it.isThumbnail }.size }
return data.sumOf { it.second.filter { !it.isThumbnail() }.size }
} else {
return data.sumOf { it.second.size }
}
@ -80,9 +80,9 @@ class PhotoDisplayDateAdapter(
holder.vb.run {
item.run {
val (date, files) = item
val childAdapter = PhotoDisplayDateChildAdapter(
val childAdapter = SortDisplayChildAdapter(
mContext,
scanType,
fileType,
mColumns,
viewModel,
{ resultPhotosFiles, addOrRemove, isDateAllSelected ->
@ -108,8 +108,8 @@ class PhotoDisplayDateAdapter(
textChildCounts.text = "(${files.size})"
recyclerChild.apply {
layoutManager = when (scanType) {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
layoutManager = when (fileType) {
FileType.AUDIO, FileType.DOCUMENT -> {
LinearLayoutManager(context)
}

View File

@ -25,16 +25,22 @@ import com.ux.video.file.filerecovery.databinding.FileSpanCountTwoAdapterBinding
import com.ux.video.file.filerecovery.databinding.OneAudioItemBinding
import com.ux.video.file.filerecovery.databinding.OneDocumentsItemBinding
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.duration
import com.ux.video.file.filerecovery.db.targetFile
import com.ux.video.file.filerecovery.recovery.ui.recoveryphoto.RecoveryPhotoViewModel
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.CustomTextView
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanRepository
class PhotoDisplayDateChildAdapter(
/**
* 排序页面显示 已经恢复的文件页面显示
*/
class SortDisplayChildAdapter(
mContext: Context,
var scanType: Int,
var fileType: FileType,
var mColumns: Int,
var viewModel: ViewModel? = null,
/**
@ -72,12 +78,12 @@ class PhotoDisplayDateChildAdapter(
}
override fun getItemViewType(position: Int): Int {
when (scanType) {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio -> {
when (fileType) {
FileType.AUDIO -> {
return TYPE_AUDIO
}
Common.VALUE_SCAN_TYPE_documents, Common.VALUE_SCAN_TYPE_deleted_documents -> {
FileType.DOCUMENT -> {
return TYPE_DOCUMENTS
}
@ -172,20 +178,10 @@ class PhotoDisplayDateChildAdapter(
item.run {
holder.vb.let {
it.textName.text = name
it.textDuration.text = Common.formatDuration(duration)
it.textDuration.text = Common.formatDuration(duration())
it.textSize.text = sizeString
initAudioDocuments(it.imageSelect, this)
// viewModel.checkIsSelect(this).let { isSelected ->
// it.imageSelect.isSelected = isSelected
// addOrRemove(this, isSelected)
// }
// it.imageSelect.setOnClickListener {
// it.isSelected = !it.isSelected
// it.isSelected.let { newStatus ->
// addOrRemove(this, newStatus)
// }
// }
it.constraintLayout.setOnClickListener {
clickItem(this)
}
@ -200,7 +196,7 @@ class PhotoDisplayDateChildAdapter(
it.textName.text = name
it.textDate.text = Common.getItemMonthDay(lastModified)
it.textSize.text = sizeString
targetFile?.let { file ->
targetFile().let { file ->
it.imageIcon.setImageResource(Common.getFileIconRes(file))
}
@ -235,11 +231,11 @@ class PhotoDisplayDateChildAdapter(
private fun initAudioDocuments(imageSelect: ImageView, item: ResultDataFiles) {
viewModel?.let {
if (it is ScanRepository) {
if (it is SortingViewModel) {
it.checkIsSelect(item)
} else {
it as RecoveryPhotoViewModel
it.checkIsSelect(item)
it.checkIsSelect(fileType, item)
}.let { isSelected ->
imageSelect.isSelected = isSelected == true
addOrRemove(item, isSelected == true)
@ -265,31 +261,21 @@ class PhotoDisplayDateChildAdapter(
) {
item.run {
initAudioDocuments(imageSelectStatus, this)
// viewModel.checkIsSelect(this).let {
// imageSelectStatus.isSelected = it
// addOrRemove(this, it)
// }
// imageSelectStatus.setOnClickListener {
// it.isSelected = !it.isSelected
// it.isSelected.let { newStatus ->
// addOrRemove(this, newStatus)
// }
// }
textSize.text = sizeString
imageType.setImageResource(
when (scanType) {
Common.VALUE_SCAN_TYPE_photo, Common.VALUE_SCAN_TYPE_deleted_photo -> R.drawable.icon_type_photo
Common.VALUE_SCAN_TYPE_video, Common.VALUE_SCAN_TYPE_deleted_video -> R.drawable.icon_type_video
when (fileType) {
FileType.PHOTO -> R.drawable.icon_type_photo
FileType.VIDEO -> R.drawable.icon_type_video
else -> R.drawable.icon_type_photo
}
)
Glide.with(mContext)
.load(targetFile)
.load(targetFile())
.apply(
RequestOptions()
.transform(CenterCrop(), RoundedCorners(8.dpToPx(mContext)))
)
.error(R.drawable.photo_place_holder)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
@ -299,7 +285,7 @@ class PhotoDisplayDateChildAdapter(
): Boolean {
ScanManager.showLog(
"加载图片",
"-------path = ${path} file=${targetFile}"
"-------path = ${path} e=${e?.message}"
)
return false
}

View File

@ -6,8 +6,8 @@ import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -15,17 +15,10 @@ import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.isThumbnail
import com.ux.video.file.filerecovery.detail.DetailsActivity
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import com.ux.video.file.filerecovery.utils.Common.setItemSelect
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterByDuration
@ -36,19 +29,19 @@ import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterRemoveThumbnai
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterThumbnailsAsync
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRange
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRangeList
import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat
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.minutesToMillisecond
import com.ux.video.file.filerecovery.utils.ExtendFunctions.removeItem
import com.ux.video.file.filerecovery.utils.FileType
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
import com.ux.video.file.filerecovery.utils.ScanRepository
import com.ux.video.file.filerecovery.utils.ScanType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Date
class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
class SortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
companion object {
//指定文件夹下的所有文件
@ -67,14 +60,14 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
val SORT_DESC_DATE = 3
}
private var scanType: Int = VALUE_SCAN_TYPE_photo
private lateinit var fileType: FileType
private var sortDialogFragment: SortDialogFragment? = null
private var columns = 3
private var dateAdapter: PhotoDisplayDateAdapter? = null
private var dateAdapter: SortDisplayAdapter? = null
//文件大小排序使用的适配器
private var sizeSortAdapter: PhotoDisplayDateChildAdapter? = null
private var sizeSortAdapter: SortDisplayChildAdapter? = null
private var dialogCustomerDateStart: DatePickerDialogFragment? = null
@ -118,20 +111,86 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private lateinit var sizeFilterItemArray: Array<String>
private val viewModel: SortingViewModel by viewModels()
private lateinit var list: List<ResultDataFiles>
// private lateinit var viewModel: SortingViewModel
override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoSortingBinding {
return ActivityPhotoSortingBinding.inflate(inflater)
}
private lateinit var viewModel: ScanRepository
override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoSortingBinding =
ActivityPhotoSortingBinding.inflate(inflater)
override fun initData() {
super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
val list: ArrayList<ResultDataFiles>? =
intent.getParcelableArrayListExtraCompat(KEY_PHOTO_FOLDER_FILE)
val intExtra = intent.getIntExtra(Common.KEY_FILE_TYPE, FileType.PHOTO.value)
fileType = FileType.from(intExtra)!!
mItemDecoration =
GridSpacingItemDecoration(columns, Common.itemSpacing, Common.horizontalSpacing)
updateButtonCounts(0)
viewModel = ViewModelProvider(this)[ScanRepository::class.java]
viewModel.sortingData.observe(this) { sortData ->
list = sortData
list.let {
binding.tvThumbnailCounts.text =
getString(R.string.hide_thumbnails, it.filter { it.isThumbnail() }.size)
//降序(最近的在前面)
sortByDateReverse = Common.getSortByDayNewToOldInit(it)
//升序(时间最远的在前面)
sortedByDatePositive = Common.getSortByDayOldToNew(sortByDateReverse)
sortBySizeBigToSmall = Common.getSortBySizeBigToSmall(it)
sortBySizeSmallToBig = Common.getSortBySizeSmallToBig(it)
sizeSortAdapter = SortDisplayChildAdapter(
this@SortingActivity,
fileType,
columns, viewModel,
{ resultPhotosFiles, isAdd, allSelected ->
viewModel.toggleSelection(isAdd, resultPhotosFiles)
}, { hide ->
}) { item ->
startActivity(
Intent(
this@SortingActivity,
DetailsActivity::class.java
).apply {
putExtra(Common.KEY_FILE_TYPE, item.fileType.value)
putExtra(DetailsActivity.KEY_CLICK_ITEM, item)
})
}
dateAdapter =
SortDisplayAdapter(
this@SortingActivity,
fileType,
columns,
viewModel,
{ actionPath, isAdd ->
viewModel.toggleSelection(isAdd, actionPath)
}) { item ->
startActivity(
Intent(
this@SortingActivity,
DetailsActivity::class.java
).apply {
putExtra(Common.KEY_FILE_TYPE, fileType.value)
putExtra(DetailsActivity.KEY_CLICK_ITEM, item)
})
}.apply {
setData(sortByDateReverse)
resetCurrentDateList(sortByDateReverse)
}
setDateAdapter()
setSingleDelete()
setFilter()
setAllClick()
}
}
viewModel.selectedLiveData.observe(this) { selectedSet ->
allSelectedSetList = selectedSet
@ -144,64 +203,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
updateCurrentIsAllSelectStatus()
}
setScanTypeView()
list?.let {
binding.tvThumbnailCounts.text =
getString(R.string.hide_thumbnails, it.filter { it.isThumbnail }.size)
//降序(最近的在前面)
sortByDateReverse = Common.getSortByDayNewToOldInit(it)
//升序(时间最远的在前面)
sortedByDatePositive = Common.getSortByDayOldToNew(sortByDateReverse)
sortBySizeBigToSmall = Common.getSortBySizeBigToSmall(it)
sortBySizeSmallToBig = Common.getSortBySizeSmallToBig(it)
sizeSortAdapter = PhotoDisplayDateChildAdapter(
this@PhotoSortingActivity,
scanType,
columns, viewModel,
{ resultPhotosFiles, isAdd, allSelected ->
viewModel.toggleSelection(isAdd, resultPhotosFiles)
}, { hide ->
}) { item ->
startActivity(
Intent(
this@PhotoSortingActivity,
PhotoInfoActivity::class.java
).apply {
putExtra(KEY_SCAN_TYPE, scanType)
putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item)
})
}
dateAdapter =
PhotoDisplayDateAdapter(
this@PhotoSortingActivity,
scanType,
columns,
viewModel,
{ actionPath, isAdd ->
viewModel.toggleSelection(isAdd, actionPath)
}) { item ->
startActivity(
Intent(
this@PhotoSortingActivity,
PhotoInfoActivity::class.java
).apply {
putExtra(KEY_SCAN_TYPE, scanType)
putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item)
})
}.apply {
setData(sortByDateReverse)
resetCurrentDateList(sortByDateReverse)
}
setDateAdapter()
setSingleDelete()
setFilter()
setAllClick()
}
}
@ -210,7 +212,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
imageViewBack.setOnClickListener { finish() }
switchHideThumbnails.setOnCheckedChangeListener { _, isChecked ->
when (recyclerView.adapter) {
is PhotoDisplayDateAdapter -> {
is SortDisplayAdapter -> {
lifecycleScope.launch {
dateAdapter?.run {
initGetCurrentDateList().let { list ->
@ -225,7 +227,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
is PhotoDisplayDateChildAdapter -> {
is SortDisplayChildAdapter -> {
lifecycleScope.launch {
sizeSortAdapter?.run {
initGetCurrentSizeList().let {
@ -336,7 +338,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
tvSelectAll.setOnClickListener {
it.isSelected = !it.isSelected
when (binding.recyclerView.adapter) {
is PhotoDisplayDateAdapter -> {
is SortDisplayAdapter -> {
dateAdapter?.setAllSelected(it.isSelected)
dateAdapter?.getCurrentData()?.let {
it as List<Pair<String, List<ResultDataFiles>>>
@ -346,7 +348,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
is PhotoDisplayDateChildAdapter -> {
is SortDisplayChildAdapter -> {
sizeSortAdapter?.setAllSelected(it.isSelected)
sizeSortAdapter?.getCurrentData()?.let {
it as List<ResultDataFiles>
@ -367,8 +369,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
*/
private fun setScanTypeView() {
binding.run {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
fileType?.let {
when (it) {
FileType.PHOTO -> {
titleSize.text = getString(R.string.size)
filterLayoutLinearlayout.isVisible = true
relativeThumbnails.isVisible = true
@ -376,7 +379,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
sizeFilterItemArray = resources.getStringArray(R.array.filter_size_photo)
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
FileType.VIDEO -> {
titleSize.text = getString(R.string.duration)
filterLayoutLinearlayout.isVisible = true
relativeThumbnails.isVisible = false
@ -385,7 +388,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
resources.getStringArray(R.array.filter_duration_video_audio)
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
FileType.AUDIO -> {
titleSize.text = getString(R.string.duration)
filterLayoutLinearlayout.isVisible = false
relativeThumbnails.isVisible = false
@ -395,7 +398,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
initSearch()
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
FileType.DOCUMENT -> {
titleSize.text = getString(R.string.size)
filterLayoutLinearlayout.isVisible = false
relativeThumbnails.isVisible = false
@ -403,6 +406,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
sizeFilterItemArray = resources.getStringArray(R.array.filter_documents_size)
initSearch()
}
}
}
}
@ -413,8 +419,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
binding.tvSelectCounts.text = it.toString()
updateButtonCounts(it)
when (binding.recyclerView.adapter) {
is PhotoDisplayDateAdapter -> {
val adapter = binding.recyclerView.adapter as PhotoDisplayDateAdapter
is SortDisplayAdapter -> {
val adapter = binding.recyclerView.adapter as SortDisplayAdapter
if (it > 0) {
binding.tvSelectAll.isSelected = it == adapter.getTotalChildCount(false)
} else {
@ -423,8 +429,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
is PhotoDisplayDateChildAdapter -> {
val adapter = binding.recyclerView.adapter as PhotoDisplayDateChildAdapter
is SortDisplayChildAdapter -> {
val adapter = binding.recyclerView.adapter as SortDisplayChildAdapter
if (it > 0) {
binding.tvSelectAll.isSelected =
it == adapter.itemCount
@ -448,7 +454,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
currentDateList = null
binding.tvThumbnailCounts.text =
getString(R.string.hide_thumbnails, currentList.filter { it.isThumbnail }.size)
getString(R.string.hide_thumbnails, currentList.filter { it.isThumbnail() }.size)
}
@ -461,7 +467,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
currentDateList = currentList
currentSizeList = null
val totalSelectedCount = currentList.sumOf { pair ->
pair.second.filter { it.isThumbnail }.size
pair.second.filter { it.isThumbnail() }.size
}
binding.tvThumbnailCounts.text =
getString(R.string.hide_thumbnails, totalSelectedCount)
@ -474,7 +480,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private fun setDateAdapter() {
binding.recyclerView.run {
adapter = dateAdapter?.apply { setColumns(columns) }
layoutManager = LinearLayoutManager(this@PhotoSortingActivity)
layoutManager = LinearLayoutManager(this@SortingActivity)
setPadding(0, 0, 0, 70.dpToPx(context))
clipToPadding = false
}
@ -484,8 +490,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
binding.recyclerView.run {
val aPx = 16.dpToPx(context)
val bottom = 70.dpToPx(context)
when (scanType) {
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio,VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
when (fileType) {
FileType.AUDIO, FileType.DOCUMENT-> {
layoutManager = LinearLayoutManager(context)
setPadding(aPx, 0, 0, bottom)
}
@ -513,7 +519,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
setItemSelect(it as LinearLayout, true)
resources.getStringArray(R.array.filter_date).let { data ->
filterDatePopupWindows = filterDatePopupWindows ?: DateFilterPopupWindows(
this@PhotoSortingActivity,
this@SortingActivity,
0,
{ clickValue, showDialog ->
when (clickValue) {
@ -576,7 +582,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
setItemSelect(it as LinearLayout, true)
sizeFilterItemArray.let { data ->
filterSizePopupWindows = filterSizePopupWindows ?: FilterPopupWindows(
this@PhotoSortingActivity,
this@SortingActivity,
data,
0,
{ clickValue ->
@ -598,7 +604,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
setItemSelect(it as LinearLayout, true)
resources.getStringArray(R.array.filter_layout).let { data ->
filterLayoutPopupWindows = filterLayoutPopupWindows ?: FilterPopupWindows(
this@PhotoSortingActivity,
this@SortingActivity,
data,
1,
{ clickValue ->
@ -608,13 +614,13 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
data[2] -> columns = 4
}
when (binding.recyclerView.adapter) {
is PhotoDisplayDateAdapter -> {
is SortDisplayAdapter -> {
dateAdapter?.setColumns(columns)
}
is PhotoDisplayDateChildAdapter -> {
is SortDisplayChildAdapter -> {
binding.recyclerView.layoutManager =
GridLayoutManager(this@PhotoSortingActivity, columns)
GridLayoutManager(this@SortingActivity, columns)
sizeSortAdapter?.setColumns(columns)
}
}
@ -654,10 +660,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private fun startFilter() {
Common.showLog("--------------开始筛选")
val filterSizeCovert = filterSizeCovert(scanType, filterSize)
val filterSizeCovert = filterSizeCovert(fileType, filterSize)
when (binding.recyclerView.adapter) {
//当前是时间排序
is PhotoDisplayDateAdapter -> {
is SortDisplayAdapter -> {
//确定当前排序
val list = if (sortReverse) sortByDateReverse else sortedByDatePositive
list.filterWithinDateRange(
@ -665,8 +671,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null,
endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null
).run {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
when (fileType) {
FileType.PHOTO,FileType.DOCUMENT -> {
filterBySize(filterSizeCovert.first, filterSizeCovert.second)
}
@ -684,15 +690,15 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
//当前是大小排序
is PhotoDisplayDateChildAdapter -> {
is SortDisplayChildAdapter -> {
val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig
list.filterWithinDateRangeList(
filterDate,
startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null,
endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null
).run {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
when (fileType) {
FileType.PHOTO,FileType.DOCUMENT-> {
filterBySizeList(filterSizeCovert.first, filterSizeCovert.second)
}
@ -736,9 +742,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
private fun filterSizeCovert(scanType: Int, filterSize: String): Pair<Long, Long> {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
private fun filterSizeCovert(fileType: FileType, filterSize: String): Pair<Long, Long> {
when (fileType) {
FileType.PHOTO -> {
val stringArray = resources.getStringArray(R.array.filter_size_photo)
return when (filterSize) {
stringArray[0] -> Pair(-1L, -1L)
@ -749,7 +755,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video, VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
FileType.VIDEO, FileType.AUDIO -> {
val stringArray = resources.getStringArray(R.array.filter_duration_video_audio)
return when (filterSize) {
stringArray[0] -> Pair(-1L, -1L)
@ -761,7 +767,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
FileType.DOCUMENT-> {
val stringArray = resources.getStringArray(R.array.filter_documents_size)
return when (filterSize) {
stringArray[0] -> Pair(-1L, -1L)
@ -773,7 +779,6 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
}
return Pair(-1L, -1L)
}
/**
@ -846,9 +851,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
* @param type 0 恢复 1 删除
*/
private fun complete(number: Int, type: Int) {
startActivity(Intent(this@PhotoSortingActivity, RecoverySuccessActivity::class.java).apply {
startActivity(Intent(this@SortingActivity, RecoverySuccessActivity::class.java).apply {
putExtra(RecoverySuccessActivity.KEY_SUCCESS_COUNT, number)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_TYPE, type)
putExtra(Common.KEY_FILE_TYPE, fileType.value)
})
if (type == 1) {
lifecycleScope.launch {
@ -1019,11 +1025,11 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private fun checkTypeAfter(onAction: (isDateSort: Boolean) -> Unit) {
when (binding.recyclerView.adapter) {
is PhotoDisplayDateAdapter -> {
is SortDisplayAdapter -> {
onAction(true)
}
is PhotoDisplayDateChildAdapter -> {
is SortDisplayChildAdapter -> {
onAction(false)
}
}

View File

@ -1,12 +1,14 @@
package com.ux.video.file.filerecovery.utils
package com.ux.video.file.filerecovery.sort
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.result.ScanResultRepository
import com.ux.video.file.filerecovery.utils.Common
class ScanRepository : ViewModel() {
class SortingViewModel : ViewModel() {
// private val _selectedFlow = MutableStateFlow<MutableSet<String>>(mutableSetOf())
// val selectedFlow: StateFlow<MutableSet<String>> = _selectedFlow
//
@ -14,7 +16,7 @@ class ScanRepository : ViewModel() {
// private val _selectedDisplayFlow = MutableStateFlow<MutableSet<String>>(mutableSetOf())
// val selectedDisplayFlow: StateFlow<MutableSet<String>> = _selectedDisplayFlow
val sortingData: MutableLiveData<List<ResultDataFiles>> = ScanResultRepository.sortingData
private val _selectedLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedLiveData: LiveData<Set<ResultDataFiles>> = _selectedLiveData

View File

@ -1,36 +1,15 @@
package com.ux.video.file.filerecovery.success
import android.content.Intent
import android.os.Environment
import android.view.LayoutInflater
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityRecoverOrDeletedSuccessBinding
import com.ux.video.file.filerecovery.databinding.ActivityScanningBinding
import com.ux.video.file.filerecovery.main.MainActivity
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_AUDIO
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_DOCUMENT
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_PHOTO
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_VIDEO
import com.ux.video.file.filerecovery.result.ScanResultDisplayActivity
import com.ux.video.file.filerecovery.recovery.RecoveryActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanRepository
import com.ux.video.file.filerecovery.utils.ScanState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import com.ux.video.file.filerecovery.utils.FileType
class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBinding>() {
@ -39,7 +18,7 @@ class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBind
val KEY_SUCCESS_COUNT = "success_count"
}
private var scanType: Int = VALUE_SCAN_TYPE_photo
private lateinit var fileType: FileType
//0 恢复成功 1 删除成功
private var successType = 0
@ -48,7 +27,10 @@ class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBind
override fun initData() {
super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
val intExtra = intent.getIntExtra(Common.KEY_FILE_TYPE, FileType.PHOTO.value)
fileType = FileType.from(intExtra)!!
successType = intent.getIntExtra(KEY_SUCCESS_TYPE, 0)
val counts = intent.getIntExtra(KEY_SUCCESS_COUNT, 0)
@ -58,17 +40,17 @@ class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBind
tvLeft.text = resources.getString(R.string.text_continue)
tvRight.text = resources.getString(R.string.view)
}
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> tvFileType.text =
when (fileType) {
FileType.PHOTO -> tvFileType.text =
resources.getString(R.string.describe_photos)
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> tvFileType.text =
FileType.VIDEO -> tvFileType.text =
resources.getString(R.string.describe_videos)
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> tvFileType.text =
FileType.AUDIO -> tvFileType.text =
resources.getString(R.string.describe_audios)
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> tvFileType.text =
FileType.DOCUMENT -> tvFileType.text =
resources.getString(R.string.describe_documents)
}
when (successType) {
@ -86,7 +68,13 @@ class RecoverySuccessActivity : BaseActivity<ActivityRecoverOrDeletedSuccessBind
bottomBtnRecoverLayout.tvLeft.setOnClickListener { finish()}
bottomBtnRecoverLayout.tvRight.setOnClickListener {
//todo 跳转到所有恢复文件页面
finish()
Intent(this@RecoverySuccessActivity,RecoveryActivity::class.java).apply {
putExtra(Common.KEY_FILE_TYPE,fileType.value)
}.let {
startActivity(it)
}
}
}

View File

@ -2,6 +2,8 @@ package com.ux.video.file.filerecovery.utils
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.media.MediaMetadataRetriever
@ -11,34 +13,24 @@ import android.view.View
import android.view.ViewGroup
import android.webkit.MimeTypeMap
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.ScanManager.getVideoResolution
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Date
import java.util.Locale
import kotlin.collections.sortedBy
object Common {
val itemSpacing = 10
val horizontalSpacing = 16
val KEY_SCAN_TYPE = "scan_type"
val VALUE_SCAN_TYPE_photo = 0
val VALUE_SCAN_TYPE_deleted_photo = 1
val VALUE_SCAN_TYPE_video = 2
val VALUE_SCAN_TYPE_deleted_video = 3
val VALUE_SCAN_TYPE_audio = 4
val VALUE_SCAN_TYPE_deleted_audio = 5
val VALUE_SCAN_TYPE_documents = 6
val VALUE_SCAN_TYPE_deleted_documents = 7
val KEY_SCAN_TYPE = "key_scan_type"
val KEY_FILE_TYPE = "key_file_type"
val FILE_TYPE_PHOTO = 0
val FILE_TYPE_VIDEO = 1
val FILE_TYPE_AUDIO = 2
val FILE_TYPE_DOCUMENTS = 3
@ -103,6 +95,7 @@ object Common {
if (filtered.isNotEmpty()) key to filtered else null
}
}
fun searchByNameList(
list: List<ResultDataFiles>,
keyword: String
@ -112,7 +105,6 @@ object Common {
}
/**
* 格式化文件大小显示
*/
@ -261,19 +253,7 @@ object Common {
}
/**
* 去掉缩略图的集合
*/
suspend fun filterThumbnailsAsync(
originalList: MutableList<Pair<String, List<ResultDataFiles>>>
): List<Pair<String, List<ResultDataFiles>>> = withContext(Dispatchers.Default) {
originalList.asSequence()
.map { (key, files) ->
key to files.asSequence().filter { !it.isThumbnail }.toList()
}
.filter { it.second.isNotEmpty() }
.toList()
}
fun removeSelectedFromList(
@ -294,12 +274,12 @@ object Common {
}
fun getMediaDuration(filePath: String): Long {
val retriever = MediaMetadataRetriever()
return try {
retriever.setDataSource(filePath)
val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
val durationStr =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
durationStr?.toLongOrNull() ?: 0L // 单位:毫秒
} catch (e: Exception) {
e.printStackTrace()
@ -323,6 +303,7 @@ object Common {
String.format("%02d:%02d", minutes, seconds)
}
}
fun getFileMIME(file: File): String {
val ext = file.extension.lowercase()
val mimeTypeFromExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
@ -340,6 +321,7 @@ object Common {
mime == "application/zip" ||
mime == "application/x-rar-compressed" ||
mime == "application/x-7z-compressed" -> "archive"
else -> "other"
}
}
@ -348,9 +330,11 @@ object Common {
"xapk" to "application/zip",
// 可以继续添加其他自定义扩展名
)
fun getMimeTypeParts(file: File): String {
val extension = file.extension.lowercase()
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)?:customMimeMap[extension]
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
?: customMimeMap[extension]
showLog("-----------ext=$extension mimeType=${mimeType}")
return if (mimeType != null && mimeType.contains("/")) {
val parts = mimeType.split("/")
@ -380,6 +364,7 @@ object Common {
fun getFormatDate(time: Long): String {
return dateFormat.format(Date(time))
}
fun getItemMonthDay(time: Long): String {
return itemDateFormat.format(time)
}
@ -400,4 +385,117 @@ object Common {
fun showLog(msg: String) {
Log.d("============", msg)
}
fun shareSingleFile(context: Context, file: File, fileType: FileType) {
val uri = FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider", // 与 Manifest 保持一致
file
)
val intent = Intent(Intent.ACTION_SEND).apply {
type = getMediaFileStr(fileType)
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(intent, "分享文件"))
}
fun shareMultipleFiles(context: Context, files: List<File>, fileType: FileType) {
val uris = files.map {
FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", it)
}
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
type = getMediaFileStr(fileType)
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(intent, "分享多个文件"))
}
fun getResolution(type: FileType, file: File): String {
return when (type) {
FileType.PHOTO -> {
getImageSize(file).run {
"$first*$second"
}
}
FileType.VIDEO -> getVideoResolution(file.path).run {
"$first*$second"
}
else -> ""
}
}
private fun getMediaFileStr(fileType: FileType): String {
return when (fileType) {
FileType.PHOTO -> "image/*"
FileType.VIDEO -> "video/*"
FileType.AUDIO -> "audio/*"
FileType.DOCUMENT -> "application/*"
}
}
private fun getImageSize(file: File): Pair<Int, Int> {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.absolutePath, options)
val width = options.outWidth
val height = options.outHeight
return Pair(width, height)
}
fun shareApp(context: Context) {
val appPackageName = context.packageName
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(
Intent.EXTRA_TEXT,
"I recommend a useful app\nhttps://play.google.com/store/apps/details?id=$appPackageName"
)
}
context.startActivity(
Intent.createChooser(shareIntent, "分享应用")
)
}
fun getScanType(scanType: ScanType) {
when (scanType) {
ScanType.ALL_PHOTO -> {}
ScanType.DELETED_PHOTO -> {}
ScanType.ALL_VIDEO -> {}
ScanType.DELETED_VIDEO -> {}
ScanType.ALL_AUDIO -> {}
ScanType.DELETED_AUDIO -> {}
ScanType.ALL_DOCUMENT -> {}
ScanType.DELETED_DOCUMENT -> {}
}
}
fun switchFileType(fileType: FileType,onPhoto:()-> Unit,onVideo:()-> Unit,onAudio:()-> Unit,onDocument:()-> Unit){
when (fileType) {
FileType.PHOTO -> {onPhoto()}
FileType.VIDEO -> {onVideo()}
FileType.AUDIO -> {onAudio()}
FileType.DOCUMENT -> {onDocument()}
}
}
fun getScanType1(scanType: ScanType) {
when (scanType) {
ScanType.ALL_PHOTO, ScanType.DELETED_PHOTO -> {}
ScanType.ALL_VIDEO, ScanType.DELETED_VIDEO -> {}
ScanType.ALL_AUDIO, ScanType.DELETED_AUDIO -> {}
ScanType.ALL_DOCUMENT, ScanType.DELETED_DOCUMENT -> {}
}
}
}

View File

@ -8,6 +8,8 @@ import android.os.Parcelable
import android.util.TypedValue
import androidx.recyclerview.widget.RecyclerView
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.duration
import com.ux.video.file.filerecovery.db.isThumbnail
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Date
@ -24,6 +26,8 @@ object ExtendFunctions {
}
inline fun <reified T : Parcelable> Intent.getParcelableArrayListExtraCompat(key: String): ArrayList<T>? {
extras?.classLoader = T::class.java.classLoader
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableArrayListExtra(key, T::class.java)
} else {
@ -134,7 +138,7 @@ object ExtendFunctions {
maxSize: Long
): List<ResultDataFiles> {
if (minSize == -1L) return this
return this.filter { it.duration in minSize..maxSize }
return this.filter { it.duration() in minSize..maxSize }
}
/**
@ -160,7 +164,7 @@ object ExtendFunctions {
): List<Pair<String, List<ResultDataFiles>>> {
if (minSize == -1L) return this
return this.mapNotNull { (date, files) ->
val filtered = files.filter { it.duration in minSize..maxSize }
val filtered = files.filter { it.duration() in minSize..maxSize }
if (filtered.isNotEmpty()) date to filtered else null
}
}
@ -185,7 +189,7 @@ object ExtendFunctions {
withContext(Dispatchers.Default) {
this@filterThumbnailsAsync.asSequence()
.mapNotNull { (key, files) ->
val filtered = files.asSequence().filter { !it.isThumbnail }.toList()
val filtered = files.asSequence().filter { !it.isThumbnail() }.toList()
if (filtered.isNotEmpty()) key to filtered else null
}
.toList()
@ -198,7 +202,7 @@ object ExtendFunctions {
suspend fun List<ResultDataFiles>.filterRemoveThumbnailsAsync(): List<ResultDataFiles> =
withContext(Dispatchers.Default) {
this@filterRemoveThumbnailsAsync.asSequence()
.filter { !it.isThumbnail } // 去掉 isThumbnail = true 的项
.filter { !it.isThumbnail() } // 去掉 isThumbnail = true 的项
.toList()
}

View File

@ -0,0 +1,13 @@
package com.ux.video.file.filerecovery.utils
enum class FileType(val value: Int,val tabIndex: Int) {
PHOTO(8,0),
VIDEO(9,1),
AUDIO(10,2),
DOCUMENT(11,3);
companion object {
fun from(value: Int): FileType? =
FileType.entries.find { it.value == value }
}
}

View File

@ -1,23 +1,17 @@
package com.ux.video.file.filerecovery.utils
import android.content.ContentUris
import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.text.format.Formatter
import android.util.Log
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
@ -56,7 +50,7 @@ object ScanManager {
context: Context,
root: File, maxDepth: Int = 5,
maxFiles: Int = 5000,
type: Int
fileType: FileType
): Flow<ScanState> = flow {
val result = mutableMapOf<String, MutableList<File>>()
@ -71,24 +65,7 @@ object ScanManager {
if (file.isDirectory) {
scanDocuments(file, depth + 1)
} else {
var fileCheckBoolean: Boolean = false
when (type) {
VALUE_SCAN_TYPE_photo -> {
fileCheckBoolean = isFormatFile(file, IMAGE_FILE) && isValidImage(file)
}
VALUE_SCAN_TYPE_video -> {
fileCheckBoolean = isFormatFile(file, VIDEO_FILE)
}
VALUE_SCAN_TYPE_audio -> {
fileCheckBoolean = isFormatFile(file, AUDIO_FILE)
}
VALUE_SCAN_TYPE_documents -> {
fileCheckBoolean = file.isFile && isFormatFile(file, DOCUMENT_FILE)
}
}
val fileCheckBoolean: Boolean = checkFileFormat(file,fileType)
if (fileCheckBoolean) {
val dirName = file.parentFile?.name ?: "Unknown"
@ -114,8 +91,7 @@ object ScanManager {
file.length()
),
lastModified = file.lastModified(),
resolution = getResolution(type, file),
fileType = getFileType(type)
fileType = fileType
)
}
ResultData(dir, ArrayList(resultDataFilesList))
@ -159,9 +135,9 @@ object ScanManager {
fun scanHiddenPhotoAsync(
context: Context,
root: File, maxDepth: Int = 5,
maxFiles: Int = 5000, type: Int
maxFiles: Int = 5000, fileType: FileType
): Flow<ScanState> = flow {
// scanRecycler(context)
val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0
@ -169,46 +145,52 @@ object ScanManager {
suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) {
if (!dir.exists() || !dir.isDirectory) return
if (depth > maxDepth || fileCount >= maxFiles) return
val skipDirs = setOf(
"Android",
"obb",
"data",
".thumbnails",
".cache"
)
if (skipDirs.contains(dir.name)) return
showLog("HiddenScan", "${dir.name} 111111")
dir.listFiles()?.forEach { file ->
if (file.isDirectory) {
val isHidden = file.name.startsWith(".")
scanDir(file, depth + 1, insideHidden = insideHidden || isHidden)
} else {
val fileCheckBoolean: Boolean = checkFileFormat(file,fileType)
if (insideHidden) {
var fileCheckBoolean: Boolean = false
when (type) {
VALUE_SCAN_TYPE_deleted_photo -> {
fileCheckBoolean =
isFormatFile(file, IMAGE_FILE) && isValidImage(file)
}
VALUE_SCAN_TYPE_deleted_video -> {
fileCheckBoolean = isFormatFile(file, VIDEO_FILE)
}
VALUE_SCAN_TYPE_deleted_audio -> {
fileCheckBoolean = isFormatFile(file, AUDIO_FILE)
}
VALUE_SCAN_TYPE_deleted_documents -> {
fileCheckBoolean = file.isFile && isFormatFile(file, DOCUMENT_FILE)
}
}
if (fileCheckBoolean) {
val dirName = file.parentFile?.name ?: "Unknown"
ScanManager.showLog("HiddenScan", "${dirName} 22222")
ScanManager.showLog("HiddenScan", "${dirName} 22222 ${file.name}")
val list = result.getOrPut(dirName) { mutableListOf() }
list.add(file)
fileCount++
emit(ScanState.Progress(fileCount, file.absolutePath))
}
} else {
if (file.name.startsWith(".") && fileCheckBoolean) {
val dirName = file.parentFile?.name ?: "Unknown"
ScanManager.showLog("HiddenScan", "${dirName} 33333 ${file.name}")
val list = result.getOrPut(dirName) { mutableListOf() }
list.add(file)
fileCount++
emit(ScanState.Progress(fileCount, file.absolutePath))
}
}
}
}
}
scanDir(root, depth = 0)
ScanManager.showLog("HiddenScan", " 3333")
ScanManager.showLog("HiddenScan", " 44444444")
val map = result.map { (dir, files) ->
val resultDataFilesList = files.map { file ->
ResultDataFiles(
@ -220,8 +202,7 @@ object ScanManager {
file.length()
),
lastModified = file.lastModified(),
resolution = getResolution(type, file),
fileType = getFileType(type)
fileType = fileType
)
}
@ -231,17 +212,63 @@ object ScanManager {
}
private fun getFileType(scanType: Int): Int {
return when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> 0
private fun checkFileFormat(file: File, fileType: FileType): Boolean {
return when (fileType) {
FileType.PHOTO -> {
isFormatFile(file, IMAGE_FILE)
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> 1
FileType.VIDEO -> {
isFormatFile(file, VIDEO_FILE)
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> 2
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> 3
else -> 3
FileType.AUDIO -> {
isFormatFile(file, AUDIO_FILE)
}
FileType.DOCUMENT -> {
file.isFile && isFormatFile(file, DOCUMENT_FILE)
}
}
}
private fun scanRecycler(context: Context) {
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME
)
val selection = "${MediaStore.Images.Media.IS_TRASHED} = ?"
val selectionArgs = arrayOf("1")
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
null
)
Log.d("Trash", "cursor=$cursor ")
cursor?.use {
Log.d("Trash", "11111111")
while (it.moveToNext()) {
Log.d("Trash", "2222222222")
val id = it.getLong(0)
val name = it.getString(1)
val uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
// 这是“可恢复”的文件
Log.d("Trash", "name=$name uri=$uri")
}
}
}
private fun getFileSizeByMediaStore(context: Context, file: File): Long {
val uri = Uri.fromFile(file)
@ -255,23 +282,24 @@ object ScanManager {
return file.length() // fallback
}
private fun getResolution(type: Int, file: File): String {
return when (type) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
getImageSize(file).run {
"$first*$second"
}
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> getVideoResolution(file.path).run {
"$first*$second"
}
else -> ""
}
}
// private fun getResolution(type: Int, file: File): String {
// return when (type) {
// VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
// getImageSize(file).run {
// "$first*$second"
// }
// }
//
// VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> getVideoResolution(file.path).run {
// "$first*$second"
// }
//
// else -> ""
// }
// }
private fun isFormatFile(file: File, types: List<String>): Boolean {
if (!file.exists() || file.length() <= 0) return false
val ext = file.extension.lowercase()
return types.contains(ext)
}
@ -284,6 +312,7 @@ object ScanManager {
Log.d(tag, msg)
}
fun isValidImage(file: File): Boolean {
if (!file.exists() || file.length() <= 0) return false
return try {

View File

@ -0,0 +1,38 @@
package com.ux.video.file.filerecovery.utils
enum class ScanType(val value: Int) {
ALL_PHOTO(0),
DELETED_PHOTO(1),
ALL_VIDEO(2),
DELETED_VIDEO(3),
ALL_AUDIO(4),
DELETED_AUDIO(5),
ALL_DOCUMENT(6),
DELETED_DOCUMENT(7);
val mediaType: FileType
get() = when (this) {
ALL_PHOTO, DELETED_PHOTO -> FileType.PHOTO
ALL_VIDEO, DELETED_VIDEO -> FileType.VIDEO
ALL_AUDIO, DELETED_AUDIO -> FileType.AUDIO
ALL_DOCUMENT, DELETED_DOCUMENT -> FileType.DOCUMENT
}
val isDeletedScan: Boolean
get() = when (this) {
DELETED_PHOTO,
DELETED_VIDEO,
DELETED_AUDIO,
DELETED_DOCUMENT -> true
else -> false
}
companion object {
fun from(value: Int): ScanType? =
ScanType.entries.find { it.value == value }
}
}

View File

@ -107,4 +107,9 @@ class PlayMediaManager(var context: Context, var mediaFile: File, var playView:
}
})
}
fun stopPlayAudio(){
player.stop()
}
}

View File

@ -15,8 +15,10 @@ import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityVideoPlayBinding
import com.ux.video.file.filerecovery.sort.RecoverOrDeleteManager
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.db.targetFile
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.FileType
class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
@ -48,7 +50,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
binding.run {
myData?.let { resultPhotosFiles->
imageBack.setOnClickListener { finish() }
resultPhotosFiles.targetFile?.let {
resultPhotosFiles.targetFile().let {
PlayMediaManager(context = this@VideoPlayActivity, mediaFile = it, playView = playerView,
seekBar = seekBar, playBtn = playImage, onUpdateProgress = { current,total->
textTimeCurrent.text = current
@ -141,7 +143,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
myData?.let {
player = ExoPlayer.Builder(this).build()
binding.playerView.player = player
val mediaItem = MediaItem.fromUri(Uri.fromFile(it.targetFile))
val mediaItem = MediaItem.fromUri(Uri.fromFile(it.targetFile()))
player.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
@ -177,6 +179,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
startActivity(Intent(this@VideoPlayActivity, RecoverySuccessActivity::class.java).apply {
putExtra(RecoverySuccessActivity.KEY_SUCCESS_COUNT, number)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_TYPE, type)
putExtra(Common.KEY_FILE_TYPE, FileType.VIDEO.value)
})
}
}

View File

@ -0,0 +1,28 @@
package com.ux.video.file.filerecovery.welcome
import android.content.Intent
import android.view.LayoutInflater
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivitySplashBinding
import com.ux.video.file.filerecovery.main.MainActivity
import com.ux.video.file.filerecovery.recovery.RecoveryActivity
class SplashActivity : BaseActivity<ActivitySplashBinding>() {
override fun inflateBinding(inflater: LayoutInflater): ActivitySplashBinding =
ActivitySplashBinding.inflate(inflater)
override fun initView() {
super.initView()
binding.textEnter.setOnClickListener {
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
finish()
}
}
override fun initData() {
super.initData()
}
}

View File

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

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="50dp" />
<gradient
android:angle="180"
android:endColor="@color/welcome_enter_start_color"
android:startColor="@color/welcome_enter_end_color" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
tools:context=".sort.PhotoInfoActivity">
tools:context=".detail.DetailsActivity">
<RelativeLayout
android:layout_width="match_parent"

View File

@ -27,6 +27,7 @@
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_setting"
android:id="@+id/im_setting"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
@ -186,7 +187,7 @@
android:id="@+id/layout_recovery"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/main_type_bg"
android:gravity="center_vertical"
@ -219,6 +220,7 @@
android:layout_height="60dp"
android:layout_marginStart="11dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
android:background="@drawable/main_type_bg"
android:gravity="center_vertical"
android:orientation="horizontal"
@ -243,66 +245,8 @@
app:fontType="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_wchat">
<Button
android:id="@+id/btn_permission"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="管理所有文件的权限申请" />
<Button
android:id="@+id/btn_scan_all_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="扫描所有图片" />
<Button
android:id="@+id/btn_scan_all_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="扫描所有视频" />
<Button
android:id="@+id/btn_scan_all_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="扫描所有音频" />
<Button
android:id="@+id/btn_scan_all_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="扫描所有文件" />
</LinearLayout>
<RelativeLayout
android:id="@+id/layout_permission"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<Button
android:id="@+id/allow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="50dp"
android:text="Allow" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
tools:context=".sort.PhotoSortingActivity">
tools:context=".sort.SortingActivity">
<RelativeLayout
android:id="@+id/layout_top"

View File

@ -1,17 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".documents.DocumentsScanResultActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
tools:context=".settings.PrivacyPolicyActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,101 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
tools:context=".settings.SetupActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/image_view_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingHorizontal="13dp"
android:paddingVertical="14dp"
android:src="@drawable/black_return" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/settings"
android:textColor="@color/main_title"
android:textSize="16sp"
app:fontType="bold" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layout_share_app"
android:layout_width="match_parent"
android:layout_height="65dp"
android:paddingStart="16dp"
android:paddingEnd="0dp">
<ImageView
android:id="@+id/im_share_app"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:src="@drawable/icon_share_app" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/im_share_app"
android:text="@string/share_app"
android:textColor="@color/main_title"
android:textSize="14sp"
app:fontType="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:background="@color/dividing_line_color" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layout_privacy_policy"
android:layout_width="match_parent"
android:layout_height="65dp"
android:paddingStart="16dp"
android:paddingEnd="0dp">
<ImageView
android:id="@+id/im_privacy_policy"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:src="@drawable/icon_privacy_policy" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/im_privacy_policy"
android:text="@string/privacy_policy"
android:textColor="@color/main_title"
android:textSize="14sp"
app:fontType="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:background="@color/dividing_line_color" />
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/welcome_bg"
tools:context=".welcome.SplashActivity">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginTop="87dp"
android:text="@string/welcome_name"
android:textColor="@color/main_title"
android:textSize="48sp"
app:fontType="alimama"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginTop="15dp"
android:text="@string/welcome_sub_text"
android:textColor="@color/main_sub_title"
android:textSize="16sp"
app:fontType="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_name" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_enter"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="60dp"
android:layout_marginBottom="80dp"
android:background="@drawable/bg_welcome_enter"
android:gravity="center"
android:text="@string/enter"
android:textColor="@color/white"
android:textSize="18sp"
app:fontType="alimama"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,26 @@
<?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="wrap_content"
android:layout_height="40dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/bg_000000_10"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_checkmark_16dp"/>
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@color/white"
android:layout_marginStart="5dp"
android:text="@string/deleted_success"/>
</LinearLayout>

View File

@ -11,7 +11,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/selector_recovery_file_tab_layout_title"
android:textSize="15sp"
android:textSize="13sp"
android:gravity="center"
app:fontType="bold" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- <style name="Theme.FileRecovery" parent="Theme.SplashScreen">-->
<!-- <item name="windowSplashScreenBackground">@color/black</item>-->
<!-- <item name="windowSplashScreenAnimatedIcon">@mipmap/recovery</item>-->
<!-- <item name="postSplashScreenTheme">@style/Theme.FileRecovery</item>-->
<!-- </style>-->
<style name="Base.Theme.FileRecovery" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
<item name="android:windowBackground">@color/white</item>
</style>
</resources>

View File

@ -26,5 +26,8 @@
<color name="view_div_color">#D9D9D9</color>
<color name="bg_seekbar_video_play">#99F2F2F7</color>
<color name="dialog_delete_success_color">#98000000</color>
<color name="welcome_enter_start_color">#70ABFF</color>
<color name="welcome_enter_end_color">#326EFF</color>
</resources>

View File

@ -1,10 +1,13 @@
<resources>
<string name="app_name">File Recovery</string>
<string name="app_name">File Recovery Tool</string>
<string name="scan_all_file">Scan all files</string>
<string name="scan_deleted_files">Scan deleted files</string>
<string name="select_all">Select all</string>
<string name="select">Select</string>
<string name="deselect_all">Deselect all</string>
<string name="enter">ENTER</string>
<string name="welcome_name">File\nRecovery</string>
<string name="welcome_sub_text">Important files shouldn\'t just disappear.</string>
<string name="size_kb">%.2f KB</string>
@ -29,7 +32,7 @@
<string name="audio_title">Audio recovery</string>
<string name="document_title">Document recovery</string>
<string name="permission_request">Permission is required to access all files</string>
<string name="permission_request_content">File Recovery -All Recovery requires full access to your device storage to search for lost or deleted files.</string>
<string name="permission_request_content">File Recovery Tool -All Recovery requires full access to your device storage to search for lost or deleted files.</string>
<string name="permission_request_promote">We will never share, upload, or send your data without your permission.</string>
<string name="cancel">Cancel</string>
@ -88,7 +91,7 @@ wait..</string>
<string name="search">Search</string>
<string name="recovered_files">Recovered files</string>
<string name="text_counts">(%d)</string>
<string name="warning">Please do not uninstall the File Recovery -
<string name="warning">Please do not uninstall the File Recovery Tool -
All Recovery to avoid file loss.</string>
<string name="no_files">No files have been recovered yet~</string>
<string name="scan_photos">Scan photos</string>
@ -96,7 +99,11 @@ All Recovery to avoid file loss.</string>
<string name="scan_audios">Scan audios</string>
<string name="scan_documents">Scan documents</string>
<string name="share_placeholder">Share (%d)</string>
<string name="settings">Settings</string>
<string name="share_app">Share App</string>
<string name="share">Share</string>
<string name="privacy_policy">Privacy Policy</string>
<string name="privacy_policy_url">https://file-filerecovery.bitbucket.io//privacy.html</string>
<string-array name="filter_date">

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 指向公共根目录 /storage/emulated/0 -->
<external-path
name="scanned_root"
path="." />
<!-- 推荐:用于分享时的临时缓存目录 -->
<external-cache-path
name="share_cache"
path="share/" />
</paths>

View File

@ -2,10 +2,12 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
id("com.google.gms.google-services") version "4.4.2" apply false
id ("com.google.firebase.crashlytics") version "3.0.2" apply false
}
buildscript {
dependencies {
classpath("io.objectbox:objectbox-gradle-plugin:4.0.3")
classpath("io.objectbox:objectbox-gradle-plugin:5.0.1")
}
}

View File

@ -21,3 +21,6 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=2080

6
keystore.properties Normal file
View File

@ -0,0 +1,6 @@
app_name=File Recovery
package_name=com.ux.video.file.filerecovery
keystoreFile=app/FileRecovery
key_alias=FileRecoverykey0
key_store_password=FileRecovery
key_password=FileRecovery