视频播放

This commit is contained in:
litingting 2025-10-22 18:35:22 +08:00
parent 587636c7a5
commit 063540eb58
45 changed files with 1629 additions and 526 deletions

View File

@ -55,4 +55,6 @@ dependencies {
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation ("com.google.android.material:material:1.13.0") implementation ("com.google.android.material:material:1.13.0")
implementation(project(":pickerview")) implementation(project(":pickerview"))
implementation ("androidx.media3:media3-exoplayer:1.8.0")
implementation ("androidx.media3:media3-ui:1.8.0")
} }

View File

@ -24,6 +24,9 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.FileRecovery" android:theme="@style/Theme.FileRecovery"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".video.VideoPlayActivity"
android:exported="false" />
<activity <activity
android:name=".photo.PhotoInfoActivity" android:name=".photo.PhotoInfoActivity"
android:exported="false" /> android:exported="false" />

View File

@ -20,13 +20,15 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
if(addPadding()){
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
}
insets insets
} }
initView() initView()
initData() initData()
} }
protected open fun addPadding() = true
protected abstract fun inflateBinding(inflater: LayoutInflater): VB protected abstract fun inflateBinding(inflater: LayoutInflater): VB
protected open fun initView() {} protected open fun initView() {}

View File

@ -17,6 +17,10 @@ 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.VALUE_SCAN_TYPE_video
import kotlin.properties.Delegates import kotlin.properties.Delegates
/**
* 选择扫描所有文件还是扫描删除过的文件
*/
class ScanSelectTypeActivity : BaseActivity<ActivityScanSelectTypeBinding>() { class ScanSelectTypeActivity : BaseActivity<ActivityScanSelectTypeBinding>() {
companion object { companion object {

View File

@ -23,6 +23,9 @@ import org.jaaksi.pickerview.picker.TimePicker.OnTimeSelectListener
import org.jaaksi.pickerview.widget.DefaultCenterDecoration import org.jaaksi.pickerview.widget.DefaultCenterDecoration
import java.util.Date import java.util.Date
/**
* 自定义日期筛选选择日期弹窗
*/
class DatePickerDialogFragment( class DatePickerDialogFragment(
var mContext: Context, var mContext: Context,
var title: String, var title: String,

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager 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.BaseAdapter
import com.ux.video.file.filerecovery.base.DiffBaseAdapter import com.ux.video.file.filerecovery.base.DiffBaseAdapter
import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding
@ -16,6 +17,7 @@ import com.ux.video.file.filerecovery.utils.ScanRepository
class PhotoDisplayDateAdapter( class PhotoDisplayDateAdapter(
mContext: Context, mContext: Context,
var scanType: Int,
var mColumns: Int, var mColumns: Int,
var viewModel: ScanRepository, var viewModel: ScanRepository,
var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, isAdd: Boolean) -> Unit, var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, isAdd: Boolean) -> Unit,
@ -34,7 +36,6 @@ class PhotoDisplayDateAdapter(
) )
/** /**
* 返回所有嵌套的数据量总数 * 返回所有嵌套的数据量总数
*/ */
@ -81,6 +82,7 @@ class PhotoDisplayDateAdapter(
val (date, files) = item val (date, files) = item
val childAdapter = PhotoDisplayDateChildAdapter( val childAdapter = PhotoDisplayDateChildAdapter(
mContext, mContext,
scanType,
mColumns, mColumns,
viewModel, viewModel,
{ resultPhotosFiles, addOrRemove, isDateAllSelected -> { resultPhotosFiles, addOrRemove, isDateAllSelected ->
@ -106,7 +108,15 @@ class PhotoDisplayDateAdapter(
textChildCounts.text = "(${files.size})" textChildCounts.text = "(${files.size})"
recyclerChild.apply { recyclerChild.apply {
layoutManager = GridLayoutManager(context, mColumns) layoutManager = when (scanType) {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio -> {
LinearLayoutManager(context)
}
else -> {
GridLayoutManager(context, mColumns)
}
}
adapter = childAdapter adapter = childAdapter
isNestedScrollingEnabled = false isNestedScrollingEnabled = false
} }

View File

@ -18,9 +18,11 @@ import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.ux.video.file.filerecovery.App import com.ux.video.file.filerecovery.App
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.NewBaseAdapter import com.ux.video.file.filerecovery.base.NewBaseAdapter
import com.ux.video.file.filerecovery.databinding.FileSpanCountThreeAdapterBinding import com.ux.video.file.filerecovery.databinding.FileSpanCountThreeAdapterBinding
import com.ux.video.file.filerecovery.databinding.FileSpanCountTwoAdapterBinding import com.ux.video.file.filerecovery.databinding.FileSpanCountTwoAdapterBinding
import com.ux.video.file.filerecovery.databinding.OneAudioDocumentsItemBinding
import com.ux.video.file.filerecovery.utils.Common import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.CustomTextView import com.ux.video.file.filerecovery.utils.CustomTextView
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
@ -29,6 +31,7 @@ import com.ux.video.file.filerecovery.utils.ScanRepository
class PhotoDisplayDateChildAdapter( class PhotoDisplayDateChildAdapter(
mContext: Context, mContext: Context,
var scanType: Int,
var mColumns: Int, var mColumns: Int,
var viewModel: ScanRepository, var viewModel: ScanRepository,
/** /**
@ -49,10 +52,13 @@ class PhotoDisplayDateChildAdapter(
companion object { companion object {
//音频或者文档
private const val TYPE_ONE = 1
private const val TYPE_TWO = 2 private const val TYPE_TWO = 2
private const val TYPE_THREE = 3 private const val TYPE_THREE = 3
private const val TYPE_FOUR = 4 private const val TYPE_FOUR = 4
} }
fun setAllSelected(isAdd: Boolean) { fun setAllSelected(isAdd: Boolean) {
data.forEach { data.forEach {
addOrRemove(it, isAdd) addOrRemove(it, isAdd)
@ -61,6 +67,12 @@ class PhotoDisplayDateChildAdapter(
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
when (scanType) {
Common.VALUE_SCAN_TYPE_audio, Common.VALUE_SCAN_TYPE_deleted_audio -> {
return TYPE_ONE
}
else -> {
return when (mColumns) { return when (mColumns) {
2 -> TYPE_TWO 2 -> TYPE_TWO
3 -> TYPE_THREE 3 -> TYPE_THREE
@ -68,6 +80,10 @@ class PhotoDisplayDateChildAdapter(
else -> TYPE_THREE else -> TYPE_THREE
} }
} }
}
}
fun setColumns(int: Int) { fun setColumns(int: Int) {
mColumns = int mColumns = int
@ -94,6 +110,14 @@ class PhotoDisplayDateChildAdapter(
): RecyclerView.ViewHolder { ): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (viewType) { return when (viewType) {
TYPE_ONE -> OneHolder(
OneAudioDocumentsItemBinding.inflate(
inflater,
parent,
false
)
)
TYPE_TWO -> TwoHolder( TYPE_TWO -> TwoHolder(
FileSpanCountTwoAdapterBinding.inflate( FileSpanCountTwoAdapterBinding.inflate(
inflater, inflater,
@ -121,13 +145,34 @@ class PhotoDisplayDateChildAdapter(
when (holder) { when (holder) {
is TwoHolder -> holder.vb.run { is TwoHolder -> holder.vb.run {
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item, imageType)
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item)
} }
is ThreeHolder -> holder.vb.run { is ThreeHolder -> holder.vb.run {
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item, imageType)
}
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item) is OneHolder -> {
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)
}
}
}
}
} }
} }
@ -139,15 +184,19 @@ class PhotoDisplayDateChildAdapter(
class TwoHolder(val vb: FileSpanCountTwoAdapterBinding) : class TwoHolder(val vb: FileSpanCountTwoAdapterBinding) :
RecyclerView.ViewHolder(vb.root) RecyclerView.ViewHolder(vb.root)
class OneHolder(val vb: OneAudioDocumentsItemBinding) :
RecyclerView.ViewHolder(vb.root)
private fun initDateView( private fun initDateView(
rootLayout: RelativeLayout, rootLayout: RelativeLayout,
imageSelectStatus: ImageView, imageSelectStatus: ImageView,
textSize: CustomTextView, textSize: CustomTextView,
imageThumbnail: ImageView, imageThumbnail: ImageView,
item: ResultPhotosFiles item: ResultPhotosFiles,
imageType: ImageView
) { ) {
item.run { item.run {
viewModel.checkIsSelect(this).let { viewModel.checkIsSelect(this).let {
imageSelectStatus.isSelected = it imageSelectStatus.isSelected = it
addOrRemove(this, it) addOrRemove(this, it)
@ -160,6 +209,13 @@ class PhotoDisplayDateChildAdapter(
} }
textSize.text = sizeString 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
else -> R.drawable.icon_type_photo
}
)
Glide.with(mContext) Glide.with(mContext)
.load(targetFile) .load(targetFile)
.apply( .apply(
@ -211,14 +267,4 @@ class PhotoDisplayDateChildAdapter(
} }
fun getVisibleCount(list: MutableList<ResultPhotosFiles> = data, hideThumbnails: Boolean): Int {
if(hideThumbnails){
return list.filter { !it.isThumbnail }.size
}else{
return list.size
}
}
} }

View File

@ -3,12 +3,8 @@ package com.ux.video.file.filerecovery.photo
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.activity.enableEdgeToEdge import androidx.core.view.isVisible
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
@ -21,12 +17,19 @@ import com.bumptech.glide.request.target.Target
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityPhotoInfoBinding import com.ux.video.file.filerecovery.databinding.ActivityPhotoInfoBinding
import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding
import com.ux.video.file.filerecovery.photo.PhotoSortingActivity
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common 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.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.video.VideoPlayActivity
class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() { class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
@ -34,6 +37,7 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
val KEY_CLICK_ITEM = "click_item" val KEY_CLICK_ITEM = "click_item"
} }
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var myData: ResultPhotosFiles? = null private var myData: ResultPhotosFiles? = null
override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoInfoBinding = override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoInfoBinding =
@ -45,8 +49,16 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
intent.getParcelableExtra(KEY_CLICK_ITEM, ResultPhotosFiles::class.java) intent.getParcelableExtra(KEY_CLICK_ITEM, ResultPhotosFiles::class.java)
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
intent.getParcelableExtra("MY_KEY") intent.getParcelableExtra(KEY_CLICK_ITEM)
} }
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
setView()
}
override fun initData() {
super.initData()
binding.run { binding.run {
imageViewBack.setOnClickListener { finish() } imageViewBack.setOnClickListener { finish() }
myData?.let { resultPhotosFiles -> myData?.let { resultPhotosFiles ->
@ -55,21 +67,16 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
tvPath.text = resultPhotosFiles.path tvPath.text = resultPhotosFiles.path
tvDate.text = Common.getFormatDate(resultPhotosFiles.lastModified) tvDate.text = Common.getFormatDate(resultPhotosFiles.lastModified)
tvResolution.text = resultPhotosFiles.resolution tvResolution.text = resultPhotosFiles.resolution
tvDuration.text = Common.formatDuration(resultPhotosFiles.duration)
Glide.with(this@PhotoInfoActivity) Glide.with(this@PhotoInfoActivity)
.load(resultPhotosFiles.targetFile) .load(resultPhotosFiles.targetFile)
.apply( .apply(RequestOptions().transform(CenterCrop(), RoundedCorners(8.dpToPx(this@PhotoInfoActivity))))
RequestOptions()
.transform(
CenterCrop(),
RoundedCorners(8.dpToPx(this@PhotoInfoActivity))
)
)
.listener(object : RequestListener<Drawable> { .listener(object : RequestListener<Drawable> {
override fun onLoadFailed( override fun onLoadFailed(
e: GlideException?, e: GlideException?,
model: Any?, model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable?>, target: Target<Drawable?>,
isFirstResource: Boolean isFirstResource: Boolean
): Boolean { ): Boolean {
return false return false
@ -92,7 +99,12 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
layoutBottom.tvLeft.run { layoutBottom.tvLeft.run {
text = resources.getString(R.string.delete) text = resources.getString(R.string.delete)
setOnClickListener { setOnClickListener {
RecoverOrDeleteManager.showConfirmDeleteDialog(true,supportFragmentManager,lifecycleScope,setOf(resultPhotosFiles)){count-> RecoverOrDeleteManager.showConfirmDeleteDialog(
true,
supportFragmentManager,
lifecycleScope,
setOf(resultPhotosFiles)
) { count ->
complete(count, 1) complete(count, 1)
} }
} }
@ -101,7 +113,11 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
layoutBottom.tvRight.run { layoutBottom.tvRight.run {
text = resources.getString(R.string.recover) text = resources.getString(R.string.recover)
setOnClickListener { setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog(supportFragmentManager,lifecycleScope,setOf(resultPhotosFiles)){count-> RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager,
lifecycleScope,
setOf(resultPhotosFiles)
) { count ->
complete(count, 0) complete(count, 0)
} }
} }
@ -110,6 +126,69 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
} }
} }
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
layoutType.isVisible = false
layoutSize.isVisible = false
layoutDuration.isVisible = false
imPlay.isVisible = false
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
layoutName.isVisible = true
layoutPath.isVisible = true
layoutResolution.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = true
layoutType.isVisible = false
layoutSize.isVisible = false
imPlay.isVisible = true
myData?.let { data->
frameImage.setOnClickListener {
startActivity(Intent(this@PhotoInfoActivity, VideoPlayActivity::class.java).apply {
putExtra(VideoPlayActivity.KEY_DATA, data)
})
}
}
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
layoutName.isVisible = true
layoutPath.isVisible = true
layoutSize.isVisible = true
layoutDate.isVisible = true
layoutDuration.isVisible = true
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
layoutDuration.isVisible = false
layoutDuration.isVisible = false
}
}
}
}
private fun complete(number: Int, type: Int) { private fun complete(number: Int, type: Int) {
finish() finish()
startActivity(Intent(this@PhotoInfoActivity, RecoverySuccessActivity::class.java).apply { startActivity(Intent(this@PhotoInfoActivity, RecoverySuccessActivity::class.java).apply {

View File

@ -4,6 +4,7 @@ import android.content.Intent
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -13,8 +14,19 @@ import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding
import com.ux.video.file.filerecovery.success.RecoverySuccessActivity import com.ux.video.file.filerecovery.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common 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.Common.setItemSelect
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterByDuration
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterByDurationList
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySize import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySize
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySizeList import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySizeList
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterRemoveThumbnailsAsync import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterRemoveThumbnailsAsync
@ -22,12 +34,11 @@ import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterThumbnailsAsyn
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRange 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.filterWithinDateRangeList
import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat 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.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.ExtendFunctions.removeItem
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync
import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync
import com.ux.video.file.filerecovery.utils.ScanRepository import com.ux.video.file.filerecovery.utils.ScanRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -57,6 +68,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
val SORT_DESC_DATE = 3 val SORT_DESC_DATE = 3
} }
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var sortDialogFragment: SortDialogFragment? = null private var sortDialogFragment: SortDialogFragment? = null
private var columns = 3 private var columns = 3
private var dateAdapter: PhotoDisplayDateAdapter? = null private var dateAdapter: PhotoDisplayDateAdapter? = null
@ -76,7 +89,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private var filterDate = FILTER_DATE_ALL private var filterDate = FILTER_DATE_ALL
//筛选大小,默认全部-1 //筛选大小,默认全部-1
private var filterSize = FILTER_SIZE_ALL private var filterSize: String = "All"
private var filterDatePopupWindows: DateFilterPopupWindows? = null private var filterDatePopupWindows: DateFilterPopupWindows? = null
private var filterStartDate: Date? = null private var filterStartDate: Date? = null
@ -103,6 +116,8 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private lateinit var mItemDecoration: GridSpacingItemDecoration private lateinit var mItemDecoration: GridSpacingItemDecoration
private lateinit var sizeFilterItemArray: Array<String>
private lateinit var viewModel: ScanRepository private lateinit var viewModel: ScanRepository
override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoSortingBinding = override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoSortingBinding =
@ -110,13 +125,12 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
override fun initData() { override fun initData() {
super.initData() super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
val list: ArrayList<ResultPhotosFiles>? = val list: ArrayList<ResultPhotosFiles>? =
intent.getParcelableArrayListExtraCompat(KEY_PHOTO_FOLDER_FILE) intent.getParcelableArrayListExtraCompat(KEY_PHOTO_FOLDER_FILE)
mItemDecoration = mItemDecoration =
GridSpacingItemDecoration(columns, Common.itemSpacing, Common.horizontalSpacing) GridSpacingItemDecoration(columns, Common.itemSpacing, Common.horizontalSpacing)
updateButtonCounts(0) updateButtonCounts(0)
viewModel = ViewModelProvider(this).get(ScanRepository::class.java) viewModel = ViewModelProvider(this).get(ScanRepository::class.java)
viewModel.selectedLiveData.observe(this) { selectedSet -> viewModel.selectedLiveData.observe(this) { selectedSet ->
@ -129,12 +143,11 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}") Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
updateCurrentIsAllSelectStatus() updateCurrentIsAllSelectStatus()
} }
setScanTypeView()
list?.let { list?.let {
binding.tvThumbnailCounts.text = binding.tvThumbnailCounts.text =
getString(R.string.hide_thumbnails, it.filter { it.isThumbnail }.size) getString(R.string.hide_thumbnails, it.filter { it.isThumbnail }.size)
//降序(最近的在前面) //降序(最近的在前面)
sortByDateReverse = Common.getSortByDayNewToOldInit(it) sortByDateReverse = Common.getSortByDayNewToOldInit(it)
//升序(时间最远的在前面) //升序(时间最远的在前面)
@ -144,6 +157,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
sizeSortAdapter = PhotoDisplayDateChildAdapter( sizeSortAdapter = PhotoDisplayDateChildAdapter(
this@PhotoSortingActivity, this@PhotoSortingActivity,
scanType,
columns, viewModel, columns, viewModel,
{ resultPhotosFiles, isAdd, allSelected -> { resultPhotosFiles, isAdd, allSelected ->
viewModel.toggleSelection(isAdd, resultPhotosFiles) viewModel.toggleSelection(isAdd, resultPhotosFiles)
@ -156,6 +170,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
this@PhotoSortingActivity, this@PhotoSortingActivity,
PhotoInfoActivity::class.java PhotoInfoActivity::class.java
).apply { ).apply {
putExtra(KEY_SCAN_TYPE,scanType)
putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item) putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item)
}) })
@ -163,6 +178,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
dateAdapter = dateAdapter =
PhotoDisplayDateAdapter( PhotoDisplayDateAdapter(
this@PhotoSortingActivity, this@PhotoSortingActivity,
scanType,
columns, columns,
viewModel, viewModel,
{ actionPath, isAdd -> { actionPath, isAdd ->
@ -173,6 +189,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
this@PhotoSortingActivity, this@PhotoSortingActivity,
PhotoInfoActivity::class.java PhotoInfoActivity::class.java
).apply { ).apply {
putExtra(KEY_SCAN_TYPE,scanType)
putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item) putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item)
}) })
}.apply { }.apply {
@ -182,6 +199,13 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
setDateAdapter() setDateAdapter()
setSingleDelete() setSingleDelete()
setFilter() setFilter()
setAllClick()
}
}
private fun setAllClick() {
binding.run { binding.run {
imageViewBack.setOnClickListener { finish() } imageViewBack.setOnClickListener { finish() }
switchHideThumbnails.setOnCheckedChangeListener { _, isChecked -> switchHideThumbnails.setOnCheckedChangeListener { _, isChecked ->
@ -220,7 +244,6 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
tvRecover.setOnClickListener { tvRecover.setOnClickListener {
// showRecoveringDialog()
RecoverOrDeleteManager.showRecoveringDialog( RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager, supportFragmentManager,
lifecycleScope, lifecycleScope,
@ -231,7 +254,6 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
tvDelete.setOnClickListener { tvDelete.setOnClickListener {
// showConfirmDeleteDialog()
RecoverOrDeleteManager.showConfirmDeleteDialog( RecoverOrDeleteManager.showConfirmDeleteDialog(
fragmentManager = supportFragmentManager, fragmentManager = supportFragmentManager,
scope = lifecycleScope, scope = lifecycleScope,
@ -318,12 +340,12 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
dateAdapter?.setAllSelected(it.isSelected) dateAdapter?.setAllSelected(it.isSelected)
dateAdapter?.getCurrentData()?.let { dateAdapter?.getCurrentData()?.let {
it as List<Pair<String, List<ResultPhotosFiles>>> it as List<Pair<String, List<ResultPhotosFiles>>>
if (it.size > 0) if (it.size > 0)
Common.showLog("------------全选按钮 日期-${it.size} ${it[0].second[0].path}") Common.showLog("------------全选按钮 日期-${it.size} ${it[0].second[0].path}")
} }
} }
is PhotoDisplayDateChildAdapter -> { is PhotoDisplayDateChildAdapter -> {
sizeSortAdapter?.setAllSelected(it.isSelected) sizeSortAdapter?.setAllSelected(it.isSelected)
sizeSortAdapter?.getCurrentData()?.let { sizeSortAdapter?.getCurrentData()?.let {
@ -335,11 +357,49 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
} }
} }
} }
/**
* 不同类型下的ui和功能点区分
*/
private fun setScanTypeView() {
binding.run {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
titleSize.text = getString(R.string.size)
filterLayoutLinearlayout.isVisible = true
relativeThumbnails.isVisible = true
sizeFilterItemArray = resources.getStringArray(R.array.filter_size_photo)
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
titleSize.text = getString(R.string.duration)
filterLayoutLinearlayout.isVisible = true
relativeThumbnails.isVisible = false
sizeFilterItemArray =
resources.getStringArray(R.array.filter_duration_video_audio)
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
titleSize.text = getString(R.string.duration)
filterLayoutLinearlayout.isVisible = false
relativeThumbnails.isVisible = false
sizeFilterItemArray =
resources.getStringArray(R.array.filter_duration_video_audio)
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
titleSize.text = getString(R.string.size)
filterLayoutLinearlayout.isVisible = false
relativeThumbnails.isVisible = false
sizeFilterItemArray = resources.getStringArray(R.array.filter_documents_size)
}
}
}
} }
private fun updateCurrentIsAllSelectStatus() { private fun updateCurrentIsAllSelectStatus() {
@ -498,19 +558,20 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
//大小筛选 //大小筛选
filterSizeLayout.setOnClickListener { filterSizeLayout.setOnClickListener {
setItemSelect(it as LinearLayout, true) setItemSelect(it as LinearLayout, true)
resources.getStringArray(R.array.filter_size).let { data -> sizeFilterItemArray.let { data ->
filterSizePopupWindows = filterSizePopupWindows ?: FilterPopupWindows( filterSizePopupWindows = filterSizePopupWindows ?: FilterPopupWindows(
this@PhotoSortingActivity, this@PhotoSortingActivity,
data, data,
0, 0,
{ clickValue -> { clickValue ->
titleSize.text = clickValue titleSize.text = clickValue
when (clickValue) { filterSize = clickValue
data[0] -> filterSize = FILTER_SIZE_ALL // when (clickValue) {
data[1] -> filterSize = FILTER_SIZE_1 // data[0] -> filterSize = clickValue
data[2] -> filterSize = FILTER_SIZE_5 // data[1] -> filterSize = FILTER_SIZE_1
data[3] -> filterSize = FILTER_SIZE_OVER_5 // data[2] -> filterSize = FILTER_SIZE_5
} // data[3] -> filterSize = FILTER_SIZE_OVER_5
// }
startFilter() startFilter()
}) { }) {
setItemSelect(it, false) setItemSelect(it, false)
@ -578,47 +639,60 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
/** /**
* 执行筛选结果 * 执行筛选结果 todo
*/ */
private fun startFilter() { private fun startFilter() {
Common.showLog("--------------开始筛选") Common.showLog("--------------开始筛选")
val filterSizeCovert = filterSizeCovert(scanType, filterSize)
when (binding.recyclerView.adapter) { when (binding.recyclerView.adapter) {
//当前是时间排序 //当前是时间排序
is PhotoDisplayDateAdapter -> { is PhotoDisplayDateAdapter -> {
//确定当前排序 //确定当前排序
val list = if (sortReverse) sortByDateReverse else sortedByDatePositive val list = if (sortReverse) sortByDateReverse else sortedByDatePositive
val filterSizeCovert = filterSizeCovert(filterSize)
list.filterWithinDateRange( list.filterWithinDateRange(
filterDate, filterDate,
startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null, startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null,
endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null
) ).run {
.filterBySize(filterSizeCovert.first, filterSizeCovert.second) when (scanType) {
.let { currentList -> VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
filterBySize(filterSizeCovert.first, filterSizeCovert.second)
}
else -> {
filterByDuration(filterSizeCovert.first, filterSizeCovert.second)
}
}
}.let { currentList ->
checkRefreshDisPlaySelected(list1 = currentList) checkRefreshDisPlaySelected(list1 = currentList)
dateAdapter?.resetAllValue(null) dateAdapter?.resetAllValue(null)
dateAdapter?.setData(currentList) dateAdapter?.setData(currentList)
resetCurrentDateList(currentList) resetCurrentDateList(currentList)
} }
} }
//当前是大小排序 //当前是大小排序
is PhotoDisplayDateChildAdapter -> { is PhotoDisplayDateChildAdapter -> {
val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig
val filterSizeCovert = filterSizeCovert(filterSize)
list.filterWithinDateRangeList( list.filterWithinDateRangeList(
filterDate, filterDate,
startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null, startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null,
endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null
) ).run {
.filterBySizeList(filterSizeCovert.first, filterSizeCovert.second) when (scanType) {
.let { currentList -> VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
filterBySizeList(filterSizeCovert.first, filterSizeCovert.second)
}
else -> {
filterByDurationList(filterSizeCovert.first, filterSizeCovert.second)
}
}
}.let { currentList ->
checkRefreshDisPlaySelected(list2 = currentList) checkRefreshDisPlaySelected(list2 = currentList)
sizeSortAdapter?.setData(currentList) sizeSortAdapter?.setData(currentList)
resetCurrentSizeList(currentList) resetCurrentSizeList(currentList)
} }
} }
} }
@ -651,15 +725,44 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
private fun filterSizeCovert(filterSize: Int): Pair<Long, Long> { private fun filterSizeCovert(scanType: Int, filterSize: String): Pair<Long, Long> {
when (scanType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
val stringArray = resources.getStringArray(R.array.filter_size_photo)
return when (filterSize) { return when (filterSize) {
FILTER_SIZE_ALL -> Pair(-1L, -1L) stringArray[0] -> Pair(-1L, -1L)
FILTER_SIZE_1 -> Pair(0L, 1.mbToBytes()) stringArray[1] -> Pair(0L, 1.mbToBytes())
FILTER_SIZE_5 -> Pair(1.mbToBytes(), 5.mbToBytes()) stringArray[2] -> Pair(1.mbToBytes(), 5.mbToBytes())
FILTER_SIZE_OVER_5 -> Pair(5.mbToBytes(), Long.MAX_VALUE) stringArray[3] -> Pair(5.mbToBytes(), Long.MAX_VALUE)
else -> Pair(-1L, -1L) else -> Pair(-1L, -1L)
} }
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video, VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
val stringArray = resources.getStringArray(R.array.filter_duration_video_audio)
return when (filterSize) {
stringArray[0] -> Pair(-1L, -1L)
stringArray[1] -> Pair(0L, 5.minutesToMillisecond())
stringArray[2] -> Pair(5.minutesToMillisecond(), 20.minutesToMillisecond())
stringArray[3] -> Pair(20.minutesToMillisecond(), 60.minutesToMillisecond())
stringArray[4] -> Pair(60.minutesToMillisecond(), Long.MAX_VALUE)
else -> Pair(-1L, -1L)
}
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
val stringArray = resources.getStringArray(R.array.filter_documents_size)
return when (filterSize) {
stringArray[0] -> Pair(-1L, -1L)
stringArray[1] -> Pair(0L, 500.kbToBytes())
stringArray[2] -> Pair(500.kbToBytes(), 1.mbToBytes())
stringArray[3] -> Pair(1.mbToBytes(), Long.MAX_VALUE)
else -> Pair(-1L, -1L)
}
}
}
return Pair(-1L, -1L)
} }
/** /**
@ -727,7 +830,6 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
/** /**
* 删除或者恢复完成 * 删除或者恢复完成
* @param type 0 恢复 1 删除 * @param type 0 恢复 1 删除

View File

@ -3,6 +3,7 @@ package com.ux.video.file.filerecovery.photo
import java.io.File import java.io.File
import android.os.Parcelable import android.os.Parcelable
import com.ux.video.file.filerecovery.utils.Common
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@ -29,4 +30,10 @@ data class ResultPhotosFiles(
} }
return false return false
} }
//音视频时长
val duration: Long
get() {
return Common.getMediaDuration(path.toString()) }
} }

View File

@ -2,16 +2,14 @@ package com.ux.video.file.filerecovery.result
import android.content.Intent import android.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityScanResultDisplayBinding import com.ux.video.file.filerecovery.databinding.ActivityScanResultDisplayBinding
import com.ux.video.file.filerecovery.photo.PhotoSortingActivity import com.ux.video.file.filerecovery.photo.PhotoSortingActivity
import com.ux.video.file.filerecovery.photo.ResultPhotos import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.result.ScanningActivity import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE 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_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_audio
@ -22,17 +20,16 @@ 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_photo
import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video 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.utils.ExtendFunctions.getParcelableArrayListExtraCompat
import com.ux.video.file.filerecovery.utils.ScanManager
import com.ux.video.file.filerecovery.utils.ScanRepository
/** /**
* 扫描结果汇总展示 * 扫描结果汇总展示
*/ */
class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>() { class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>() {
private var scanResultAdapter: ScanResultAdapter? = null
private var scanType: Int = VALUE_SCAN_TYPE_photo private var scanType: Int = VALUE_SCAN_TYPE_photo
private var exitDialog: ExitDialogFragment? = null private var exitDialog: ExitDialogFragment? = null
private var list: ArrayList<ResultPhotos>? = null
companion object { companion object {
val KEY_SCAN_RESULT = "scan_result" val KEY_SCAN_RESULT = "scan_result"
@ -43,50 +40,16 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
override fun initView() { override fun initView() {
super.initView() super.initView()
val list: ArrayList<ResultPhotos>? = list = intent.getParcelableArrayListExtraCompat(KEY_SCAN_RESULT)
intent.getParcelableArrayListExtraCompat(KEY_SCAN_RESULT) scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
scanResultAdapter = ScanResultAdapter(this@ScanResultDisplayActivity) { folderLists -> setSelectTypeTitle(scanType)
startActivity(
Intent(
this@ScanResultDisplayActivity,
PhotoSortingActivity::class.java
).apply {
putParcelableArrayListExtra(
PhotoSortingActivity.KEY_PHOTO_FOLDER_FILE,
folderLists
)
})
}
binding.recyclerResult.run {
adapter = scanResultAdapter
layoutManager = LinearLayoutManager(this@ScanResultDisplayActivity)
}
list?.let {
binding.run {
textDirCount.text = it.size.toString()
val sumOf = it.sumOf { it.allFiles.size }
textAllCounts.text = sumOf.toString()
}
scanResultAdapter?.setData(it)
}
// ScanRepository.instance.photoResults.observe(this@ScanResultDisplayActivity) {
// binding.run {
// textDirCount.text = it.size.toString()
// val sumOf = it.sumOf { it.allFiles.size }
// textAllCounts.text = sumOf.toString()
// }
// scanResultAdapter?.setData(it)
// }
} }
override fun initData() { override fun initData() {
super.initData() super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
setSelectTypeTitle(scanType)
binding.imageViewBack.setOnClickListener { dealExit() } binding.imageViewBack.setOnClickListener { dealExit() }
onBackPressedDispatcher.addCallback( onBackPressedDispatcher.addCallback(
this, this,
object : OnBackPressedCallback(true) { object : OnBackPressedCallback(true) {
@ -94,6 +57,46 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
dealExit() dealExit()
} }
}) })
binding.run {
val myAdapter = when (scanType) {
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
bottomLayout.setBackgroundResource(R.drawable.bg_rectangle_white_top_20)
ScanResultDocumentsAdapter(
this@ScanResultDisplayActivity,
scanType
) { folderLists ->
goSort(folderLists)
}
}
else -> {
bottomLayout.setBackgroundResource(0)
ScanResultPhotoAdapter(
this@ScanResultDisplayActivity,
scanType
) { folderLists ->
goSort(folderLists)
}
}
}.apply {
list?.let {
textDirCount.text = it.size.toString()
val sumOf = it.sumOf { it.allFiles.size }
textAllCounts.text = sumOf.toString()
setData(it)
}
}
recyclerResult.run {
adapter = myAdapter
layoutManager = LinearLayoutManager(this@ScanResultDisplayActivity)
}
}
} }
@ -103,27 +106,47 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
} }
exitDialog?.show(supportFragmentManager, "") exitDialog?.show(supportFragmentManager, "")
} }
private fun setSelectTypeTitle(fileType: Int) { private fun setSelectTypeTitle(fileType: Int) {
binding.run { binding.run {
when (fileType) { when (fileType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> { VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
title.text = getString(R.string.photo_title) title.text = getString(R.string.photo_title)
textFileType.text = getString(R.string.text_photos)
} }
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> { VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
title.text = getString(R.string.video_title) title.text = getString(R.string.video_title)
textFileType.text = getString(R.string.text_videos)
} }
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> { VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
title.text = getString(R.string.audio_title) title.text = getString(R.string.audio_title)
textFileType.text = getString(R.string.text_audios)
} }
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> { VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
title.text = getString(R.string.document_title) title.text = getString(R.string.document_title)
textFileType.text = getString(R.string.text_documents)
} }
} }
} }
} }
private fun goSort(list: ArrayList<ResultPhotosFiles>) {
startActivity(
Intent(
this@ScanResultDisplayActivity,
PhotoSortingActivity::class.java
).apply {
putExtra(KEY_SCAN_TYPE, scanType)
putParcelableArrayListExtra(
PhotoSortingActivity.KEY_PHOTO_FOLDER_FILE,
list
)
})
}
} }

View File

@ -0,0 +1,65 @@
package com.ux.video.file.filerecovery.result
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.utils.Common
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import java.io.File
/**
* 文件或者音频的扫描结果汇总适配器
*/
class ScanResultDocumentsAdapter(
mContext: Context,
var type: Int,
var onClickItem: (allFiles: ArrayList<ResultPhotosFiles>) -> Unit
) :
BaseAdapter<ResultPhotos, ScanResultDocumentsAdapterBinding>(mContext) {
override fun getViewBinding(parent: ViewGroup): ScanResultDocumentsAdapterBinding =
ScanResultDocumentsAdapterBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
@SuppressLint("SetTextI18n")
override fun bindItem(
holder: VHolder<ScanResultDocumentsAdapterBinding>,
item: ResultPhotos
) {
holder.vb.run {
item.run {
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->{
icon.setImageResource(R.drawable.icon_folder_audio)
}
Common.VALUE_SCAN_TYPE_documents, Common.VALUE_SCAN_TYPE_deleted_documents->{
icon.setImageResource(R.drawable.icon_folder_documents)
}
}
}
}
}
}

View File

@ -16,8 +16,9 @@ import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import java.io.File import java.io.File
class ScanResultAdapter( class ScanResultPhotoAdapter(
mContext: Context, mContext: Context,
var type: Int,
var onClickItem: (allFiles: ArrayList<ResultPhotosFiles>) -> Unit var onClickItem: (allFiles: ArrayList<ResultPhotosFiles>) -> Unit
) : ) :
BaseAdapter<ResultPhotos, ScanResultAdapterBinding>(mContext) { BaseAdapter<ResultPhotos, ScanResultAdapterBinding>(mContext) {

View File

@ -1,8 +1,11 @@
package com.ux.video.file.filerecovery.result package com.ux.video.file.filerecovery.result
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Environment import android.os.Environment
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity import com.ux.video.file.filerecovery.base.BaseActivity
@ -26,20 +29,10 @@ import com.ux.video.file.filerecovery.utils.ScanState
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.lifecycle.repeatOnLifecycle
class ScanningActivity : BaseActivity<ActivityScanningBinding>() { class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
companion object {
// 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
}
private var scanType: Int = VALUE_SCAN_TYPE_photo private var scanType: Int = VALUE_SCAN_TYPE_photo
override fun inflateBinding(inflater: LayoutInflater): ActivityScanningBinding = override fun inflateBinding(inflater: LayoutInflater): ActivityScanningBinding =
@ -53,7 +46,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_documents -> scanAll() 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() VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_deleted_video, VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_deleted_documents -> scanDeleted()
} }
binding.scanProgress.setCenterImage(R.drawable.im_photo_center_image)
binding.imageViewBack.setOnClickListener { finish() } binding.imageViewBack.setOnClickListener { finish() }
} }
@ -65,41 +58,49 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
VALUE_SCAN_TYPE_photo -> { VALUE_SCAN_TYPE_photo -> {
title.text = getString(R.string.photo_title) title.text = getString(R.string.photo_title)
tvScanDescribe.text = getString(R.string.describe_photos) tvScanDescribe.text = getString(R.string.describe_photos)
scanProgress.setCenterImage(R.drawable.im_photo_center_image)
} }
VALUE_SCAN_TYPE_deleted_photo -> { VALUE_SCAN_TYPE_deleted_photo -> {
title.text = getString(R.string.photo_title) title.text = getString(R.string.photo_title)
tvScanDescribe.text = getString(R.string.describe_delete_photos) tvScanDescribe.text = getString(R.string.describe_delete_photos)
scanProgress.setCenterImage(R.drawable.im_photo_center_image)
} }
VALUE_SCAN_TYPE_video -> { VALUE_SCAN_TYPE_video -> {
title.text = getString(R.string.video_title) title.text = getString(R.string.video_title)
tvScanDescribe.text = getString(R.string.describe_videos) tvScanDescribe.text = getString(R.string.describe_videos)
scanProgress.setCenterImage(R.drawable.im_video_center_image)
} }
VALUE_SCAN_TYPE_deleted_video -> { VALUE_SCAN_TYPE_deleted_video -> {
title.text = getString(R.string.video_title) title.text = getString(R.string.video_title)
tvScanDescribe.text = getString(R.string.describe_delete_videos) tvScanDescribe.text = getString(R.string.describe_delete_videos)
scanProgress.setCenterImage(R.drawable.im_video_center_image)
} }
VALUE_SCAN_TYPE_audio -> { VALUE_SCAN_TYPE_audio -> {
title.text = getString(R.string.audio_title) title.text = getString(R.string.audio_title)
tvScanDescribe.text = getString(R.string.describe_audios) tvScanDescribe.text = getString(R.string.describe_audios)
scanProgress.setCenterImage(R.drawable.im_audio_center_image)
} }
VALUE_SCAN_TYPE_deleted_audio -> { VALUE_SCAN_TYPE_deleted_audio -> {
title.text = getString(R.string.audio_title) title.text = getString(R.string.audio_title)
tvScanDescribe.text = getString(R.string.describe_delete_audios) tvScanDescribe.text = getString(R.string.describe_delete_audios)
scanProgress.setCenterImage(R.drawable.im_audio_center_image)
} }
VALUE_SCAN_TYPE_documents -> { VALUE_SCAN_TYPE_documents -> {
title.text = getString(R.string.document_title) title.text = getString(R.string.document_title)
tvScanDescribe.text = getString(R.string.describe_documents) tvScanDescribe.text = getString(R.string.describe_documents)
scanProgress.setCenterImage(R.drawable.im_documents_center_image)
} }
VALUE_SCAN_TYPE_deleted_documents -> { VALUE_SCAN_TYPE_deleted_documents -> {
title.text = getString(R.string.document_title) title.text = getString(R.string.document_title)
tvScanDescribe.text = getString(R.string.describe_delete_documents) tvScanDescribe.text = getString(R.string.describe_delete_documents)
scanProgress.setCenterImage(R.drawable.im_documents_center_image)
} }
} }
} }
@ -110,8 +111,10 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
private fun scanAll() { private fun scanAll() {
val total = 800 val total = 800
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
val root = Environment.getExternalStorageDirectory() val root = Environment.getExternalStorageDirectory()
ScanManager.scanAllDocuments(this@ScanningActivity,root, type = scanType).flowOn(Dispatchers.IO).collect { ScanManager.scanAllDocuments(this@ScanningActivity, root, type = scanType)
.flowOn(Dispatchers.IO).collect {
when (it) { when (it) {
is ScanState.Progress -> { is ScanState.Progress -> {
updateProgress(it) updateProgress(it)
@ -122,6 +125,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
} }
} }
} }
}
} }
@ -129,9 +133,10 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
private fun scanDeleted() { private fun scanDeleted() {
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
val root = Environment.getExternalStorageDirectory() val root = Environment.getExternalStorageDirectory()
ScanManager.scanHiddenPhotoAsync(this@ScanningActivity,root, type = scanType).flowOn(Dispatchers.IO).collect { ScanManager.scanHiddenPhotoAsync(this@ScanningActivity, root, type = scanType)
.flowOn(Dispatchers.IO).collect {
when (it) { when (it) {
is ScanState.Progress -> { is ScanState.Progress -> {
updateProgress(it) updateProgress(it)
@ -147,6 +152,8 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
} }
}
private fun updateProgress(scanState: ScanState.Progress) { private fun updateProgress(scanState: ScanState.Progress) {
val total = 1000 val total = 1000
@ -166,27 +173,32 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
} }
@SuppressLint("SetTextI18n")
private fun updateComplete(scanState: ScanState.Complete) { private fun updateComplete(scanState: ScanState.Complete) {
binding.scanProgress.setProgress(100) binding.run {
scanProgress.setProgress(100)
scanState.let { scanState.let {
startActivity( val size = it.result.size
Intent( if (size == 0) {
this@ScanningActivity, tvScanDescribe.text.let {
ScanResultDisplayActivity::class.java tvEmptyTypeFile.text = "0 $it"
).apply { tvSorry.text = getString(R.string.not_found,it)
}
relativeScanFinishedEmpty.isVisible = true
linearCounts.isVisible = false
}else{
finish()
startActivity(Intent(this@ScanningActivity, ScanResultDisplayActivity::class.java).apply {
putParcelableArrayListExtra( putParcelableArrayListExtra(
ScanResultDisplayActivity.KEY_SCAN_RESULT, ScanResultDisplayActivity.KEY_SCAN_RESULT,
it.result it.result
) )
putExtra(KEY_SCAN_TYPE, scanType)
}) })
ScanManager.showLog(
"HiddenScan",
"完成: ${it.result.size}"
)
} }
finish() ScanManager.showLog("HiddenScan", "完成: ${it.result.size}")
}
}
} }
} }

View File

@ -1,8 +1,10 @@
package com.ux.video.file.filerecovery.utils package com.ux.video.file.filerecovery.utils
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.icu.text.SimpleDateFormat import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar import android.icu.util.Calendar
import android.media.MediaMetadataRetriever
import android.os.Environment import android.os.Environment
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@ -265,6 +267,36 @@ object Common {
} }
fun getMediaDuration(filePath: String): Long {
val retriever = MediaMetadataRetriever()
return try {
retriever.setDataSource(filePath)
val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
durationStr?.toLongOrNull() ?: 0L // 单位:毫秒
} catch (e: Exception) {
e.printStackTrace()
0L
} finally {
retriever.release()
}
}
fun formatDuration(ms: Long): String {
val totalSeconds = ms / 1000
val hours = totalSeconds / 3600
val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
return if (hours > 0) {
String.format("%02d:%02d:%02d", hours, minutes, seconds)
} else {
String.format("%02d:%02d", minutes, seconds)
}
}
fun getFormatDate(time: Long): String { fun getFormatDate(time: Long): String {
return dateFormat.format(Date(time)) return dateFormat.format(Date(time))
} }

View File

@ -40,21 +40,6 @@ object ExtendFunctions {
} }
/**
* 按时间筛选最近 N 个月
*/
// fun List<ResultPhotosFiles>.filterWithinMonthsList(months: Int): List<ResultPhotosFiles> {
// if (months == -1) return this
// val today = Calendar.getInstance()
// val monthsAgo = Calendar.getInstance().apply {
// add(Calendar.MONTH, -months)
// }
// return this.filter {
// val cal = Calendar.getInstance().apply { timeInMillis = it.lastModified }
// !cal.before(monthsAgo) && !cal.after(today)
// }
// }
fun List<ResultPhotosFiles>.filterWithinDateRangeList( fun List<ResultPhotosFiles>.filterWithinDateRangeList(
months: Int = -1, months: Int = -1,
startDate: Date? = null, startDate: Date? = null,
@ -64,10 +49,9 @@ object ExtendFunctions {
val today = Calendar.getInstance() val today = Calendar.getInstance()
return when { return when {
// ✅ 1. -1 表示不过滤,返回全部
months == -1 -> this months == -1 -> this
// ✅ 2. 0 表示仅根据 startDate / endDate 筛选
months == 0 -> this.filter { file -> months == 0 -> this.filter { file ->
val date = Date(file.lastModified) val date = Date(file.lastModified)
when { when {
@ -78,7 +62,6 @@ object ExtendFunctions {
} }
} }
// ✅ 3. 其他情况:按“最近 N 个月”筛选
else -> { else -> {
val monthsAgo = Calendar.getInstance().apply { val monthsAgo = Calendar.getInstance().apply {
add(Calendar.MONTH, -months) add(Calendar.MONTH, -months)
@ -95,32 +78,7 @@ object ExtendFunctions {
/**
* 按文件大小筛选区间 [minSize, maxSize]
*/
fun List<ResultPhotosFiles>.filterBySizeList(
minSize: Long,
maxSize: Long
): List<ResultPhotosFiles> {
if (minSize == -1L) return this
return this.filter { it.size in minSize..maxSize }
}
/**
* 按时间筛选最近 N 个月
*/
// fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinMonths(months: Int): List<Pair<String, List<ResultPhotosFiles>>> {
// if (months == -1) return this
// val sdf = Common.dateFormat
// val today = Calendar.getInstance()
// val monthsAgo = Calendar.getInstance().apply {
// add(Calendar.MONTH, -months)
// }
// return this.filter { (dayStr, _) ->
// val day = sdf.parse(dayStr)
// day != null && !day.before(monthsAgo.time) && !day.after(today.time)
// }
// }
fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinDateRange( fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinDateRange(
months: Int = -1, months: Int = -1,
startDate: Date? = null, startDate: Date? = null,
@ -136,10 +94,10 @@ object ExtendFunctions {
} }
return when { return when {
// -1 表示不过滤,返回全部
months == -1 -> this months == -1 -> this
// 0 表示只用日期范围过滤
months == 0 -> this.filter { (dayStr, _) -> months == 0 -> this.filter { (dayStr, _) ->
val day = sdf.parse(dayStr) ?: return@filter false val day = sdf.parse(dayStr) ?: return@filter false
when { when {
@ -150,22 +108,37 @@ object ExtendFunctions {
} }
} }
// 其他情况:按“最近 N 个月”过滤
else -> this.filter { (dayStr, _) -> else -> this.filter { (dayStr, _) ->
val day = sdf.parse(dayStr) ?: return@filter false val day = sdf.parse(dayStr) ?: return@filter false
!day.before(monthsAgo.time) && !day.after(today.time) !day.before(monthsAgo.time) && !day.after(today.time)
} }
} }
} }
/**
* 按文件大小筛选区间 [minSize, maxSize]
*/
fun List<ResultPhotosFiles>.filterBySizeList(
minSize: Long,
maxSize: Long
): List<ResultPhotosFiles> {
if (minSize == -1L) return this
return this.filter { it.size in minSize..maxSize }
}
/** /**
* 分组数据按大小筛选 * 按文件大小筛选区间 [minSize, maxSize]
*/
fun List<ResultPhotosFiles>.filterByDurationList(
minSize: Long,
maxSize: Long
): List<ResultPhotosFiles> {
if (minSize == -1L) return this
return this.filter { it.duration in minSize..maxSize }
}
/**
* 分组数据按大小筛选 ,图片和文件筛选文件大小
*/ */
fun List<Pair<String, List<ResultPhotosFiles>>>.filterBySize( fun List<Pair<String, List<ResultPhotosFiles>>>.filterBySize(
minSize: Long, minSize: Long,
@ -177,10 +150,34 @@ object ExtendFunctions {
if (filtered.isNotEmpty()) date to filtered else null if (filtered.isNotEmpty()) date to filtered else null
} }
} }
/**
* 分组数据按大小筛选 ,音视频筛选时长
*/
fun List<Pair<String, List<ResultPhotosFiles>>>.filterByDuration(
minSize: Long,
maxSize: Long
): List<Pair<String, List<ResultPhotosFiles>>> {
if (minSize == -1L) return this
return this.mapNotNull { (date, files) ->
val filtered = files.filter { it.duration in minSize..maxSize }
if (filtered.isNotEmpty()) date to filtered else null
}
}
fun Int.mbToBytes(): Long { fun Int.mbToBytes(): Long {
return this * 1000L * 1000L return this * 1000L * 1000L
} }
fun Int.kbToBytes(): Long {
return this * 1000L
}
fun Int.minutesToMillisecond(): Long {
return this * 60 * 1000L
}
/** /**
* 移除掉缩略图后的数据 * 移除掉缩略图后的数据
*/ */

View File

@ -3,6 +3,7 @@ package com.ux.video.file.filerecovery.utils
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
@ -22,6 +23,8 @@ 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.VALUE_SCAN_TYPE_video
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@ -63,9 +66,12 @@ object ScanManager {
val result = mutableMapOf<String, MutableList<File>>() val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0 var fileCount = 0
suspend fun scanDocuments(dir: File, depth: Int) { suspend fun scanDocuments(dir: File, depth: Int) {
val context = currentCoroutineContext()
if (!dir.exists() || !dir.isDirectory) return if (!dir.exists() || !dir.isDirectory) return
if (depth > maxDepth || fileCount >= maxFiles) return if (depth > maxDepth || fileCount >= maxFiles) return
dir.listFiles()?.forEach { file -> dir.listFiles()?.forEach { file ->
context.ensureActive()
if (file.isDirectory) { if (file.isDirectory) {
scanDocuments(file, depth + 1) scanDocuments(file, depth + 1)
} else { } else {
@ -107,11 +113,12 @@ object ScanManager {
name = file.name, name = file.name,
path = file.absolutePath, path = file.absolutePath,
size = file.length(), size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()), sizeString = android.text.format.Formatter.formatFileSize(
context,
file.length()
),
lastModified = file.lastModified(), lastModified = file.lastModified(),
resolution = getImageSize(file).run { resolution = getResolution(type,file)
"$first*$second"
}
) )
} }
ResultPhotos(dir, ArrayList(resultPhotosFilesList)) ResultPhotos(dir, ArrayList(resultPhotosFilesList))
@ -128,6 +135,25 @@ object ScanManager {
return Pair(width, height) return Pair(width, height)
} }
fun getVideoResolution(filePath: String): Pair<Int, Int> {
val retriever = MediaMetadataRetriever()
return try {
retriever.setDataSource(filePath)
val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
?.toIntOrNull() ?: 0
val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
?.toIntOrNull() ?: 0
width to height
} catch (e: Exception) {
e.printStackTrace()
0 to 0
} finally {
retriever.release()
}
}
/** /**
* 递归扫描隐藏目录下的有效图片(删除的图片) * 递归扫描隐藏目录下的有效图片(删除的图片)
* @param maxDepth // 最大递归深度 * @param maxDepth // 最大递归深度
@ -141,6 +167,7 @@ object ScanManager {
val result = mutableMapOf<String, MutableList<File>>() val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0 var fileCount = 0
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) { suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) {
if (!dir.exists() || !dir.isDirectory) return if (!dir.exists() || !dir.isDirectory) return
@ -192,11 +219,12 @@ object ScanManager {
name = file.name, name = file.name,
path = file.absolutePath, path = file.absolutePath,
size = file.length(), size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()), sizeString = android.text.format.Formatter.formatFileSize(
context,
file.length()
),
lastModified = file.lastModified(), lastModified = file.lastModified(),
resolution = getImageSize(file).run { resolution = getResolution(type,file)
"$first*$second"
}
) )
} }
@ -217,6 +245,22 @@ object ScanManager {
return file.length() // fallback 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 isFormatFile(file: File, types: List<String>): Boolean { private fun isFormatFile(file: File, types: List<String>): Boolean {
val ext = file.extension.lowercase() val ext = file.extension.lowercase()
return types.contains(ext) return types.contains(ext)

View File

@ -0,0 +1,183 @@
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.success.RecoverySuccessActivity
import com.ux.video.file.filerecovery.utils.Common
class VideoPlayActivity : BaseActivity<ActivityVideoPlayBinding>() {
companion object {
val KEY_DATA = "key_data"
}
private lateinit var player: ExoPlayer
private var myData: ResultPhotosFiles? = null
private val updateHandler = Handler(Looper.getMainLooper())
override fun inflateBinding(inflater: LayoutInflater): ActivityVideoPlayBinding =
ActivityVideoPlayBinding.inflate(inflater)
override fun initView() {
super.initView()
}
override fun addPadding(): Boolean = false
override fun initData() {
super.initData()
myData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(KEY_DATA, ResultPhotosFiles::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(KEY_DATA)
}
initPlayer()
binding.run {
myData?.let { resultPhotosFiles->
imageBack.setOnClickListener { finish() }
playImage.setOnClickListener {
if (player.playbackState == Player.STATE_ENDED) {
player.seekTo(0)
}
if (!player.isPlaying) {
player.play()
it.isSelected = true
} else {
player.pause()
it.isSelected = false
}
}
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
seekBar: SeekBar?,
progress: Int,
fromUser: Boolean
) {
if (fromUser) {
val newPosition = progress * player.duration / 100
player.seekTo(newPosition)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
startProgressUpdater()
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 startProgressUpdater() {
updateHandler.post(object : Runnable {
override fun run() {
if (player.isPlaying || player.isLoading) {
val pos = player.currentPosition
val dur = player.duration.takeIf { it > 0 } ?: 1L
val progress = (pos * 100 / dur).toInt()
binding.seekBar.progress = progress
binding.textTimeCurrent.text = Common.formatDuration(pos)
binding.textTimeTotal.text = Common.formatDuration(dur)
}
updateHandler.postDelayed(this, 500)
}
})
}
private fun initPlayer() {
myData?.let {
player = ExoPlayer.Builder(this).build()
binding.playerView.player = player
val mediaItem = MediaItem.fromUri(Uri.fromFile(it.targetFile))
player.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
when (playbackState) {
Player.STATE_IDLE -> {
}
Player.STATE_BUFFERING -> {
}
Player.STATE_READY -> {
}
Player.STATE_ENDED -> {
binding.playImage.isSelected = false
}
}
}
})
player.setMediaItem(mediaItem)
player.prepare()
}
}
private fun complete(number: Int, type: Int) {
finish()
startActivity(Intent(this@VideoPlayActivity, RecoverySuccessActivity::class.java).apply {
putExtra(RecoverySuccessActivity.KEY_SUCCESS_COUNT, number)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_TYPE, type)
})
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

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">
<size android:width="8dp" android:height="16dp"/>
<corners android:radius="20dp"/>
<solid android:color="@color/white"/>
</shape>

View File

@ -0,0 +1,25 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background" android:height="4dp" android:gravity="center_vertical">
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<size android:height="4dp" />
<solid android:color="@color/bg_seekbar_video_play" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress" android:height="4dp" android:gravity="center_vertical">
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<size android:height="4dp" />
<solid android:color="@color/main_sub_title" />
</shape>
</item>
<item android:id="@android:id/progress" android:height="4dp" android:gravity="center_vertical">
<clip>
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<size android:height="4dp" />
<solid android:color="@color/main_text_blue" />
</shape>
</clip>
</item>
</layer-list>

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:drawable="@drawable/icon_info_pause" android:state_selected="true" />
<item android:drawable="@drawable/icon_info_play" android:state_selected="false" />
</selector>

View File

@ -48,13 +48,30 @@
android:padding="16dp" android:padding="16dp"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:layout_width="match_parent"
android:id="@+id/frame_image"
android:layout_height="320dp">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="320dp" /> android:layout_height="320dp" />
<ImageView
android:id="@+id/im_play"
android:layout_width="76dp"
android:layout_height="76dp"
android:layout_gravity="center"
android:src="@drawable/icon_info_play" />
</FrameLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
@ -80,6 +97,33 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/type"
android:textColor="@color/main_sub_title"
android:textSize="14sp"
app:fontType="bold" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:gravity="end"
android:textColor="@color/main_title"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_path"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="11dp" android:layout_marginTop="11dp"
@ -105,6 +149,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_resolution"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="11dp" android:layout_marginTop="11dp"
@ -130,6 +175,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_date"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="11dp" android:layout_marginTop="11dp"
@ -154,6 +200,58 @@
android:textSize="12sp" /> android:textSize="12sp" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/layout_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/size"
android:textColor="@color/main_sub_title"
android:textSize="14sp"
app:fontType="bold" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:gravity="end"
android:textColor="@color/main_title"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/duration"
android:textColor="@color/main_sub_title"
android:textSize="14sp"
app:fontType="bold" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:gravity="end"
android:textColor="@color/main_title"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -52,13 +52,13 @@
android:orientation="horizontal"> android:orientation="horizontal">
<com.ux.video.file.filerecovery.utils.CustomTextView <com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/title_date"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/date" android:ellipsize="end"
android:maxWidth="85dp" android:maxWidth="85dp"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:text="@string/date"
android:id="@+id/title_date"
android:textColor="@color/selector_black_blue" android:textColor="@color/selector_black_blue"
android:textSize="16sp" android:textSize="16sp"
app:fontType="bold" /> app:fontType="bold" />
@ -81,13 +81,13 @@
android:orientation="horizontal"> android:orientation="horizontal">
<com.ux.video.file.filerecovery.utils.CustomTextView <com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/title_size"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/size" android:ellipsize="end"
android:maxWidth="85dp" android:maxWidth="85dp"
android:maxLines="1" android:maxLines="1"
android:id="@+id/title_size" android:text="@string/size"
android:ellipsize="end"
android:textColor="@color/selector_black_blue" android:textColor="@color/selector_black_blue"
android:textSize="16sp" android:textSize="16sp"
app:fontType="bold" /> app:fontType="bold" />
@ -125,22 +125,27 @@
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:id="@+id/im_sort"
android:layout_width="47dp" android:layout_width="47dp"
android:layout_height="16dp" android:layout_height="16dp"
android:layout_alignTop="@id/filter_date_layout" android:layout_alignTop="@id/filter_date_layout"
android:layout_alignBottom="@id/filter_date_layout" android:layout_alignBottom="@id/filter_date_layout"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:id="@+id/im_sort"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:src="@drawable/icon_sort" /> android:src="@drawable/icon_sort" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/relative_thumbnails"
android:layout_below="@id/filter_date_layout">
<com.ux.video.file.filerecovery.utils.CustomTextView <com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_thumbnail_counts" android:id="@+id/tv_thumbnail_counts"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/filter_date_layout"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:text="@string/hide_thumbnails" android:text="@string/hide_thumbnails"
@ -169,6 +174,8 @@
app:trackTint="@color/selector_switch_track_color" /> app:trackTint="@color/selector_switch_track_color" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -8,6 +8,7 @@
android:background="@color/white" android:background="@color/white"
android:orientation="vertical" android:orientation="vertical"
tools:context=".result.ScanResultDisplayActivity"> tools:context=".result.ScanResultDisplayActivity">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="44dp" android:layout_height="44dp"
@ -99,11 +100,11 @@
android:id="@+id/text_file_type" android:id="@+id/text_file_type"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:fontType="bold" android:text="photos"
android:textColor="@color/main_sub_title" android:textColor="@color/main_sub_title"
android:text="photos" /> android:textSize="14sp"
app:fontType="bold" />
</LinearLayout> </LinearLayout>
<View <View
@ -141,23 +142,31 @@
android:id="@+id/text_dir" android:id="@+id/text_dir"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:fontType="bold" android:text="Folders"
android:textColor="@color/main_sub_title" android:textColor="@color/main_sub_title"
android:text="Folders" /> android:textSize="14sp"
app:fontType="bold" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/bottom_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_rectangle_white_top_20"
android:layout_marginTop="20dp">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_result" android:id="@+id/recycler_result"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:paddingVertical="10dp"
android:layout_marginTop="30dp" /> android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -34,67 +34,71 @@
app:fontType="bold" /> app:fontType="bold" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/color_bg" android:background="@color/color_bg"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linear_counts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:orientation="horizontal"
android:paddingTop="150dp">
<com.ux.video.file.filerecovery.utils.CircleImageProgressView <com.ux.video.file.filerecovery.utils.CircleImageProgressView
android:id="@+id/scan_progress" android:id="@+id/scan_progress"
style="?android:attr/progressBarStyleHorizontal" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="130dp" android:layout_width="130dp"
android:layout_height="130dp" android:layout_height="130dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dp"
android:max="100" android:max="100"
android:progress="10" /> android:progress="10"
app:layout_constraintEnd_toEndOf="parent"
<LinearLayout app:layout_constraintStart_toStartOf="parent"
android:id="@+id/linear_counts" app:layout_constraintTop_toTopOf="parent" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/scan_progress"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:orientation="horizontal">
<com.ux.video.file.filerecovery.utils.CustomTextView <com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_scan_current_counts" android:id="@+id/tv_scan_current_counts"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:gravity="end"
android:text="10" android:text="10"
android:textColor="@color/main_title" android:textColor="@color/main_title"
android:textSize="16sp" android:textSize="16sp"
app:fontType="bold" /> app:fontType="bold"
app:layout_constraintEnd_toStartOf="@id/tv_scan_describe"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scan_progress" />
<com.ux.video.file.filerecovery.utils.CustomTextView <com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_scan_describe" android:id="@+id/tv_scan_describe"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:text="10" android:text="10"
android:textColor="@color/main_title" android:textColor="@color/main_title"
android:textSize="16sp" android:textSize="16sp"
app:fontType="bold" /> app:fontType="bold"
</LinearLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/tv_scan_current_counts"
app:layout_constraintTop_toTopOf="@id/tv_scan_current_counts" />
<LinearLayout
android:id="@+id/linear_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear_counts"
android:layout_marginStart="16dp"
android:layout_marginTop="100dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ProgressBar <ProgressBar
android:id="@+id/loading_pb" android:id="@+id/loading_pb"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:indeterminateTint="@color/main_title" /> android:layout_marginStart="16dp"
android:layout_marginTop="103dp"
android:indeterminateTint="@color/main_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_scan_current_counts" />
<com.ux.video.file.filerecovery.utils.CustomTextView <com.ux.video.file.filerecovery.utils.CustomTextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -103,22 +107,80 @@
android:text="@string/scan" android:text="@string/scan"
android:textColor="@color/main_title" android:textColor="@color/main_title"
android:textSize="20sp" android:textSize="20sp"
app:fontType="bold" /> app:fontType="bold"
</LinearLayout> app:layout_constraintBottom_toBottomOf="@id/loading_pb"
app:layout_constraintStart_toEndOf="@id/loading_pb"
app:layout_constraintTop_toTopOf="@id/loading_pb"
app:layout_constraintVertical_bias="0.5" />
<TextView <TextView
android:id="@+id/tv_scan_current_file_path" android:id="@+id/tv_scan_current_file_path"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/linear_loading" android:layout_alignStart="@id/loading_pb"
android:layout_alignStart="@id/linear_loading"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="path" android:text="path"
android:textColor="@color/main_sub_title" android:textColor="@color/main_sub_title"
android:textSize="12sp" /> android:textSize="12sp"
app:layout_constraintStart_toStartOf="@id/loading_pb"
app:layout_constraintTop_toBottomOf="@id/loading_pb" />
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
android:id="@+id/relative_scan_finished_empty"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
<ImageView
android:id="@+id/icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="220dp"
android:src="@drawable/icon_finished" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_finish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/icon"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="@string/finished"
android:textColor="@color/main_title"
android:textSize="16sp"
app:fontType="bold" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_empty_type_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_finish"
android:layout_marginStart="16dp"
android:layout_marginTop="107dp"
android:text="0"
android:textColor="@color/main_title"
android:textSize="16sp"
app:fontType="bold" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_sorry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_empty_type_file"
android:layout_alignStart="@id/tv_empty_type_file"
android:layout_marginTop="3dp"
android:text="@string/not_found"
android:textColor="@color/main_sub_title"
android:textSize="12sp"
app:fontType="regular" />
</RelativeLayout> </RelativeLayout>
</FrameLayout>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,88 @@
<?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=".video.VideoPlayActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:resize_mode="fill"
app:use_controller="false" />
<ImageView
android:id="@+id/image_back"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginTop="54dp"
android:paddingHorizontal="15dp"
android:paddingVertical="12dp"
android:src="@drawable/back_white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
app:layout_constraintBottom_toTopOf="@id/layout_bottom">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_time_current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="10sp"
app:fontType="bold"
tools:text="00:00" />
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="0dp"
android:layout_height="16dp"
android:layout_weight="1"
android:max="100"
android:progress="30"
android:progressDrawable="@drawable/seekbar_video_play"
android:splitTrack="false"
android:thumb="@drawable/seekbar_thumb" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_time_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="10sp"
app:fontType="bold"
tools:text="00:00" />
</LinearLayout>
<include
android:id="@+id/layout_bottom"
layout="@layout/common_bottom_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageView
android:id="@+id/play_image"
android:layout_width="110dp"
android:layout_height="110dp"
android:src="@drawable/selector_play_button"
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

@ -3,10 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" android:id="@+id/root_layout"
android:layout_marginEnd="10dp"
android:layout_marginTop="9dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="150dp"> android:layout_height="150dp"
android:layout_marginTop="9dp"
android:layout_marginEnd="10dp">
<ImageView <ImageView
android:id="@+id/image_thumbnail" android:id="@+id/image_thumbnail"
@ -14,20 +14,34 @@
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView <RelativeLayout
android:id="@+id/text_size"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignParentBottom="true"
android:layout_centerVertical="true"
android:paddingStart="6dp"
android:background="@drawable/photo_size_bg" android:background="@drawable/photo_size_bg"
android:layout_alignParentBottom="true">
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="6dp"
android:text="150kb" android:text="150kb"
android:layout_centerVertical="true"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="11sp" android:textSize="11sp"
app:fontType="bold" app:fontType="bold"
tools:ignore="RtlSymmetry" /> tools:ignore="RtlSymmetry" />
<ImageView
android:id="@+id/image_type"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="10dp"
android:src="@drawable/icon_type_photo" />
</RelativeLayout>
<ImageView <ImageView
android:id="@+id/image_select" android:id="@+id/image_select"

View File

@ -13,19 +13,34 @@
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView <RelativeLayout
android:id="@+id/text_size"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:background="@drawable/photo_size_bg" android:background="@drawable/photo_size_bg">
android:gravity="center_vertical"
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingStart="6dp" android:paddingStart="6dp"
android:text="150kb" android:text="150kb"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="14sp" android:textSize="14sp"
app:fontType="bold" /> app:fontType="bold" />
<ImageView
android:id="@+id/image_type"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="10dp"
android:src="@drawable/icon_type_photo" />
</RelativeLayout>
<ImageView <ImageView
android:id="@+id/image_select" android:id="@+id/image_select"

View File

@ -0,0 +1,81 @@
<?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:layout_width="match_parent"
android:layout_height="64dp">
<ImageView
android:id="@+id/image_select"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintLeft_toLeftOf="parent"
android:src="@drawable/selector_icon_checkmark_28dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_name"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="20dp"
android:gravity="bottom"
android:textColor="@color/main_title"
android:textSize="14sp"
app:fontType="bold"
app:layout_constraintBottom_toTopOf="@id/linear_duration"
app:layout_constraintLeft_toRightOf="@id/image_select"
app:layout_constraintRight_toLeftOf="@id/image_play"
android:layout_marginEnd="10dp"
android:maxLines="1"
android:ellipsize="end"
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">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/icon_small_audio" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
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>
<ImageView
android:id="@+id/image_play"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="16dp"
android:src="@drawable/icon_item_audio_play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="46dp"
android:id="@+id/relative_layout"
android:paddingHorizontal="16dp">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/icon_folder_audio" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_dir_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_toEndOf="@id/icon"
android:gravity="center"
android:text="@string/allow"
android:textColor="@color/main_title"
android:textSize="16sp"
app:fontType="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_file_counts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/allow"
android:layout_toStartOf="@id/im_arrow"
android:layout_marginEnd="8dp"
android:layout_centerVertical="true"
android:textColor="@color/main_sub_title"
android:textSize="14sp"
app:fontType="bold" />
<ImageView
android:layout_width="7dp"
android:layout_height="11dp"
android:id="@+id/im_arrow"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:src="@drawable/icon_vector" />
</RelativeLayout>

View File

@ -24,6 +24,7 @@
<color name="date_dialog_center_bg">#15787880</color> <color name="date_dialog_center_bg">#15787880</color>
<color name="date_dialog_bg_unselected">#F2F2F7</color> <color name="date_dialog_bg_unselected">#F2F2F7</color>
<color name="view_div_color">#D9D9D9</color> <color name="view_div_color">#D9D9D9</color>
<color name="bg_seekbar_video_play">#99F2F2F7</color>
</resources> </resources>

View File

@ -36,12 +36,16 @@
<string name="exit">Exit</string> <string name="exit">Exit</string>
<string name="allow">Allow</string> <string name="allow">Allow</string>
<string name="scan">Scanning…</string> <string name="scan">Scanning…</string>
<string name="text_photos">Photos</string>
<string name="describe_photos">photos</string> <string name="describe_photos">photos</string>
<string name="describe_delete_photos">deleted photos</string> <string name="describe_delete_photos">deleted photos</string>
<string name="text_videos">Videos</string>
<string name="describe_videos">videos</string> <string name="describe_videos">videos</string>
<string name="describe_delete_videos">deleted videos</string> <string name="describe_delete_videos">deleted videos</string>
<string name="text_audios">Audios</string>
<string name="describe_audios">audios</string> <string name="describe_audios">audios</string>
<string name="describe_delete_audios">deleted audios</string> <string name="describe_delete_audios">deleted audios</string>
<string name="text_documents">Documents</string>
<string name="describe_documents">documents</string> <string name="describe_documents">documents</string>
<string name="describe_delete_documents">deleted documents</string> <string name="describe_delete_documents">deleted documents</string>
<string name="finished">Finished!</string> <string name="finished">Finished!</string>
@ -49,6 +53,7 @@
<string name="exit_content">If you exit,the scanning results will be discarded.Are you sure you want to exit now?</string> <string name="exit_content">If you exit,the scanning results will be discarded.Are you sure you want to exit now?</string>
<string name="date">Date</string> <string name="date">Date</string>
<string name="size">Size</string> <string name="size">Size</string>
<string name="duration">Duration</string>
<string name="layout">Layout</string> <string name="layout">Layout</string>
<string name="hide_thumbnails">Hide thumbnails (%d)</string> <string name="hide_thumbnails">Hide thumbnails (%d)</string>
<string name="hide_thumbnails_describe">Thumbnails refer to photos below 256 pixels</string> <string name="hide_thumbnails_describe">Thumbnails refer to photos below 256 pixels</string>
@ -64,6 +69,7 @@
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="name">Name</string> <string name="name">Name</string>
<string name="path">Path</string> <string name="path">Path</string>
<string name="type">Type</string>
<string name="resolution">Resolution</string> <string name="resolution">Resolution</string>
<string name="recovering">Recovering...</string> <string name="recovering">Recovering...</string>
<string name="recovering_content">It may take a few seconds to recover the file(s), please <string name="recovering_content">It may take a few seconds to recover the file(s), please
@ -78,6 +84,8 @@ wait..</string>
<string name="confirm_delete_content">The file(s) will be completely deleted and cannot be recovered.</string> <string name="confirm_delete_content">The file(s) will be completely deleted and cannot be recovered.</string>
<string name="confirm_delete">Confirm delete?</string> <string name="confirm_delete">Confirm delete?</string>
<string name="view">View</string> <string name="view">View</string>
<string name="not_found">SorryNo %s found</string>
<string-array name="filter_date"> <string-array name="filter_date">
<item>All</item> <item>All</item>
@ -86,12 +94,27 @@ wait..</string>
<item>within 24 month</item> <item>within 24 month</item>
<item>Customize</item> <item>Customize</item>
</string-array> </string-array>
<string-array name="filter_size"> <string-array name="filter_size_photo">
<item>All</item> <item>All</item>
<item>0-1 M</item> <item>0-1 M</item>
<item>1-5 M</item> <item>1-5 M</item>
<item>>5 M</item> <item>>5 M</item>
</string-array> </string-array>
<string-array name="filter_documents_size">
<item>All</item>
<item>0-500 KB</item>
<item>500 KB-1 M</item>
<item>>1 M</item>
</string-array>
<string-array name="filter_duration_video_audio">
<item>All</item>
<item>0-5 minutes</item>
<item>5-20 minutes</item>
<item>20-60 minutes</item>
<item>>60 minutes</item>
</string-array>
<string-array name="filter_layout"> <string-array name="filter_layout">
<item>2 columns</item> <item>2 columns</item>
<item>3 columns</item> <item>3 columns</item>