增加数据库

This commit is contained in:
litingting 2025-10-29 09:55:30 +08:00
parent 56f3f48dd9
commit b62abaec37
53 changed files with 1003 additions and 295 deletions

View File

@ -3,6 +3,7 @@ plugins {
alias(libs.plugins.kotlin.android)
id ("kotlin-kapt")
id ("kotlin-parcelize")
id("io.objectbox")
}
android {
@ -47,6 +48,9 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.fragment.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View File

@ -0,0 +1,78 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "2:1219714858038066790",
"lastPropertyId": "8:5134665607056785092",
"name": "ResultDataFiles",
"properties": [
{
"id": "1:1863268749092884504",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:685886748782553244",
"name": "fileType",
"type": 5
},
{
"id": "3:313976899017664946",
"name": "name",
"type": 9
},
{
"id": "4:3491625332486824618",
"name": "path",
"type": 9
},
{
"id": "5:2788384583815433725",
"name": "size",
"type": 6
},
{
"id": "6:8902434027574194684",
"name": "sizeString",
"type": 9
},
{
"id": "7:8305655849151310709",
"name": "lastModified",
"type": 6
},
{
"id": "8:5134665607056785092",
"name": "resolution",
"type": 9
}
],
"relations": []
}
],
"lastEntityId": "2:1219714858038066790",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
4830032338777965154
],
"retiredIndexUids": [],
"retiredPropertyUids": [
7967846713481917737,
5916406351352231902,
5265574492754659377,
1284016652408596993,
3831579248666795267,
4445203567182319472,
6858261029979256233,
2835789057594891780
],
"retiredRelationUids": [],
"version": 1
}

View File

@ -2,12 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Android 10 及以下 -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" /> <!-- Android 11+ -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 11+ -->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
@ -24,14 +20,17 @@
android:supportsRtl="true"
android:theme="@style/Theme.FileRecovery"
tools:targetApi="31">
<activity
android:name=".recovery.RecoveryActivity"
android:exported="false" />
<activity
android:name=".video.VideoPlayActivity"
android:exported="false" />
<activity
android:name=".photo.PhotoInfoActivity"
android:name=".sort.PhotoInfoActivity"
android:exported="false" />
<activity
android:name=".photo.PhotoSortingActivity"
android:name=".sort.PhotoSortingActivity"
android:exported="false" />
<activity
android:name=".result.ScanResultDisplayActivity"

View File

@ -1,6 +1,7 @@
package com.ux.video.file.filerecovery
import android.app.Application
import com.ux.video.file.filerecovery.db.ObjectBoxManager
import org.jaaksi.pickerview.widget.BasePickerView
class App: Application() {
@ -14,5 +15,6 @@ class App: Application() {
BasePickerView.sDefaultItemSize = 40
// BasePickerView.sDefaultDrawIndicator = false
ObjectBoxManager.initObjectBoxDb(this)
}
}

View File

@ -0,0 +1,24 @@
package com.ux.video.file.filerecovery.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
abstract class BaseFragment<T : ViewBinding> : Fragment() {
protected lateinit var binding: T
abstract fun initBinding(inflater: LayoutInflater, container: ViewGroup?): T
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = initBinding(inflater,container)
return binding.root
}
}

View File

@ -0,0 +1,144 @@
package com.ux.video.file.filerecovery.db
import android.content.Context
import com.ux.video.file.filerecovery.utils.Common
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.config.DebugFlags
import io.objectbox.reactive.DataSubscription
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.jvm.java
object ObjectBoxManager {
val pageSize = 10
lateinit var objectboxStore: BoxStore
var AllRecoveryBox: Box<ResultDataFiles>? = null
val observer: DataSubscription? = null
fun initObjectBoxDb(context: Context) {
objectboxStore = MyObjectBox.builder().androidContext(context)
.debugFlags(DebugFlags.LOG_QUERIES or DebugFlags.LOG_QUERY_PARAMETERS)
.build()
}
private fun getDataBox(): Box<ResultDataFiles> {
AllRecoveryBox =
AllRecoveryBox ?: objectboxStore.boxFor<ResultDataFiles>(ResultDataFiles::class.java)
return AllRecoveryBox!!
}
// fun getLikeBox(): Box<ResultPhotosFiles>? {
// likeWallpaperBox = likeWallpaperBox ?: boxStore?.boxFor<LikeWallpaper>(ResultPhotosFiles::class.java)
// return likeWallpaperBox
// }
//
// fun setLikeUpdateListener(queryAllLike:(List<LikeWallpaper>)->Unit): DataSubscription {
// val likeBox: Box<LikeWallpaper>? = getLikeBox()
// val build: Query<LikeWallpaper> = likeBox!!.query()
// .build()
// return build.subscribe(DataSubscriptionList())
// .on(AndroidScheduler.mainThread())
// .observer { data ->
// Helper.showMyLog("---OnLikeUpdateListener-------------" + data.size)
// queryAllLike(data)
// }
// }
//
fun addRecoveryFile(resultData: ResultDataFiles) {
val objectBox: Box<ResultDataFiles> = getDataBox()
val first = objectBox.query(ResultDataFiles_.path.equal(resultData.path))
.build().findFirst()
if (first == null) {
objectBox.put(resultData)
Common.showLog("----------addRecoveryFile----${resultData.path}")
}
}
//
//
// fun insertLike(favoriteData: LikeWallpaper) {
// val likeBox: Box<LikeWallpaper>? = getLikeBox()
// val first: LikeWallpaper? = likeBox!!.query()
// .equal(LikeWallpaper_.id, favoriteData.id)
// .build()
// .findFirst()
// if (first == null) {
// Helper.showMyLog("---insertLike-----------------id=" + favoriteData.id)
// likeBox.put(favoriteData)
// } else {
// Helper.showMyLog("---insertLike-----------------id=" + favoriteData.id)
// }
// }
//
//
fun deleteRecoveryFile(resultData: ResultDataFiles) {
val objectBox = getDataBox()
val first =
objectBox.query(ResultDataFiles_.path.equal(resultData.path)).build().findFirst()
if (first != null) {
val remove = objectBox.remove(first.id)
Common.showLog("----------addRecoveryFile---删除${remove}-${resultData.path}")
}
}
//
// fun queryIsLike(id: Long): Boolean {
// val likeBox: Box<LikeWallpaper>? = getLikeBox()
// val first: LikeWallpaper? = likeBox!!.query()
// .equal(LikeWallpaper_.id, id)
// .build()
// .findFirst()
// return first != null
// }
//
//
//
fun queryRecoveryFile(type: Int): List<ResultDataFiles> {
val dataBox = getDataBox()
val list: List<ResultDataFiles> = dataBox.query(ResultDataFiles_.fileType.equal(type))
.build()
.find()
return list
}
suspend fun queryRecoveryFileAsync(type: Int): List<ResultDataFiles> =
withContext(Dispatchers.IO) {
val dataBox = getDataBox()
dataBox.query(ResultDataFiles_.fileType.equal(type))
.build()
.find()
}
//
// fun queryLike(currentPage: Int): List<LikeWallpaper> {
// val offset: Int = (currentPage - 1) * pageSize
// val likeBox: Box<LikeWallpaper>? = getLikeBox()
// val data: List<LikeWallpaper> = likeBox!!.query()
// .build()
// .find(offset.toLong(), pageSize.toLong())
//
// return data
// }
//
// fun queryAllData(wallpaperType: Long): List<VideoWallpaper> {
// val objectBoxLike: Box<VideoWallpaper>? = getDataBox()
// val data: List<VideoWallpaper> = objectBoxLike!!.query()
// .equal(VideoWallpaper_.wallpapertype, wallpaperType)
// .build()
// .find()
//
// return data
// }
}

View File

@ -1,11 +1,11 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.db
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class ResultPhotos(
data class ResultData(
val dirName: String,
val allFiles: ArrayList<ResultPhotosFiles>
val allFiles: ArrayList<ResultDataFiles>
): Parcelable

View File

@ -1,15 +1,22 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.db
import java.io.File
import android.os.Parcelable
import com.ux.video.file.filerecovery.utils.Common
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import kotlinx.parcelize.Parcelize
@Entity
@Parcelize
data class ResultPhotosFiles(
data class ResultDataFiles(
@Id
var id: Long = 0,
// 0-3 photo\video\audio\documents
var fileType: Int,
val name: String,
val path: String? = null,
val path: String,
val size: Long, // 字节
val sizeString: String,
val lastModified: Long, // 时间戳
@ -36,4 +43,5 @@ data class ResultPhotosFiles(
val duration: Long
get() {
return Common.getMediaDuration(path.toString()) }
}

View File

@ -5,10 +5,10 @@ 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.photo.ResultPhotos
import com.ux.video.file.filerecovery.db.ResultData
class DocumentsScanResultAdapter(mContext: Context) :
BaseAdapter<ResultPhotos, DocumentsScanResultAdapterBinding>(mContext) {
BaseAdapter<ResultData, DocumentsScanResultAdapterBinding>(mContext) {
override fun getViewBinding(parent: ViewGroup): DocumentsScanResultAdapterBinding =
DocumentsScanResultAdapterBinding.inflate(
LayoutInflater.from(parent.context),
@ -18,7 +18,7 @@ class DocumentsScanResultAdapter(mContext: Context) :
override fun bindItem(
holder: VHolder<DocumentsScanResultAdapterBinding>,
item: ResultPhotos
item: ResultData
) {
holder.vb.run {

View File

@ -1,7 +1,6 @@
package com.ux.video.file.filerecovery.main
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -20,9 +19,8 @@ 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.main.ScanSelectTypeActivity
import com.ux.video.file.filerecovery.photo.DateFilterPopupWindows
import com.ux.video.file.filerecovery.photo.DatePickerDialogFragment
import com.ux.video.file.filerecovery.recovery.RecoveryActivity
import com.ux.video.file.filerecovery.sort.DatePickerDialogFragment
import com.ux.video.file.filerecovery.utils.ScanManager
class MainActivity : BaseActivity<ActivityMainBinding>() {
@ -95,6 +93,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
currentGoType = ScanSelectTypeActivity.Companion.VALUE_DOCUMENT
intentCheck()
}
layoutRecovery.setOnClickListener {
startActivity(Intent(this@MainActivity,RecoveryActivity::class.java))
}
}
binding.tvTitle.setOnClickListener {

View File

@ -0,0 +1,74 @@
package com.ux.video.file.filerecovery.recovery
import android.view.LayoutInflater
import androidx.lifecycle.lifecycleScope
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.google.android.material.tabs.TabLayoutMediator
import com.ux.video.file.filerecovery.R
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.utils.CustomTextView
import kotlinx.coroutines.launch
class RecoveryActivity : BaseActivity<ActivityRecoveryBinding>() {
override fun inflateBinding(inflater: LayoutInflater): ActivityRecoveryBinding = ActivityRecoveryBinding.inflate(inflater)
override fun initView() {
super.initView()
binding.run {
viewPage2.adapter = MyViewPage2Adapter(this@RecoveryActivity,listOf(RecoveryPhotoFragment(),RecoveryPhotoFragment(),RecoveryPhotoFragment(),RecoveryPhotoFragment()))
TabLayoutMediator(tabLayout,viewPage2){tab,position->
val tabView = LayoutInflater.from(this@RecoveryActivity).inflate(R.layout.tab_layout_item, null)
val tvTitle = tabView.findViewById<CustomTextView>(R.id.tab_item_title)
val tvCount = tabView.findViewById<CustomTextView>(R.id.tab_item_count)
tvCount.text = getString(R.string.text_counts, 0)
when(position){
0->{tvTitle.text = getString(R.string.text_photos)}
1->{tvTitle.text = getString(R.string.text_videos)}
2->{tvTitle.text = getString(R.string.text_audios)}
3->{tvTitle.text = getString(R.string.text_documents)}
}
tab.customView = tabView
}.attach()
tabLayout.addOnTabSelectedListener(object :OnTabSelectedListener{
override fun onTabSelected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
}
}
override fun initData() {
super.initData()
lifecycleScope.launch{
val recoveryFilePhoto = ObjectBoxManager.queryRecoveryFileAsync(0)
val recoveryFileVideo = ObjectBoxManager.queryRecoveryFileAsync(1)
val recoveryFileAudio = ObjectBoxManager.queryRecoveryFileAsync(2)
val recoveryFileDocuments = ObjectBoxManager.queryRecoveryFileAsync(3)
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)
}
}
}
}

View File

@ -0,0 +1,14 @@
package com.ux.video.file.filerecovery.recovery.ui
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class MyViewPage2Adapter(
fragmentActivity: FragmentActivity,
private val fragments: List<Fragment>
) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int = fragments.size
override fun createFragment(position: Int): Fragment = fragments[position]
}

View File

@ -0,0 +1,32 @@
package com.ux.video.file.filerecovery.recovery.ui.recoveryphoto
import androidx.fragment.app.viewModels
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseFragment
import com.ux.video.file.filerecovery.databinding.FragmentRecoveryPhotoBinding
class RecoveryPhotoFragment : BaseFragment<FragmentRecoveryPhotoBinding>() {
companion object {
fun newInstance() = RecoveryPhotoFragment()
}
private val viewModel: RecoveryPhotoViewModel by viewModels()
override fun initBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRecoveryPhotoBinding =
FragmentRecoveryPhotoBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}

View File

@ -0,0 +1,7 @@
package com.ux.video.file.filerecovery.recovery.ui.recoveryphoto
import androidx.lifecycle.ViewModel
class RecoveryPhotoViewModel : ViewModel() {
}

View File

@ -7,9 +7,9 @@ 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.photo.PhotoSortingActivity
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
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
@ -28,7 +28,7 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var exitDialog: ExitDialogFragment? = null
private var list: ArrayList<ResultPhotos>? = null
private var list: ArrayList<ResultData>? = null
companion object {
val KEY_SCAN_RESULT = "scan_result"
@ -136,7 +136,7 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
}
private fun goSort(list: ArrayList<ResultPhotosFiles>) {
private fun goSort(list: ArrayList<ResultDataFiles>) {
startActivity(
Intent(
this@ScanResultDisplayActivity,

View File

@ -4,20 +4,12 @@ import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
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.databinding.ScanResultDocumentsAdapterBinding
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
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.ExtendFunctions.dpToPx
import java.io.File
/**
* 文件或者音频的扫描结果汇总适配器
@ -25,9 +17,9 @@ import java.io.File
class ScanResultDocumentsAdapter(
mContext: Context,
var type: Int,
var onClickItem: (allFiles: ArrayList<ResultPhotosFiles>) -> Unit
var onClickItem: (allFiles: ArrayList<ResultDataFiles>) -> Unit
) :
BaseAdapter<ResultPhotos, ScanResultDocumentsAdapterBinding>(mContext) {
BaseAdapter<ResultData, ScanResultDocumentsAdapterBinding>(mContext) {
override fun getViewBinding(parent: ViewGroup): ScanResultDocumentsAdapterBinding =
ScanResultDocumentsAdapterBinding.inflate(
LayoutInflater.from(parent.context),
@ -38,7 +30,7 @@ class ScanResultDocumentsAdapter(
@SuppressLint("SetTextI18n")
override fun bindItem(
holder: VHolder<ScanResultDocumentsAdapterBinding>,
item: ResultPhotos
item: ResultData
) {
holder.vb.run {

View File

@ -11,17 +11,17 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.databinding.ScanResultAdapterBinding
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.db.ResultData
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import java.io.File
class ScanResultPhotoAdapter(
mContext: Context,
var type: Int,
var onClickItem: (allFiles: ArrayList<ResultPhotosFiles>) -> Unit
var onClickItem: (allFiles: ArrayList<ResultDataFiles>) -> Unit
) :
BaseAdapter<ResultPhotos, ScanResultAdapterBinding>(mContext) {
BaseAdapter<ResultData, ScanResultAdapterBinding>(mContext) {
override fun getViewBinding(parent: ViewGroup): ScanResultAdapterBinding =
ScanResultAdapterBinding.inflate(
LayoutInflater.from(parent.context),
@ -32,7 +32,7 @@ class ScanResultPhotoAdapter(
@SuppressLint("SetTextI18n")
override fun bindItem(
holder: VHolder<ScanResultAdapterBinding>,
item: ResultPhotos
item: ResultData
) {
holder.vb.run {

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.graphics.Color
import android.os.Bundle

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.content.Context

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.annotation.SuppressLint
import android.content.Context

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseIngDialogFragment

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.content.Context
import android.graphics.Color
@ -9,7 +9,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow
import android.widget.RelativeLayout
import androidx.core.view.forEach
import com.ux.video.file.filerecovery.databinding.CommonLayoutFilterItemBinding
import com.ux.video.file.filerecovery.databinding.PopwindowsFilterBinding
import com.ux.video.file.filerecovery.utils.Common

View File

@ -1,18 +1,18 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.base.DiffBaseAdapter
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.GridSpacingItemDecoration
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
class PhotoDisplayDateAdapter(
@ -20,10 +20,10 @@ class PhotoDisplayDateAdapter(
var scanType: Int,
var mColumns: Int,
var viewModel: ScanRepository,
var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, isAdd: Boolean) -> Unit,
var clickItem: (item: ResultPhotosFiles) -> Unit
var onSelectedUpdate: (resultDataFiles: ResultDataFiles, isAdd: Boolean) -> Unit,
var clickItem: (item: ResultDataFiles) -> Unit
) :
BaseAdapter<Pair<String, List<ResultPhotosFiles>>, PhotoDisplayDateAdapterBinding>(mContext) {
BaseAdapter<Pair<String, List<ResultDataFiles>>, PhotoDisplayDateAdapterBinding>(mContext) {
private var allSelected: Boolean? = null
@ -75,7 +75,7 @@ class PhotoDisplayDateAdapter(
@SuppressLint("SetTextI18n")
override fun bindItem(
holder: VHolder<PhotoDisplayDateAdapterBinding>,
item: Pair<String, List<ResultPhotosFiles>>
item: Pair<String, List<ResultDataFiles>>
) {
holder.vb.run {
item.run {
@ -109,9 +109,10 @@ class PhotoDisplayDateAdapter(
recyclerChild.apply {
layoutManager = when (scanType) {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio -> {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
LinearLayoutManager(context)
}
else -> {
GridLayoutManager(context, mColumns)
}
@ -125,17 +126,17 @@ class PhotoDisplayDateAdapter(
}
}
object ItemDiffCallback : DiffUtil.ItemCallback<Pair<String, List<ResultPhotosFiles>>>() {
object ItemDiffCallback : DiffUtil.ItemCallback<Pair<String, List<ResultDataFiles>>>() {
override fun areItemsTheSame(
oldItem: Pair<String, List<ResultPhotosFiles>>,
newItem: Pair<String, List<ResultPhotosFiles>>
oldItem: Pair<String, List<ResultDataFiles>>,
newItem: Pair<String, List<ResultDataFiles>>
): Boolean {
return oldItem.first == newItem.first
}
override fun areContentsTheSame(
oldItem: Pair<String, List<ResultPhotosFiles>>,
newItem: Pair<String, List<ResultPhotosFiles>>
oldItem: Pair<String, List<ResultDataFiles>>,
newItem: Pair<String, List<ResultDataFiles>>
): Boolean {
return oldItem == newItem
}

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.content.Context
import android.graphics.drawable.Drawable
@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
@ -22,7 +21,9 @@ import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.NewBaseAdapter
import com.ux.video.file.filerecovery.databinding.FileSpanCountThreeAdapterBinding
import com.ux.video.file.filerecovery.databinding.FileSpanCountTwoAdapterBinding
import com.ux.video.file.filerecovery.databinding.OneAudioDocumentsItemBinding
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.utils.Common
import com.ux.video.file.filerecovery.utils.CustomTextView
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
@ -40,23 +41,25 @@ class PhotoDisplayDateChildAdapter(
* @param addOrRemove 选中还是取消选中
* @param dateAllSelected 这组数据是否全部选中某一天
*/
var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, addOrRemove: Boolean, dateAllSelected: Boolean) -> Unit,
var onSelectedUpdate: (resultDataFiles: ResultDataFiles, addOrRemove: Boolean, dateAllSelected: Boolean) -> Unit,
var hideThumbnailsUpdate: (dateAllSelected: Boolean) -> Unit,
var clickItem: (item: ResultPhotosFiles) -> Unit
var clickItem: (item: ResultDataFiles) -> Unit
) :
NewBaseAdapter<ResultPhotosFiles>(mContext) {
NewBaseAdapter<ResultDataFiles>(mContext) {
//日期组某一天的数据选择状态维护
val dateSelectedMap = mutableSetOf<ResultPhotosFiles>()
val dateSelectedMap = mutableSetOf<ResultDataFiles>()
companion object {
//音频或者文档
private const val TYPE_ONE = 1
//视频和图片支持布局切换 2/3/4列
private const val TYPE_TWO = 2
private const val TYPE_THREE = 3
private const val TYPE_FOUR = 4
private const val TYPE_AUDIO = 5
private const val TYPE_DOCUMENTS = 6
}
fun setAllSelected(isAdd: Boolean) {
@ -69,7 +72,11 @@ class PhotoDisplayDateChildAdapter(
override fun getItemViewType(position: Int): Int {
when (scanType) {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio -> {
return TYPE_ONE
return TYPE_AUDIO
}
Common.VALUE_SCAN_TYPE_documents, Common.VALUE_SCAN_TYPE_deleted_documents -> {
return TYPE_DOCUMENTS
}
else -> {
@ -110,8 +117,16 @@ class PhotoDisplayDateChildAdapter(
): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
TYPE_ONE -> OneHolder(
OneAudioDocumentsItemBinding.inflate(
TYPE_AUDIO -> AudioHolder(
OneAudioItemBinding.inflate(
inflater,
parent,
false
)
)
TYPE_DOCUMENTS -> DocumentsHolder(
OneDocumentsItemBinding.inflate(
inflater,
parent,
false
@ -139,32 +154,36 @@ class PhotoDisplayDateChildAdapter(
override fun onBind(
holder: RecyclerView.ViewHolder,
item: ResultPhotosFiles,
item: ResultDataFiles,
viewType: Int
) {
when (holder) {
is TwoHolder -> holder.vb.run {
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item, imageType)
}
is ThreeHolder -> holder.vb.run {
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item, imageType)
}
is OneHolder -> {
is AudioHolder -> {
item.run {
holder.vb.let {
it.textName.text = name
it.textDuration.text = Common.formatDuration(duration)
it.textSize.text = sizeString
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)
}
}
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)
}
@ -172,6 +191,28 @@ class PhotoDisplayDateChildAdapter(
}
}
}
is DocumentsHolder -> {
item.run {
holder.vb.let {
it.textName.text = name
it.textDate.text = Common.getItemMonthDay(lastModified)
it.textSize.text = sizeString
targetFile?.let { file->
it.imageIcon.setImageResource(Common.getFileIconRes(file))
}
initAudioDocuments(it.imageSelect,this)
it.constraintLayout.setOnClickListener {
clickItem(this)
}
}
}
}
}
}
@ -182,29 +223,45 @@ class PhotoDisplayDateChildAdapter(
class TwoHolder(val vb: FileSpanCountTwoAdapterBinding) :
RecyclerView.ViewHolder(vb.root)
class OneHolder(val vb: OneAudioDocumentsItemBinding) :
class AudioHolder(val vb: OneAudioItemBinding) :
RecyclerView.ViewHolder(vb.root)
class DocumentsHolder(val vb: OneDocumentsItemBinding) :
RecyclerView.ViewHolder(vb.root)
private fun initAudioDocuments(imageSelect: ImageView,item: ResultDataFiles){
viewModel.checkIsSelect(item).let { isSelected ->
imageSelect.isSelected = isSelected
addOrRemove(item, isSelected)
}
imageSelect.setOnClickListener {
it.isSelected = !it.isSelected
it.isSelected.let { newStatus ->
addOrRemove(item, newStatus)
}
}
}
private fun initDateView(
rootLayout: RelativeLayout,
imageSelectStatus: ImageView,
textSize: CustomTextView,
imageThumbnail: ImageView,
item: ResultPhotosFiles,
item: ResultDataFiles,
imageType: ImageView
) {
item.run {
viewModel.checkIsSelect(this).let {
imageSelectStatus.isSelected = it
addOrRemove(this, it)
}
imageSelectStatus.setOnClickListener {
it.isSelected = !it.isSelected
it.isSelected.let { newStatus ->
addOrRemove(this, newStatus)
}
}
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(
@ -254,13 +311,13 @@ class PhotoDisplayDateChildAdapter(
}
private fun addOrRemove(resultPhotosFiles: ResultPhotosFiles, boolean: Boolean) {
private fun addOrRemove(resultDataFiles: ResultDataFiles, boolean: Boolean) {
if (boolean) {
dateSelectedMap.add(resultPhotosFiles)
dateSelectedMap.add(resultDataFiles)
} else {
dateSelectedMap.remove(resultPhotosFiles)
dateSelectedMap.remove(resultDataFiles)
}
onSelectedUpdate.invoke(resultPhotosFiles, boolean, dateSelectedMap.size == data.size)
onSelectedUpdate.invoke(resultDataFiles, boolean, dateSelectedMap.size == data.size)
}

View File

@ -1,17 +1,13 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
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 androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
@ -23,6 +19,7 @@ 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
@ -46,15 +43,15 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
}
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var myData: ResultPhotosFiles? = null
private lateinit var player: ExoPlayer
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, ResultPhotosFiles::class.java)
intent.getParcelableExtra(KEY_CLICK_ITEM, ResultDataFiles::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(KEY_CLICK_ITEM)
@ -77,6 +74,9 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
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 {
@ -161,14 +161,8 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
layoutSeekbar.isVisible = true
imPlay.isVisible = true
frameImage.setBackgroundResource(R.drawable.bg_info_music_f2f2f7_8)
image.setImageResource(R.drawable.image_info_music)
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
loadCenterImage(image,R.drawable.image_info_music)
initPlayAudio()
layoutResolution.isVisible = false
@ -182,15 +176,29 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
layoutSize.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = false
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)

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.content.Intent
import android.text.Editable
@ -14,6 +14,7 @@ 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.ActivityPhotoSortingBinding
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
@ -97,21 +98,21 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private var filterLayoutPopupWindows: FilterPopupWindows? = null
private lateinit var sortBySizeBigToSmall: List<ResultPhotosFiles>
private lateinit var sortBySizeSmallToBig: List<ResultPhotosFiles>
private lateinit var sortByDateReverse: List<Pair<String, List<ResultPhotosFiles>>>
private lateinit var sortedByDatePositive: List<Pair<String, List<ResultPhotosFiles>>>
private lateinit var sortBySizeBigToSmall: List<ResultDataFiles>
private lateinit var sortBySizeSmallToBig: List<ResultDataFiles>
private lateinit var sortByDateReverse: List<Pair<String, List<ResultDataFiles>>>
private lateinit var sortedByDatePositive: List<Pair<String, List<ResultDataFiles>>>
//最新显示的数据集合(包含缩略图 ,只保存当前筛选后或者排序后显示的数据不受switch切换影响
private var currentDateList: List<Pair<String, List<ResultPhotosFiles>>>? = null
private var currentSizeList: List<ResultPhotosFiles>? = null
private var currentDateList: List<Pair<String, List<ResultDataFiles>>>? = null
private var currentSizeList: List<ResultDataFiles>? = null
//选中的所有数据集合(实际选中)
private lateinit var allSelectedSetList: Set<ResultPhotosFiles>
private lateinit var allSelectedSetList: Set<ResultDataFiles>
//选中的所有数据集合(筛选后的数据实际显示的所有选中)
private lateinit var filterSelectedSetList: Set<ResultPhotosFiles>
private lateinit var filterSelectedSetList: Set<ResultDataFiles>
private lateinit var mItemDecoration: GridSpacingItemDecoration
@ -125,7 +126,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
override fun initData() {
super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
val list: ArrayList<ResultPhotosFiles>? =
val list: ArrayList<ResultDataFiles>? =
intent.getParcelableArrayListExtraCompat(KEY_PHOTO_FOLDER_FILE)
mItemDecoration =
GridSpacingItemDecoration(columns, Common.itemSpacing, Common.horizontalSpacing)
@ -338,7 +339,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
is PhotoDisplayDateAdapter -> {
dateAdapter?.setAllSelected(it.isSelected)
dateAdapter?.getCurrentData()?.let {
it as List<Pair<String, List<ResultPhotosFiles>>>
it as List<Pair<String, List<ResultDataFiles>>>
if (it.size > 0)
Common.showLog("------------全选按钮 日期-${it.size} ${it[0].second[0].path}")
}
@ -348,7 +349,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
is PhotoDisplayDateChildAdapter -> {
sizeSortAdapter?.setAllSelected(it.isSelected)
sizeSortAdapter?.getCurrentData()?.let {
it as List<ResultPhotosFiles>
it as List<ResultDataFiles>
if (it.size > 0)
Common.showLog("------------全选按钮 大小-${it.size} ${it[0].path}")
}
@ -437,12 +438,12 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
private fun initGetCurrentSizeList(): List<ResultPhotosFiles> {
private fun initGetCurrentSizeList(): List<ResultDataFiles> {
currentSizeList = currentSizeList ?: currentDateList?.flatMap { it.second }
return currentSizeList!!
}
private fun resetCurrentSizeList(currentList: List<ResultPhotosFiles>) {
private fun resetCurrentSizeList(currentList: List<ResultDataFiles>) {
currentSizeList = currentList
currentDateList = null
@ -451,12 +452,12 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}
private fun initGetCurrentDateList(): List<Pair<String, List<ResultPhotosFiles>>> {
private fun initGetCurrentDateList(): List<Pair<String, List<ResultDataFiles>>> {
currentDateList = currentDateList ?: Common.getSortByDayNewToOldInit(currentSizeList!!)
return currentDateList!!
}
private fun resetCurrentDateList(currentList: List<Pair<String, List<ResultPhotosFiles>>>) {
private fun resetCurrentDateList(currentList: List<Pair<String, List<ResultDataFiles>>>) {
currentDateList = currentList
currentSizeList = null
val totalSelectedCount = currentList.sumOf { pair ->
@ -484,7 +485,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
val aPx = 16.dpToPx(context)
val bottom = 70.dpToPx(context)
when (scanType) {
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio,VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
layoutManager = LinearLayoutManager(context)
setPadding(aPx, 0, 0, bottom)
}
@ -713,8 +714,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
* 数据筛选或者缩略图切换显示后对比刷新实际显示的选中数据
*/
private fun checkRefreshDisPlaySelected(
list1: List<Pair<String, List<ResultPhotosFiles>>>? = null,
list2: List<ResultPhotosFiles>? = null
list1: List<Pair<String, List<ResultDataFiles>>>? = null,
list2: List<ResultDataFiles>? = null
) {
lifecycleScope.launch {
list1?.let {
@ -887,13 +888,13 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
//选中集合的更新
viewModel.afterDeleted()
deferredResults["sizeList"]?.let { list ->
list as List<ResultPhotosFiles>
list as List<ResultDataFiles>
Common.showLog("---------更新 sizeList = ${list.size}")
sizeSortAdapter?.setData(list)
resetCurrentSizeList(list)
}
deferredResults["dateList"]?.let { list ->
list as List<Pair<String, List<ResultPhotosFiles>>>
list as List<Pair<String, List<ResultDataFiles>>>
Common.showLog("---------更新 dateList = ${list.size}")
dateAdapter?.setData(list)
resetCurrentDateList(list)
@ -932,13 +933,13 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
//选中集合的更新
viewModel.afterSingleDeleted(deletedData)
deferredResults["sizeList"]?.let { list ->
list as List<ResultPhotosFiles>
list as List<ResultDataFiles>
Common.showLog("---------更新 sizeList = ${list.size}")
sizeSortAdapter?.setData(list)
resetCurrentSizeList(list)
}
deferredResults["dateList"]?.let { list ->
list as List<Pair<String, List<ResultPhotosFiles>>>
list as List<Pair<String, List<ResultDataFiles>>>
Common.showLog("---------更新 dateList = ${list.size}")
dateAdapter?.setData(list)
resetCurrentDateList(list)

View File

@ -1,9 +1,9 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.app.Activity
import androidx.fragment.app.FragmentManager
import com.ux.video.file.filerecovery.db.ObjectBoxManager
import com.ux.video.file.filerecovery.db.ResultDataFiles
import androidx.lifecycle.lifecycleScope
import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync
@ -18,9 +18,9 @@ object RecoverOrDeleteManager {
private var dialogConfirmDelete: ConfirmDeleteDialogFragment? = null
//详情页面进行删除操作的监听
private var onSingleDeletedCompleteListener: ((ResultPhotosFiles) -> Unit)? = null
private var onSingleDeletedCompleteListener: ((ResultDataFiles) -> Unit)? = null
fun setOnSingleDeleteCompleteListener(listener: (ResultPhotosFiles) -> Unit) {
fun setOnSingleDeleteCompleteListener(listener: (ResultDataFiles) -> Unit) {
onSingleDeletedCompleteListener = listener
}
@ -31,21 +31,10 @@ object RecoverOrDeleteManager {
fun showRecoveringDialog(
fragmentManager: FragmentManager,
scope: CoroutineScope,
selectedSetList: Set<ResultPhotosFiles>,
selectedSetList: Set<ResultDataFiles>,
onComplete: (number: Int) -> Unit
) {
scope.copySelectedFilesAsync(
selectedSet = selectedSetList,
folder = Common.recoveryPhotoDir,
onProgress = { currentCounts: Int, fileName: String, success: Boolean ->
ScanManager.showLog("--------恢复图片 ", "----------${currentCounts} ${fileName}")
dialogRecovering?.updateProgress(currentCounts)
}) { counts ->
dialogRecovering?.updateProgress(counts)
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}")
}
dialogRecovering = dialogRecovering ?: RecoveringDialogFragment()
dialogRecovering?.run {
total = selectedSetList.size
@ -55,6 +44,21 @@ object RecoverOrDeleteManager {
}
show(fragmentManager, "")
}
scope.copySelectedFilesAsync(
selectedSet = selectedSetList,
folder = Common.recoveryPhotoDir,
onProgress = { currentCounts: Int, data: ResultDataFiles, fileName: String, success: Boolean ->
if(success){
ScanManager.showLog("--------恢复图片 ", "----------${currentCounts} ${fileName}")
dialogRecovering?.updateProgress(currentCounts)
ObjectBoxManager.addRecoveryFile(data)
}
}) { counts ->
dialogRecovering?.updateProgress(counts)
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}")
}
}
/**
@ -64,7 +68,7 @@ object RecoverOrDeleteManager {
isInfoDelete: Boolean = false,
fragmentManager: FragmentManager,
scope: CoroutineScope,
selectedSetList: Set<ResultPhotosFiles>,
selectedSetList: Set<ResultDataFiles>,
onComplete: (number: Int) -> Unit
) {
dialogConfirmDelete = dialogConfirmDelete ?: ConfirmDeleteDialogFragment()
@ -86,18 +90,10 @@ object RecoverOrDeleteManager {
isInfoDelete: Boolean = false,
fragmentManager: FragmentManager,
scope: CoroutineScope,
selectedSetList: Set<ResultPhotosFiles>,
selectedSetList: Set<ResultDataFiles>,
onComplete: (number: Int) -> Unit
) {
scope.deleteFilesAsync(
selectedSet = selectedSetList,
onProgress = { currentCounts: Int, path: String, success: Boolean ->
ScanManager.showLog("--------删除图片 ", "----------${currentCounts} ${path}")
dialogDeleting?.updateProgress(currentCounts)
}) { counts ->
dialogDeleting?.updateProgress(counts)
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}")
}
dialogDeleting = dialogDeleting ?: DeletingDialogFragment()
dialogDeleting?.run {
total = selectedSetList.size
@ -110,5 +106,18 @@ object RecoverOrDeleteManager {
}
show(fragmentManager, "")
}
scope.deleteFilesAsync(
selectedSet = selectedSetList,
onProgress = { currentCounts: Int, data: ResultDataFiles, path: String, success: Boolean ->
if (success){
ScanManager.showLog("--------删除图片 ", "----------${currentCounts} ${path}")
dialogDeleting?.updateProgress(currentCounts)
ObjectBoxManager.deleteRecoveryFile(data)
}
}) { counts ->
dialogDeleting?.updateProgress(counts)
ScanManager.showLog("--------恢复图片 ", "----------恢复完成 ${counts}")
}
}
}

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseIngDialogFragment

View File

@ -1,4 +1,4 @@
package com.ux.video.file.filerecovery.photo
package com.ux.video.file.filerecovery.sort
import android.graphics.Color
import android.os.Bundle

View File

@ -9,14 +9,13 @@ import android.os.Environment
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.webkit.MimeTypeMap
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import com.ux.video.file.filerecovery.App
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.db.ResultDataFiles
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Date
import java.util.Locale
import kotlin.collections.sortedBy
@ -40,24 +39,25 @@ object Common {
val rootDir = Environment.getExternalStorageDirectory()
val dateFormat = SimpleDateFormat("MMMM d,yyyy", Locale.ENGLISH)
val chineseFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINESE)
val itemDateFormat = SimpleDateFormat("MM-dd", Locale.CHINESE)
val recoveryPhotoDir = "MyAllRecovery/Photo"
/**
* 默认按照日期分类将最新的排前面 降序
*/
fun getSortByDayNewToOldInit(list: List<ResultPhotosFiles>): List<Pair<String, List<ResultPhotosFiles>>> {
fun getSortByDayNewToOldInit(list: List<ResultDataFiles>): List<Pair<String, List<ResultDataFiles>>> {
val grouped = list.groupBy {
dateFormat.format(Date(it.lastModified))
}
val parentData: List<Pair<String, List<ResultPhotosFiles>>> = grouped
val parentData: List<Pair<String, List<ResultDataFiles>>> = grouped
.map { it.key to it.value }
.sortedByDescending { dateFormat.parse(it.first)?.time ?: 0L }
return parentData
}
fun getSortByDayNewToOld(
list: List<Pair<String, List<ResultPhotosFiles>>>
): List<Pair<String, List<ResultPhotosFiles>>> {
list: List<Pair<String, List<ResultDataFiles>>>
): List<Pair<String, List<ResultDataFiles>>> {
return list.sortedByDescending { pair ->
dateFormat.parse(pair.first)?.time ?: 0L
}
@ -68,28 +68,28 @@ object Common {
* 按照日期排序 时间最早的排前面 升序
*
*/
fun getSortByDayOldToNew(list: List<Pair<String, List<ResultPhotosFiles>>>) =
fun getSortByDayOldToNew(list: List<Pair<String, List<ResultDataFiles>>>) =
list.sortedBy { dateFormat.parse(it.first)?.time ?: 0L }
/**
* 按照文件大小排序将最大的排前面 降序
*/
fun getSortBySizeBigToSmall(list: List<ResultPhotosFiles>) = list.sortedByDescending {
fun getSortBySizeBigToSmall(list: List<ResultDataFiles>) = list.sortedByDescending {
it.size
}
/**
* 按照文件大小排序将最小的排前面 升序
*/
fun getSortBySizeSmallToBig(list: List<ResultPhotosFiles>) = list.sortedBy {
fun getSortBySizeSmallToBig(list: List<ResultDataFiles>) = list.sortedBy {
it.size
}
fun searchByName(
data: List<Pair<String, List<ResultPhotosFiles>>>,
data: List<Pair<String, List<ResultDataFiles>>>,
keyword: String
): List<Pair<String, List<ResultPhotosFiles>>> {
): List<Pair<String, List<ResultDataFiles>>> {
if (keyword.isBlank()) return data
return data.mapNotNull { (key, files) ->
@ -98,9 +98,9 @@ object Common {
}
}
fun searchByNameList(
list: List<ResultPhotosFiles>,
list: List<ResultDataFiles>,
keyword: String
): List<ResultPhotosFiles> {
): List<ResultDataFiles> {
if (keyword.isBlank()) return list
return list.filter { it.name.contains(keyword, ignoreCase = true) }
}
@ -135,8 +135,8 @@ object Common {
* @param months 筛选months月之内的数据
*/
fun filterWithinOneMonthByDay(
grouped: List<Pair<String, List<ResultPhotosFiles>>>, months: Int
): List<Pair<String, List<ResultPhotosFiles>>> {
grouped: List<Pair<String, List<ResultDataFiles>>>, months: Int
): List<Pair<String, List<ResultDataFiles>>> {
val today = Calendar.getInstance()
val oneMonthAgo = Calendar.getInstance().apply {
add(Calendar.MONTH, -months) // 1 个月前
@ -151,7 +151,7 @@ object Common {
/**
* @param months 筛选months月之内的数据
*/
fun filterWithinOneMonth(list: List<ResultPhotosFiles>, months: Int): List<ResultPhotosFiles> {
fun filterWithinOneMonth(list: List<ResultDataFiles>, months: Int): List<ResultDataFiles> {
val today = Calendar.getInstance()
val oneMonthAgo = Calendar.getInstance().apply {
add(Calendar.MONTH, -months)
@ -184,10 +184,10 @@ object Common {
* @return 显示的选中数量和选中集合
*/
fun checkSelectListContainDate(
list: List<Pair<String, List<ResultPhotosFiles>>>,
selected: Set<ResultPhotosFiles>
): Pair<Int, MutableSet<ResultPhotosFiles>> {
val currentSelected = mutableSetOf<ResultPhotosFiles>()
list: List<Pair<String, List<ResultDataFiles>>>,
selected: Set<ResultDataFiles>
): Pair<Int, MutableSet<ResultDataFiles>> {
val currentSelected = mutableSetOf<ResultDataFiles>()
val totalSelectedCount = list.sumOf { pair ->
pair.second.count {
@ -202,10 +202,10 @@ object Common {
}
suspend fun checkSelectListContainDateAsync(
list: List<Pair<String, List<ResultPhotosFiles>>>,
selected: Set<ResultPhotosFiles>
): Pair<Int, MutableSet<ResultPhotosFiles>> = withContext(Dispatchers.Default) {
val currentSelected = mutableSetOf<ResultPhotosFiles>()
list: List<Pair<String, List<ResultDataFiles>>>,
selected: Set<ResultDataFiles>
): Pair<Int, MutableSet<ResultDataFiles>> = withContext(Dispatchers.Default) {
val currentSelected = mutableSetOf<ResultDataFiles>()
var totalSelectedCount = 0
// 高效遍历外层 + 内层列表
@ -238,10 +238,10 @@ object Common {
suspend fun checkSelectListContainSize(
list: List<ResultPhotosFiles>,
selected: Set<ResultPhotosFiles>
): Pair<Int, MutableSet<ResultPhotosFiles>> = withContext(Dispatchers.Default) {
val currentSelected = mutableSetOf<ResultPhotosFiles>()
list: List<ResultDataFiles>,
selected: Set<ResultDataFiles>
): Pair<Int, MutableSet<ResultDataFiles>> = withContext(Dispatchers.Default) {
val currentSelected = mutableSetOf<ResultDataFiles>()
var totalSelectedCount = 0
// 高效遍历外层 + 内层列表
@ -259,8 +259,8 @@ object Common {
* 去掉缩略图的集合
*/
suspend fun filterThumbnailsAsync(
originalList: MutableList<Pair<String, List<ResultPhotosFiles>>>
): List<Pair<String, List<ResultPhotosFiles>>> = withContext(Dispatchers.Default) {
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()
@ -271,9 +271,9 @@ object Common {
fun removeSelectedFromList(
list: List<Pair<String, List<ResultPhotosFiles>>>,
selectedLiveData: Set<ResultPhotosFiles>
): List<Pair<String, List<ResultPhotosFiles>>> {
list: List<Pair<String, List<ResultDataFiles>>>,
selectedLiveData: Set<ResultDataFiles>
): List<Pair<String, List<ResultDataFiles>>> {
return list.mapNotNull { (key, files) ->
val filtered = files.filterNot { it in selectedLiveData }
if (filtered.isNotEmpty()) key to filtered else null
@ -281,9 +281,9 @@ object Common {
}
fun removeSelectedFromSizeList(
list: List<ResultPhotosFiles>,
selectedLiveData: Set<ResultPhotosFiles>
): List<ResultPhotosFiles> {
list: List<ResultDataFiles>,
selectedLiveData: Set<ResultDataFiles>
): List<ResultDataFiles> {
return list.filterNot { it in selectedLiveData }
}
@ -304,6 +304,7 @@ object Common {
}
@SuppressLint("DefaultLocale")
fun formatDuration(ms: Long): String {
val totalSeconds = ms / 1000
val hours = totalSeconds / 3600
@ -316,11 +317,66 @@ object Common {
String.format("%02d:%02d", minutes, seconds)
}
}
fun getFileMIME(file: File): String {
val ext = file.extension.lowercase()
val mimeTypeFromExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: return "unknown"
return mime
return when {
mime.startsWith("image/") -> "image"
mime.startsWith("video/") -> "video"
mime.startsWith("audio/") -> "audio"
mime.startsWith("text/") -> "document"
mime == "application/pdf" -> "document"
mime.startsWith("application/vnd.openxmlformats") -> "document"
mime.startsWith("application/ms") -> "document"
mime == "application/zip" ||
mime == "application/x-rar-compressed" ||
mime == "application/x-7z-compressed" -> "archive"
else -> "other"
}
}
val customMimeMap = mapOf(
"xapk" to "application/zip",
// 可以继续添加其他自定义扩展名
)
fun getMimeTypeParts(file: File): String {
val extension = file.extension.lowercase()
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)?:customMimeMap[extension]
showLog("-----------ext=$extension mimeType=${mimeType}")
return if (mimeType != null && mimeType.contains("/")) {
val parts = mimeType.split("/")
val mainType = parts[0]
val subType = parts.getOrNull(1)
"$mainType/$subType"
} else {
"unknown"
}
}
fun getFileIconRes(file: File): Int {
val ext = file.extension.lowercase()
return when (ext) {
"doc", "docx" -> R.drawable.icon_doc
"xls", "xlsx" -> R.drawable.icon_xls
"ppt", "pptx" -> R.drawable.icon_ppt
"pdf" -> R.drawable.icon_pdf
"txt" -> R.drawable.icon_txt
"apk", "xapk" -> R.drawable.icon_apk
else -> R.drawable.icon_unknow
}
}
fun getFormatDate(time: Long): String {
return dateFormat.format(Date(time))
}
fun getItemMonthDay(time: Long): String{
return itemDateFormat.format(time)
}
fun getChineseFormatDate(date: Date): String {
return chineseFormat.format(date)

View File

@ -7,7 +7,7 @@ import android.os.Build
import android.os.Parcelable
import android.util.TypedValue
import androidx.recyclerview.widget.RecyclerView
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.db.ResultDataFiles
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Date
@ -40,11 +40,11 @@ object ExtendFunctions {
}
fun List<ResultPhotosFiles>.filterWithinDateRangeList(
fun List<ResultDataFiles>.filterWithinDateRangeList(
months: Int = -1,
startDate: Date? = null,
endDate: Date? = null
): List<ResultPhotosFiles> {
): List<ResultDataFiles> {
val today = Calendar.getInstance()
@ -79,11 +79,11 @@ object ExtendFunctions {
fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinDateRange(
fun List<Pair<String, List<ResultDataFiles>>>.filterWithinDateRange(
months: Int = -1,
startDate: Date? = null,
endDate: Date? = null
): List<Pair<String, List<ResultPhotosFiles>>> {
): List<Pair<String, List<ResultDataFiles>>> {
val sdf = Common.dateFormat
val today = Calendar.getInstance()
@ -118,10 +118,10 @@ object ExtendFunctions {
/**
* 按文件大小筛选区间 [minSize, maxSize]
*/
fun List<ResultPhotosFiles>.filterBySizeList(
fun List<ResultDataFiles>.filterBySizeList(
minSize: Long,
maxSize: Long
): List<ResultPhotosFiles> {
): List<ResultDataFiles> {
if (minSize == -1L) return this
return this.filter { it.size in minSize..maxSize }
}
@ -129,10 +129,10 @@ object ExtendFunctions {
/**
* 按文件大小筛选区间 [minSize, maxSize]
*/
fun List<ResultPhotosFiles>.filterByDurationList(
fun List<ResultDataFiles>.filterByDurationList(
minSize: Long,
maxSize: Long
): List<ResultPhotosFiles> {
): List<ResultDataFiles> {
if (minSize == -1L) return this
return this.filter { it.duration in minSize..maxSize }
}
@ -140,10 +140,10 @@ object ExtendFunctions {
/**
* 分组数据按大小筛选 ,图片和文件筛选文件大小
*/
fun List<Pair<String, List<ResultPhotosFiles>>>.filterBySize(
fun List<Pair<String, List<ResultDataFiles>>>.filterBySize(
minSize: Long,
maxSize: Long
): List<Pair<String, List<ResultPhotosFiles>>> {
): List<Pair<String, List<ResultDataFiles>>> {
if (minSize == -1L) return this
return this.mapNotNull { (date, files) ->
val filtered = files.filter { it.size in minSize..maxSize }
@ -154,10 +154,10 @@ object ExtendFunctions {
/**
* 分组数据按大小筛选 ,音视频筛选时长
*/
fun List<Pair<String, List<ResultPhotosFiles>>>.filterByDuration(
fun List<Pair<String, List<ResultDataFiles>>>.filterByDuration(
minSize: Long,
maxSize: Long
): List<Pair<String, List<ResultPhotosFiles>>> {
): List<Pair<String, List<ResultDataFiles>>> {
if (minSize == -1L) return this
return this.mapNotNull { (date, files) ->
val filtered = files.filter { it.duration in minSize..maxSize }
@ -181,7 +181,7 @@ object ExtendFunctions {
/**
* 移除掉缩略图后的数据
*/
suspend fun List<Pair<String, List<ResultPhotosFiles>>>.filterThumbnailsAsync(): List<Pair<String, List<ResultPhotosFiles>>> =
suspend fun List<Pair<String, List<ResultDataFiles>>>.filterThumbnailsAsync(): List<Pair<String, List<ResultDataFiles>>> =
withContext(Dispatchers.Default) {
this@filterThumbnailsAsync.asSequence()
.mapNotNull { (key, files) ->
@ -195,7 +195,7 @@ object ExtendFunctions {
/**
* 移除掉缩略图后的数据
*/
suspend fun List<ResultPhotosFiles>.filterRemoveThumbnailsAsync(): List<ResultPhotosFiles> =
suspend fun List<ResultDataFiles>.filterRemoveThumbnailsAsync(): List<ResultDataFiles> =
withContext(Dispatchers.Default) {
this@filterRemoveThumbnailsAsync.asSequence()
.filter { !it.isThumbnail } // 去掉 isThumbnail = true 的项
@ -203,9 +203,9 @@ object ExtendFunctions {
}
fun List<Pair<String, List<ResultPhotosFiles>>>.removeItem(
target: ResultPhotosFiles
): List<Pair<String, List<ResultPhotosFiles>>> {
fun List<Pair<String, List<ResultDataFiles>>>.removeItem(
target: ResultDataFiles
): List<Pair<String, List<ResultDataFiles>>> {
return this.mapNotNull { (key, files) ->
val updatedFiles = files.filterNot { it == target }
if (updatedFiles.isNotEmpty()) key to updatedFiles else null

View File

@ -5,14 +5,11 @@ import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.OpenableColumns
import android.text.format.Formatter
import android.util.Log
import androidx.annotation.RequiresApi
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.result.ScanningActivity
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
@ -27,7 +24,6 @@ import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
@ -108,20 +104,21 @@ object ScanManager {
scanDocuments(root, depth = 0)
val map = result.map { (dir, files) ->
val resultPhotosFilesList = files.map { file ->
ResultPhotosFiles(
val resultDataFilesList = files.map { file ->
ResultDataFiles(
name = file.name,
path = file.absolutePath,
size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(
sizeString = Formatter.formatFileSize(
context,
file.length()
),
lastModified = file.lastModified(),
resolution = getResolution(type,file)
resolution = getResolution(type,file),
fileType = getFileType(type)
)
}
ResultPhotos(dir, ArrayList(resultPhotosFilesList))
ResultData(dir, ArrayList(resultDataFilesList))
}
emit(ScanState.Complete(ArrayList(map)))
}
@ -168,7 +165,7 @@ object ScanManager {
val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0
@RequiresApi(Build.VERSION_CODES.R)
suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) {
if (!dir.exists() || !dir.isDirectory) return
if (depth > maxDepth || fileCount >= maxFiles) return
@ -211,28 +208,40 @@ object ScanManager {
}
}
scanDir(root, depth = 0)
ScanManager.showLog("HiddenScan", " 3333")
val map = result.map { (dir, files) ->
val resultPhotosFilesList = files.map { file ->
ResultPhotosFiles(
val resultDataFilesList = files.map { file ->
ResultDataFiles(
name = file.name,
path = file.absolutePath,
size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(
sizeString = Formatter.formatFileSize(
context,
file.length()
),
lastModified = file.lastModified(),
resolution = getResolution(type,file)
resolution = getResolution(type,file),
fileType = getFileType(type)
)
}
ResultPhotos(dir, ArrayList(resultPhotosFilesList))
ResultData(dir, ArrayList(resultDataFilesList))
}
emit(ScanState.Complete(ArrayList(map)))
}
private fun getFileType(scanType: Int): Int {
return when (scanType) {
VALUE_SCAN_TYPE_deleted_photo -> 0
VALUE_SCAN_TYPE_deleted_video -> 1
VALUE_SCAN_TYPE_deleted_audio -> 2
else -> 3
}
}
private fun getFileSizeByMediaStore(context: Context, file: File): Long {
val uri = Uri.fromFile(file)
context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
@ -291,13 +300,14 @@ object ScanManager {
* @param folder "AllRecovery/Photo"
*/
fun CoroutineScope.copySelectedFilesAsync(
selectedSet: Set<ResultPhotosFiles>,
selectedSet: Set<ResultDataFiles>,
rootDir: File = Common.rootDir,
folder: String,
onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit,
onProgress: (currentCounts: Int, data:ResultDataFiles,fileName: String, success: Boolean) -> Unit,
onComplete: (currentCounts: Int) -> Unit
) {
launch(Dispatchers.IO) {
var recoveryCount = 0
val targetDir = File(rootDir, folder)
if (!targetDir.exists()) targetDir.mkdirs()
selectedSet.forEachIndexed { index, resultPhotosFiles ->
@ -312,17 +322,19 @@ object ScanManager {
}
}
success = true
recoveryCount++
withContext(Dispatchers.Main) {
onProgress(index + 1,resultPhotosFiles, srcFile.name, success)
}
} catch (e: Exception) {
e.printStackTrace()
}
withContext(Dispatchers.Main) {
onProgress(index + 1, srcFile.name, success)
}
}
}
withContext(Dispatchers.Main) {
onComplete(selectedSet.size)
onComplete(recoveryCount)
}
}
}
@ -333,8 +345,8 @@ object ScanManager {
*
*/
fun CoroutineScope.deleteFilesAsync(
selectedSet: Set<ResultPhotosFiles>,
onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit,
selectedSet: Set<ResultDataFiles>,
onProgress: (currentCounts: Int, data:ResultDataFiles,fileName: String, success: Boolean) -> Unit,
onComplete: (currentCounts: Int) -> Unit
) {
launch(Dispatchers.IO) {
@ -346,16 +358,20 @@ object ScanManager {
deletedCount++
}
withContext(Dispatchers.Main) {
onProgress(index + 1, file.name, true)
onProgress(index + 1, resultPhotosFiles,file.name, true)
}
} catch (e: Exception) {
onProgress(index + 1, resultPhotosFiles.path!!, false)
onProgress(index + 1,resultPhotosFiles, resultPhotosFiles.path!!, false)
}
}
withContext(Dispatchers.Main) {
onComplete(selectedSet.size)
onComplete(deletedCount)
}
}
}
}

View File

@ -1,13 +1,9 @@
package com.ux.video.file.filerecovery.utils
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import com.ux.video.file.filerecovery.db.ResultDataFiles
class ScanRepository : ViewModel() {
@ -20,17 +16,17 @@ class ScanRepository : ViewModel() {
private val _selectedLiveData = MutableLiveData<Set<ResultPhotosFiles>>(emptySet())
val selectedLiveData: LiveData<Set<ResultPhotosFiles>> = _selectedLiveData
private val _selectedLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedLiveData: LiveData<Set<ResultDataFiles>> = _selectedLiveData
// 当前筛选显示的选中项
private val _selectedDisplayLiveData = MutableLiveData<Set<ResultPhotosFiles>>(emptySet())
val selectedDisplayLiveData: LiveData<Set<ResultPhotosFiles>> = _selectedDisplayLiveData
private val _selectedDisplayLiveData = MutableLiveData<Set<ResultDataFiles>>(emptySet())
val selectedDisplayLiveData: LiveData<Set<ResultDataFiles>> = _selectedDisplayLiveData
fun toggleSelection(isAdd: Boolean, resultPhotosFiles: ResultPhotosFiles) {
fun toggleSelection(isAdd: Boolean, resultDataFiles: ResultDataFiles) {
val current = _selectedLiveData.value?.toMutableSet() ?: mutableSetOf()
val currentDisplay = _selectedDisplayLiveData.value?.toMutableSet() ?: mutableSetOf()
resultPhotosFiles.let {
resultDataFiles.let {
if (isAdd) {
current.add(it)
currentDisplay.add(it)
@ -52,7 +48,7 @@ class ScanRepository : ViewModel() {
/**
* 数据筛选后或者缩略图显示开关切换后 重置当前显示的选中集合
*/
fun filterResetDisplayFlow(list: MutableSet<ResultPhotosFiles>){
fun filterResetDisplayFlow(list: MutableSet<ResultDataFiles>){
_selectedDisplayLiveData.value = list.toSet()
Common.showLog( "筛选后重置 _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ")
@ -75,7 +71,7 @@ class ScanRepository : ViewModel() {
/**
* 详情页删除完毕移除删除掉的数据
*/
fun afterSingleDeleted(deletedItem:ResultPhotosFiles){
fun afterSingleDeleted(deletedItem:ResultDataFiles){
val selected = _selectedLiveData.value.orEmpty().toMutableSet()
val display = _selectedDisplayLiveData.value.orEmpty().toMutableSet()
@ -88,9 +84,9 @@ class ScanRepository : ViewModel() {
fun checkIsSelect(resultPhotosFiles: ResultPhotosFiles): Boolean {
fun checkIsSelect(resultDataFiles: ResultDataFiles): Boolean {
val current = _selectedLiveData.value
return current?.contains(resultPhotosFiles) == true
return current?.contains(resultDataFiles) == true
}

View File

@ -1,9 +1,9 @@
package com.ux.video.file.filerecovery.utils
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.db.ResultData
sealed class ScanState {
data class Progress(val scannedCount: Int,val filePath: String) : ScanState()
data class Complete(val result: ArrayList<ResultPhotos>) : ScanState()
data class Complete(val result: ArrayList<ResultData>) : ScanState()
}

View File

@ -3,27 +3,18 @@ package com.ux.video.file.filerecovery.video
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.widget.SeekBar
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
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.databinding.ActivityVideoPlayBinding
import com.ux.video.file.filerecovery.photo.PhotoInfoActivity
import com.ux.video.file.filerecovery.photo.PhotoInfoActivity.Companion.KEY_CLICK_ITEM
import com.ux.video.file.filerecovery.photo.RecoverOrDeleteManager
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.sort.RecoverOrDeleteManager
import com.ux.video.file.filerecovery.db.ResultDataFiles
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common
@ -34,7 +25,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
}
private lateinit var player: ExoPlayer
private var myData: ResultPhotosFiles? = null
private var myData: ResultDataFiles? = null
private val updateHandler = Handler(Looper.getMainLooper())
override fun inflateBinding(inflater: LayoutInflater): ActivityVideoPlayBinding =
ActivityVideoPlayBinding.inflate(inflater)
@ -48,7 +39,7 @@ class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
override fun initData() {
super.initData()
myData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(KEY_DATA, ResultPhotosFiles::class.java)
intent.getParcelableExtra(KEY_DATA, ResultDataFiles::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(KEY_DATA)

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/main_title" android:state_selected="true"/>
<item android:color="@color/main_sub_title" android:state_selected="false"/>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

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

View File

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

View File

@ -0,0 +1,53 @@
<?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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
tools:context=".recovery.RecoveryActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@color/white"
android:gravity="center_vertical">
<ImageView
android:id="@+id/image_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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/recovered_files"
android:textColor="@color/main_title"
android:textSize="16sp"
app:fontType="bold" />
</RelativeLayout>
<com.google.android.material.tabs.TabLayout
android:layout_width="match_parent"
android:layout_height="66dp"
app:tabIndicatorHeight="4dp"
app:tabMaxWidth="18dp"
app:tabMode="fixed"
app:tabGravity="fill"
android:id="@+id/tab_layout"
app:tabIndicatorColor="@color/color_title_blue"
app:tabIndicatorGravity="center"
app:tabBackground="@color/white"/>
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:id="@+id/view_page2"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,20 @@
<?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/recovery_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.recoveryphoto.RecoveryPhotoFragment">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RecoveryPhotoFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,78 @@
<?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/constraint_layout"
android:layout_width="match_parent"
android:layout_height="64dp">
<ImageView
android:id="@+id/image_select"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/selector_icon_checkmark_28dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:id="@+id/image_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/icon_apk"
app:layout_constraintStart_toEndOf="@id/image_select" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_name"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="10dp"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="1"
android:textColor="@color/main_title"
android:textSize="14sp"
app:fontType="bold"
app:layout_constraintBottom_toTopOf="@id/linear_duration"
app:layout_constraintLeft_toRightOf="@id/image_icon"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="aaaaaaaaaassssssssssssssssssssssssssssssssa" />
<LinearLayout
android:id="@+id/linear_duration"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="5dp"
android:gravity="top"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/text_name"
app:layout_constraintTop_toBottomOf="@id/text_name">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/main_sub_title"
android:textSize="11sp"
tools:text="aaaaaaaaaaa" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textColor="@color/main_sub_title"
android:textSize="11sp"
tools:text="aaaaaaaaaaa" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tab_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/selector_recovery_file_tab_layout_title"
android:textSize="16sp"
app:fontType="bold" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tab_item_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/main_sub_title"
android:textSize="14sp"
app:fontType="bold" />
</LinearLayout>

View File

@ -86,6 +86,8 @@ wait..</string>
<string name="view">View</string>
<string name="not_found">SorryNo %s found</string>
<string name="search">Search</string>
<string name="recovered_files">Recovered files</string>
<string name="text_counts">(%d)</string>
<string-array name="filter_date">

View File

@ -3,3 +3,9 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
}
buildscript {
dependencies {
classpath("io.objectbox:objectbox-gradle-plugin:4.0.3")
}
}

View File

@ -10,6 +10,9 @@ appcompat = "1.7.1"
material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
lifecycleLivedataKtx = "2.9.4"
lifecycleViewmodelKtx = "2.9.4"
fragmentKtx = "1.8.9"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -22,6 +25,9 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }