视频播放

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 ("com.google.android.material:material:1.13.0")
implementation(project(":pickerview"))
implementation ("androidx.media3:media3-exoplayer:1.8.0")
implementation ("androidx.media3:media3-ui:1.8.0")
}

View File

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

View File

@ -20,13 +20,15 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
if(addPadding()){
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
}
insets
}
initView()
initData()
}
protected open fun addPadding() = true
protected abstract fun inflateBinding(inflater: LayoutInflater): VB
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 kotlin.properties.Delegates
/**
* 选择扫描所有文件还是扫描删除过的文件
*/
class ScanSelectTypeActivity : BaseActivity<ActivityScanSelectTypeBinding>() {
companion object {

View File

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

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.base.DiffBaseAdapter
import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding
@ -16,6 +17,7 @@ import com.ux.video.file.filerecovery.utils.ScanRepository
class PhotoDisplayDateAdapter(
mContext: Context,
var scanType: Int,
var mColumns: Int,
var viewModel: ScanRepository,
var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, isAdd: Boolean) -> Unit,
@ -34,14 +36,13 @@ class PhotoDisplayDateAdapter(
)
/**
* 返回所有嵌套的数据量总数
*/
fun getTotalChildCount(hideThumbnails: Boolean): Int {
if(hideThumbnails){
if (hideThumbnails) {
return data.sumOf { it.second.filter { !it.isThumbnail }.size }
}else{
} else {
return data.sumOf { it.second.size }
}
@ -81,17 +82,18 @@ class PhotoDisplayDateAdapter(
val (date, files) = item
val childAdapter = PhotoDisplayDateChildAdapter(
mContext,
scanType,
mColumns,
viewModel,
{ resultPhotosFiles, addOrRemove, isDateAllSelected ->
//点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里
tvDayAllSelect.isSelected = isDateAllSelected
onSelectedUpdate(resultPhotosFiles, addOrRemove)
}, { updateHideThumbnails->
}, { updateHideThumbnails ->
tvDayAllSelect.isSelected = updateHideThumbnails
},clickItem
}, clickItem
).apply { setData(files) }
allSelected?.let {
@ -106,7 +108,15 @@ class PhotoDisplayDateAdapter(
textChildCounts.text = "(${files.size})"
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
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.target.Target
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.databinding.FileSpanCountThreeAdapterBinding
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.CustomTextView
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
@ -29,6 +31,7 @@ import com.ux.video.file.filerecovery.utils.ScanRepository
class PhotoDisplayDateChildAdapter(
mContext: Context,
var scanType: Int,
var mColumns: Int,
var viewModel: ScanRepository,
/**
@ -38,8 +41,8 @@ class PhotoDisplayDateChildAdapter(
* @param dateAllSelected 这组数据是否全部选中某一天
*/
var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, addOrRemove: Boolean, dateAllSelected: Boolean) -> Unit,
var hideThumbnailsUpdate:(dateAllSelected: Boolean)-> Unit,
var clickItem:(item:ResultPhotosFiles)-> Unit
var hideThumbnailsUpdate: (dateAllSelected: Boolean) -> Unit,
var clickItem: (item: ResultPhotosFiles) -> Unit
) :
NewBaseAdapter<ResultPhotosFiles>(mContext) {
@ -49,10 +52,13 @@ class PhotoDisplayDateChildAdapter(
companion object {
//音频或者文档
private const val TYPE_ONE = 1
private const val TYPE_TWO = 2
private const val TYPE_THREE = 3
private const val TYPE_FOUR = 4
}
fun setAllSelected(isAdd: Boolean) {
data.forEach {
addOrRemove(it, isAdd)
@ -61,6 +67,12 @@ class PhotoDisplayDateChildAdapter(
}
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) {
2 -> TYPE_TWO
3 -> TYPE_THREE
@ -68,6 +80,10 @@ class PhotoDisplayDateChildAdapter(
else -> TYPE_THREE
}
}
}
}
fun setColumns(int: Int) {
mColumns = int
@ -82,7 +98,7 @@ class PhotoDisplayDateChildAdapter(
val screenWidth = view.context.resources.displayMetrics.widthPixels
val i = (Common.itemSpacing).dpToPx(App.mAppContext) * (mColumns - 1)
val itemSize =
(screenWidth - i - 2 * Common.horizontalSpacing.dpToPx(App.mAppContext) )/ mColumns
(screenWidth - i - 2 * Common.horizontalSpacing.dpToPx(App.mAppContext)) / mColumns
view.layoutParams = layoutParams.apply {
height = itemSize
}
@ -94,6 +110,14 @@ class PhotoDisplayDateChildAdapter(
): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
TYPE_ONE -> OneHolder(
OneAudioDocumentsItemBinding.inflate(
inflater,
parent,
false
)
)
TYPE_TWO -> TwoHolder(
FileSpanCountTwoAdapterBinding.inflate(
inflater,
@ -121,13 +145,34 @@ class PhotoDisplayDateChildAdapter(
when (holder) {
is TwoHolder -> holder.vb.run {
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item)
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item, imageType)
}
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) :
RecyclerView.ViewHolder(vb.root)
class OneHolder(val vb: OneAudioDocumentsItemBinding) :
RecyclerView.ViewHolder(vb.root)
private fun initDateView(
rootLayout: RelativeLayout,
imageSelectStatus: ImageView,
textSize: CustomTextView,
imageThumbnail: ImageView,
item: ResultPhotosFiles
item: ResultPhotosFiles,
imageType: ImageView
) {
item.run {
viewModel.checkIsSelect(this).let {
imageSelectStatus.isSelected = it
addOrRemove(this, it)
@ -160,6 +209,13 @@ class PhotoDisplayDateChildAdapter(
}
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)
.load(targetFile)
.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.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
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.base.BaseActivity
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.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.ScanManager
import com.ux.video.file.filerecovery.video.VideoPlayActivity
class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
@ -34,6 +37,7 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
val KEY_CLICK_ITEM = "click_item"
}
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var myData: ResultPhotosFiles? = null
override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoInfoBinding =
@ -45,31 +49,34 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
intent.getParcelableExtra(KEY_CLICK_ITEM, ResultPhotosFiles::class.java)
} else {
@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 {
imageViewBack.setOnClickListener { finish() }
myData?.let { resultPhotosFiles->
myData?.let { resultPhotosFiles ->
tvName.text = resultPhotosFiles.name
tvPath.text = resultPhotosFiles.path
tvDate.text = Common.getFormatDate(resultPhotosFiles.lastModified)
tvResolution.text = resultPhotosFiles.resolution
tvDuration.text = Common.formatDuration(resultPhotosFiles.duration)
Glide.with(this@PhotoInfoActivity)
.load(resultPhotosFiles.targetFile)
.apply(
RequestOptions()
.transform(
CenterCrop(),
RoundedCorners(8.dpToPx(this@PhotoInfoActivity))
)
)
.apply(RequestOptions().transform(CenterCrop(), RoundedCorners(8.dpToPx(this@PhotoInfoActivity))))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable?>,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
return false
@ -92,8 +99,13 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
layoutBottom.tvLeft.run {
text = resources.getString(R.string.delete)
setOnClickListener {
RecoverOrDeleteManager.showConfirmDeleteDialog(true,supportFragmentManager,lifecycleScope,setOf(resultPhotosFiles)){count->
complete(count,1)
RecoverOrDeleteManager.showConfirmDeleteDialog(
true,
supportFragmentManager,
lifecycleScope,
setOf(resultPhotosFiles)
) { count ->
complete(count, 1)
}
}
}
@ -101,8 +113,12 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
layoutBottom.tvRight.run {
text = resources.getString(R.string.recover)
setOnClickListener {
RecoverOrDeleteManager.showRecoveringDialog(supportFragmentManager,lifecycleScope,setOf(resultPhotosFiles)){count->
complete(count,0)
RecoverOrDeleteManager.showRecoveringDialog(
supportFragmentManager,
lifecycleScope,
setOf(resultPhotosFiles)
) { count ->
complete(count, 0)
}
}
}
@ -110,11 +126,74 @@ class PhotoInfoActivity : BaseActivity<ActivityPhotoInfoBinding>() {
}
}
private fun complete(number: Int,type: Int) {
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) {
finish()
startActivity(Intent(this@PhotoInfoActivity, RecoverySuccessActivity::class.java).apply {
putExtra(RecoverySuccessActivity.KEY_SUCCESS_COUNT,number)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_TYPE,type)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_COUNT, number)
putExtra(RecoverySuccessActivity.KEY_SUCCESS_TYPE, type)
})
}
}

View File

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

View File

@ -3,6 +3,7 @@ package com.ux.video.file.filerecovery.photo
import java.io.File
import android.os.Parcelable
import com.ux.video.file.filerecovery.utils.Common
import kotlinx.parcelize.Parcelize
@Parcelize
@ -29,4 +30,10 @@ data class ResultPhotosFiles(
}
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.view.LayoutInflater
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.base.BaseActivity
import com.ux.video.file.filerecovery.databinding.ActivityScanResultDisplayBinding
import com.ux.video.file.filerecovery.photo.PhotoSortingActivity
import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.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.VALUE_SCAN_TYPE_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_video
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>() {
private var scanResultAdapter: ScanResultAdapter? = null
private var scanType: Int = VALUE_SCAN_TYPE_photo
private var exitDialog: ExitDialogFragment? = null
private var list: ArrayList<ResultPhotos>? = null
companion object {
val KEY_SCAN_RESULT = "scan_result"
@ -43,50 +40,16 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
override fun initView() {
super.initView()
val list: ArrayList<ResultPhotos>? =
intent.getParcelableArrayListExtraCompat(KEY_SCAN_RESULT)
scanResultAdapter = ScanResultAdapter(this@ScanResultDisplayActivity) { folderLists ->
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)
}
list = intent.getParcelableArrayListExtraCompat(KEY_SCAN_RESULT)
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
setSelectTypeTitle(scanType)
// 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() {
super.initData()
scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo)
setSelectTypeTitle(scanType)
binding.imageViewBack.setOnClickListener { dealExit() }
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
@ -94,36 +57,96 @@ class ScanResultDisplayActivity : BaseActivity<ActivityScanResultDisplayBinding>
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)
}
}
private fun dealExit(){
exitDialog = exitDialog?:ExitDialogFragment(){
}.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)
}
}
}
private fun dealExit() {
exitDialog = exitDialog ?: ExitDialogFragment() {
finish()
}
exitDialog?.show(supportFragmentManager,"")
exitDialog?.show(supportFragmentManager, "")
}
private fun setSelectTypeTitle(fileType: Int) {
binding.run {
when (fileType) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
title.text = getString(R.string.photo_title)
textFileType.text = getString(R.string.text_photos)
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> {
title.text = getString(R.string.video_title)
textFileType.text = getString(R.string.text_videos)
}
VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> {
title.text = getString(R.string.audio_title)
textFileType.text = getString(R.string.text_audios)
}
VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> {
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 java.io.File
class ScanResultAdapter(
class ScanResultPhotoAdapter(
mContext: Context,
var type: Int,
var onClickItem: (allFiles: ArrayList<ResultPhotosFiles>) -> Unit
) :
BaseAdapter<ResultPhotos, ScanResultAdapterBinding>(mContext) {

View File

@ -1,8 +1,11 @@
package com.ux.video.file.filerecovery.result
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Environment
import android.view.LayoutInflater
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import com.ux.video.file.filerecovery.R
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.flow.flowOn
import kotlinx.coroutines.launch
import androidx.lifecycle.repeatOnLifecycle
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
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_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() }
}
@ -65,41 +58,49 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
VALUE_SCAN_TYPE_photo -> {
title.text = getString(R.string.photo_title)
tvScanDescribe.text = getString(R.string.describe_photos)
scanProgress.setCenterImage(R.drawable.im_photo_center_image)
}
VALUE_SCAN_TYPE_deleted_photo -> {
title.text = getString(R.string.photo_title)
tvScanDescribe.text = getString(R.string.describe_delete_photos)
scanProgress.setCenterImage(R.drawable.im_photo_center_image)
}
VALUE_SCAN_TYPE_video -> {
title.text = getString(R.string.video_title)
tvScanDescribe.text = getString(R.string.describe_videos)
scanProgress.setCenterImage(R.drawable.im_video_center_image)
}
VALUE_SCAN_TYPE_deleted_video -> {
title.text = getString(R.string.video_title)
tvScanDescribe.text = getString(R.string.describe_delete_videos)
scanProgress.setCenterImage(R.drawable.im_video_center_image)
}
VALUE_SCAN_TYPE_audio -> {
title.text = getString(R.string.audio_title)
tvScanDescribe.text = getString(R.string.describe_audios)
scanProgress.setCenterImage(R.drawable.im_audio_center_image)
}
VALUE_SCAN_TYPE_deleted_audio -> {
title.text = getString(R.string.audio_title)
tvScanDescribe.text = getString(R.string.describe_delete_audios)
scanProgress.setCenterImage(R.drawable.im_audio_center_image)
}
VALUE_SCAN_TYPE_documents -> {
title.text = getString(R.string.document_title)
tvScanDescribe.text = getString(R.string.describe_documents)
scanProgress.setCenterImage(R.drawable.im_documents_center_image)
}
VALUE_SCAN_TYPE_deleted_documents -> {
title.text = getString(R.string.document_title)
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() {
val total = 800
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
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) {
is ScanState.Progress -> {
updateProgress(it)
@ -122,6 +125,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
}
}
}
}
}
@ -129,9 +133,10 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
private fun scanDeleted() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
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) {
is ScanState.Progress -> {
updateProgress(it)
@ -147,6 +152,8 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
}
}
private fun updateProgress(scanState: ScanState.Progress) {
val total = 1000
@ -166,27 +173,32 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
}
@SuppressLint("SetTextI18n")
private fun updateComplete(scanState: ScanState.Complete) {
binding.scanProgress.setProgress(100)
binding.run {
scanProgress.setProgress(100)
scanState.let {
startActivity(
Intent(
this@ScanningActivity,
ScanResultDisplayActivity::class.java
).apply {
val size = it.result.size
if (size == 0) {
tvScanDescribe.text.let {
tvEmptyTypeFile.text = "0 $it"
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(
ScanResultDisplayActivity.KEY_SCAN_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
import android.annotation.SuppressLint
import android.content.Context
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.media.MediaMetadataRetriever
import android.os.Environment
import android.util.Log
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 {
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(
months: Int = -1,
startDate: Date? = null,
@ -64,10 +49,9 @@ object ExtendFunctions {
val today = Calendar.getInstance()
return when {
// ✅ 1. -1 表示不过滤,返回全部
months == -1 -> this
// ✅ 2. 0 表示仅根据 startDate / endDate 筛选
months == 0 -> this.filter { file ->
val date = Date(file.lastModified)
when {
@ -78,7 +62,6 @@ object ExtendFunctions {
}
}
// ✅ 3. 其他情况:按“最近 N 个月”筛选
else -> {
val monthsAgo = Calendar.getInstance().apply {
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(
months: Int = -1,
startDate: Date? = null,
@ -136,10 +94,10 @@ object ExtendFunctions {
}
return when {
// -1 表示不过滤,返回全部
months == -1 -> this
// 0 表示只用日期范围过滤
months == 0 -> this.filter { (dayStr, _) ->
val day = sdf.parse(dayStr) ?: return@filter false
when {
@ -150,22 +108,37 @@ object ExtendFunctions {
}
}
// 其他情况:按“最近 N 个月”过滤
else -> this.filter { (dayStr, _) ->
val day = sdf.parse(dayStr) ?: return@filter false
!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(
minSize: Long,
@ -177,10 +150,34 @@ object ExtendFunctions {
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 {
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.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
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 kotlinx.coroutines.CoroutineScope
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.flowOn
@ -63,9 +66,12 @@ object ScanManager {
val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0
suspend fun scanDocuments(dir: File, depth: Int) {
val context = currentCoroutineContext()
if (!dir.exists() || !dir.isDirectory) return
if (depth > maxDepth || fileCount >= maxFiles) return
dir.listFiles()?.forEach { file ->
context.ensureActive()
if (file.isDirectory) {
scanDocuments(file, depth + 1)
} else {
@ -107,11 +113,12 @@ object ScanManager {
name = file.name,
path = file.absolutePath,
size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()),
sizeString = android.text.format.Formatter.formatFileSize(
context,
file.length()
),
lastModified = file.lastModified(),
resolution = getImageSize(file).run {
"$first*$second"
}
resolution = getResolution(type,file)
)
}
ResultPhotos(dir, ArrayList(resultPhotosFilesList))
@ -128,6 +135,25 @@ object ScanManager {
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 // 最大递归深度
@ -141,6 +167,7 @@ object ScanManager {
val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0
@RequiresApi(Build.VERSION_CODES.R)
suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) {
if (!dir.exists() || !dir.isDirectory) return
@ -192,11 +219,12 @@ object ScanManager {
name = file.name,
path = file.absolutePath,
size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()),
sizeString = android.text.format.Formatter.formatFileSize(
context,
file.length()
),
lastModified = file.lastModified(),
resolution = getImageSize(file).run {
"$first*$second"
}
resolution = getResolution(type,file)
)
}
@ -217,6 +245,22 @@ object ScanManager {
return file.length() // fallback
}
private fun getResolution(type: Int,file: File): String {
return when (type) {
VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> {
getImageSize(file).run {
"$first*$second"
}
}
VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> getVideoResolution(file.path).run {
"$first*$second"
}
else -> ""
}
}
private fun isFormatFile(file: File, types: List<String>): Boolean {
val ext = file.extension.lowercase()
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"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:layout_width="match_parent"
android:id="@+id/frame_image"
android:layout_height="320dp">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
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
android:id="@+id/layout_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@ -80,6 +97,33 @@
</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_height="wrap_content"
android:layout_marginTop="11dp"
@ -105,6 +149,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layout_resolution"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
@ -130,6 +175,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layout_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
@ -154,6 +200,58 @@
android:textSize="12sp" />
</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>

View File

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

View File

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

View File

@ -34,67 +34,71 @@
app:fontType="bold" />
</RelativeLayout>
<RelativeLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_bg"
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
android:id="@+id/scan_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="130dp"
android:layout_height="130dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dp"
android:max="100"
android:progress="10" />
<LinearLayout
android:id="@+id/linear_counts"
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">
android:progress="10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/tv_scan_current_counts"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:gravity="end"
android:text="10"
android:textColor="@color/main_title"
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
android:id="@+id/tv_scan_describe"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="10"
android:textColor="@color/main_title"
android:textSize="16sp"
app:fontType="bold" />
</LinearLayout>
app:fontType="bold"
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
android:id="@+id/loading_pb"
android:layout_width="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
android:layout_width="wrap_content"
@ -103,22 +107,80 @@
android:text="@string/scan"
android:textColor="@color/main_title"
android:textSize="20sp"
app:fontType="bold" />
</LinearLayout>
app:fontType="bold"
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
android:id="@+id/tv_scan_current_file_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear_loading"
android:layout_alignStart="@id/linear_loading"
android:layout_alignStart="@id/loading_pb"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:text="path"
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>
</FrameLayout>
</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:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_marginEnd="10dp"
android:layout_marginTop="9dp"
android:layout_width="match_parent"
android:layout_height="150dp">
android:layout_height="150dp"
android:layout_marginTop="9dp"
android:layout_marginEnd="10dp">
<ImageView
android:id="@+id/image_thumbnail"
@ -14,20 +14,34 @@
android:layout_height="match_parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_size"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_alignParentBottom="true"
android:layout_centerVertical="true"
android:paddingStart="6dp"
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:layout_centerVertical="true"
android:textColor="@color/white"
android:textSize="11sp"
app:fontType="bold"
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
android:id="@+id/image_select"

View File

@ -13,19 +13,34 @@
android:layout_height="match_parent" />
<com.ux.video.file.filerecovery.utils.CustomTextView
android:id="@+id/text_size"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:background="@drawable/photo_size_bg"
android:gravity="center_vertical"
android:background="@drawable/photo_size_bg">
<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:text="150kb"
android:textColor="@color/white"
android:textSize="14sp"
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
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_bg_unselected">#F2F2F7</color>
<color name="view_div_color">#D9D9D9</color>
<color name="bg_seekbar_video_play">#99F2F2F7</color>
</resources>

View File

@ -36,12 +36,16 @@
<string name="exit">Exit</string>
<string name="allow">Allow</string>
<string name="scan">Scanning…</string>
<string name="text_photos">Photos</string>
<string name="describe_photos">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_delete_videos">deleted videos</string>
<string name="text_audios">Audios</string>
<string name="describe_audios">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_delete_documents">deleted documents</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="date">Date</string>
<string name="size">Size</string>
<string name="duration">Duration</string>
<string name="layout">Layout</string>
<string name="hide_thumbnails">Hide thumbnails (%d)</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="name">Name</string>
<string name="path">Path</string>
<string name="type">Type</string>
<string name="resolution">Resolution</string>
<string name="recovering">Recovering...</string>
<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">Confirm delete?</string>
<string name="view">View</string>
<string name="not_found">SorryNo %s found</string>
<string-array name="filter_date">
<item>All</item>
@ -86,12 +94,27 @@ wait..</string>
<item>within 24 month</item>
<item>Customize</item>
</string-array>
<string-array name="filter_size">
<string-array name="filter_size_photo">
<item>All</item>
<item>0-1 M</item>
<item>1-5 M</item>
<item>>5 M</item>
</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">
<item>2 columns</item>
<item>3 columns</item>