diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1ed77ce..67dfffd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65ac6bf..13ce729 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,6 +24,9 @@ android:supportsRtl="true" android:theme="@style/Theme.FileRecovery" tools:targetApi="31"> + diff --git a/app/src/main/java/com/ux/video/file/filerecovery/base/BaseActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/base/BaseActivity.kt index 4196ef7..757f4c4 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/base/BaseActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/base/BaseActivity.kt @@ -20,13 +20,15 @@ abstract class BaseActivity : AppCompatActivity() { setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + 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() {} diff --git a/app/src/main/java/com/ux/video/file/filerecovery/main/ScanSelectTypeActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/main/ScanSelectTypeActivity.kt index 3d45b83..b4f1d3b 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/main/ScanSelectTypeActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/main/ScanSelectTypeActivity.kt @@ -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() { companion object { diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt index d791f76..29f9742 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt @@ -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, diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt index 20420bd..cb82f29 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt @@ -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 } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt index 30b3bfd..8dd7577 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt @@ -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(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,12 +67,22 @@ class PhotoDisplayDateChildAdapter( } override fun getItemViewType(position: Int): Int { - return when (mColumns) { - 2 -> TYPE_TWO - 3 -> TYPE_THREE - 4 -> TYPE_FOUR - else -> TYPE_THREE + 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 + 4 -> TYPE_FOUR + else -> TYPE_THREE + } + } } + + } fun setColumns(int: 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 = data, hideThumbnails: Boolean): Int { - if(hideThumbnails){ - return list.filter { !it.isThumbnail }.size - }else{ - return list.size - } - - } - } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoInfoActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoInfoActivity.kt index f767c95..9d25a72 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoInfoActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoInfoActivity.kt @@ -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() { @@ -34,6 +37,7 @@ class PhotoInfoActivity : BaseActivity() { 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() { 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 { override fun onLoadFailed( e: GlideException?, model: Any?, - target: com.bumptech.glide.request.target.Target, + target: Target, isFirstResource: Boolean ): Boolean { return false @@ -92,8 +99,13 @@ class PhotoInfoActivity : BaseActivity() { 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() { 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() { } } - 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) }) } } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt index e5c5439..c77a4ec 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt @@ -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() { 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() { 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() { private lateinit var mItemDecoration: GridSpacingItemDecoration + private lateinit var sizeFilterItemArray: Array + private lateinit var viewModel: ScanRepository override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoSortingBinding = @@ -110,13 +125,12 @@ class PhotoSortingActivity : BaseActivity() { override fun initData() { super.initData() - + scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo) val list: ArrayList? = 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() { 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() { sizeSortAdapter = PhotoDisplayDateChildAdapter( this@PhotoSortingActivity, + scanType, columns, viewModel, { resultPhotosFiles, isAdd, allSelected -> viewModel.toggleSelection(isAdd, resultPhotosFiles) @@ -156,6 +170,7 @@ class PhotoSortingActivity : BaseActivity() { this@PhotoSortingActivity, PhotoInfoActivity::class.java ).apply { + putExtra(KEY_SCAN_TYPE,scanType) putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item) }) @@ -163,6 +178,7 @@ class PhotoSortingActivity : BaseActivity() { dateAdapter = PhotoDisplayDateAdapter( this@PhotoSortingActivity, + scanType, columns, viewModel, { actionPath, isAdd -> @@ -173,6 +189,7 @@ class PhotoSortingActivity : BaseActivity() { this@PhotoSortingActivity, PhotoInfoActivity::class.java ).apply { + putExtra(KEY_SCAN_TYPE,scanType) putExtra(PhotoInfoActivity.KEY_CLICK_ITEM, item) }) }.apply { @@ -182,166 +199,209 @@ class PhotoSortingActivity : BaseActivity() { setDateAdapter() setSingleDelete() setFilter() - binding.run { - imageViewBack.setOnClickListener { finish() } - switchHideThumbnails.setOnCheckedChangeListener { _, isChecked -> - when (recyclerView.adapter) { - is PhotoDisplayDateAdapter -> { - lifecycleScope.launch { - dateAdapter?.run { - initGetCurrentDateList().let { list -> - val filterThumbnailsAsync = - if (isChecked) list.filterThumbnailsAsync() else list - setData(filterThumbnailsAsync) - checkRefreshDisPlaySelected(list1 = filterThumbnailsAsync) - } + setAllClick() - } - } - - } - - is PhotoDisplayDateChildAdapter -> { - lifecycleScope.launch { - sizeSortAdapter?.run { - initGetCurrentSizeList().let { - val filterThumbnailsAsync = - if (isChecked) it.filterRemoveThumbnailsAsync() else it - setData(filterThumbnailsAsync) - checkRefreshDisPlaySelected(list2 = filterThumbnailsAsync) - } - - } - } - - } - } - updateCurrentIsAllSelectStatus() - - } - tvRecover.setOnClickListener { -// showRecoveringDialog() - RecoverOrDeleteManager.showRecoveringDialog( - supportFragmentManager, - lifecycleScope, - filterSelectedSetList - ) { count -> - complete(count, 0) - } - - } - tvDelete.setOnClickListener { -// showConfirmDeleteDialog() - RecoverOrDeleteManager.showConfirmDeleteDialog( - fragmentManager = supportFragmentManager, - scope = lifecycleScope, - selectedSetList = filterSelectedSetList - ) { count -> - complete(count, 1) - } - - } - imSort.setOnClickListener { - sortDialogFragment = sortDialogFragment ?: SortDialogFragment { - when (it) { - SORT_ASC_DATE -> { - setDateAdapter() - lifecycleScope.launch { - initGetCurrentDateList().let { - val filterThumbnailsAsync = - if (switchHideThumbnails.isChecked) it.filterThumbnailsAsync() else it - val sortByDayOldToNew = - Common.getSortByDayOldToNew(filterThumbnailsAsync) - dateAdapter?.setData(sortByDayOldToNew) - resetCurrentDateList(sortByDayOldToNew) - } - sortReverse = false - } - - } - - SORT_DESC_DATE -> { - setDateAdapter() - lifecycleScope.launch { - initGetCurrentDateList().let { - val filterThumbnailsAsync = - if (switchHideThumbnails.isChecked) it.filterThumbnailsAsync() else it - val sortByDayNewToOld = - Common.getSortByDayNewToOld(filterThumbnailsAsync) - dateAdapter?.setData(sortByDayNewToOld) - resetCurrentDateList(sortByDayNewToOld) - } - sortReverse = true - } - } - - SORT_DESC_SIZE -> { - setSizeAdapter() - lifecycleScope.launch { - initGetCurrentSizeList().let { - val filterThumbnailsAsync = - if (switchHideThumbnails.isChecked) it.filterRemoveThumbnailsAsync() else it - val sortBySizeBigToSmall = - Common.getSortBySizeBigToSmall(filterThumbnailsAsync) - sizeSortAdapter?.setData(sortBySizeBigToSmall) - resetCurrentSizeList(sortBySizeBigToSmall) - } - sortReverse = true - } - - } - - SORT_ASC_SIZE -> { - setSizeAdapter() - lifecycleScope.launch { - initGetCurrentSizeList().let { - val filterThumbnailsAsync = - if (switchHideThumbnails.isChecked) it.filterRemoveThumbnailsAsync() else it - val sortBySizeSmallToBig = - Common.getSortBySizeSmallToBig(filterThumbnailsAsync) - sizeSortAdapter?.setData(sortBySizeSmallToBig) - resetCurrentSizeList(sortBySizeSmallToBig) - } - sortReverse = false - } - } - } - } - sortDialogFragment?.show(supportFragmentManager, "") - } - - //全选按钮 只对当前显示的数据有效 - tvSelectAll.setOnClickListener { - it.isSelected = !it.isSelected - when (binding.recyclerView.adapter) { - is PhotoDisplayDateAdapter -> { - dateAdapter?.setAllSelected(it.isSelected) - dateAdapter?.getCurrentData()?.let { - it as List>> - - if (it.size > 0) - Common.showLog("------------全选按钮 日期-${it.size} ${it[0].second[0].path}") - } - - } - is PhotoDisplayDateChildAdapter -> { - sizeSortAdapter?.setAllSelected(it.isSelected) - sizeSortAdapter?.getCurrentData()?.let { - it as List - if (it.size > 0) - Common.showLog("------------全选按钮 大小-${it.size} ${it[0].path}") - } - } - } - - - - } - } } } + private fun setAllClick() { + binding.run { + imageViewBack.setOnClickListener { finish() } + switchHideThumbnails.setOnCheckedChangeListener { _, isChecked -> + when (recyclerView.adapter) { + is PhotoDisplayDateAdapter -> { + lifecycleScope.launch { + dateAdapter?.run { + initGetCurrentDateList().let { list -> + val filterThumbnailsAsync = + if (isChecked) list.filterThumbnailsAsync() else list + setData(filterThumbnailsAsync) + checkRefreshDisPlaySelected(list1 = filterThumbnailsAsync) + } + + } + } + + } + + is PhotoDisplayDateChildAdapter -> { + lifecycleScope.launch { + sizeSortAdapter?.run { + initGetCurrentSizeList().let { + val filterThumbnailsAsync = + if (isChecked) it.filterRemoveThumbnailsAsync() else it + setData(filterThumbnailsAsync) + checkRefreshDisPlaySelected(list2 = filterThumbnailsAsync) + } + + } + } + + } + } + updateCurrentIsAllSelectStatus() + + } + tvRecover.setOnClickListener { + RecoverOrDeleteManager.showRecoveringDialog( + supportFragmentManager, + lifecycleScope, + filterSelectedSetList + ) { count -> + complete(count, 0) + } + + } + tvDelete.setOnClickListener { + RecoverOrDeleteManager.showConfirmDeleteDialog( + fragmentManager = supportFragmentManager, + scope = lifecycleScope, + selectedSetList = filterSelectedSetList + ) { count -> + complete(count, 1) + } + + } + imSort.setOnClickListener { + sortDialogFragment = sortDialogFragment ?: SortDialogFragment { + when (it) { + SORT_ASC_DATE -> { + setDateAdapter() + lifecycleScope.launch { + initGetCurrentDateList().let { + val filterThumbnailsAsync = + if (switchHideThumbnails.isChecked) it.filterThumbnailsAsync() else it + val sortByDayOldToNew = + Common.getSortByDayOldToNew(filterThumbnailsAsync) + dateAdapter?.setData(sortByDayOldToNew) + resetCurrentDateList(sortByDayOldToNew) + } + sortReverse = false + } + + } + + SORT_DESC_DATE -> { + setDateAdapter() + lifecycleScope.launch { + initGetCurrentDateList().let { + val filterThumbnailsAsync = + if (switchHideThumbnails.isChecked) it.filterThumbnailsAsync() else it + val sortByDayNewToOld = + Common.getSortByDayNewToOld(filterThumbnailsAsync) + dateAdapter?.setData(sortByDayNewToOld) + resetCurrentDateList(sortByDayNewToOld) + } + sortReverse = true + } + } + + SORT_DESC_SIZE -> { + setSizeAdapter() + lifecycleScope.launch { + initGetCurrentSizeList().let { + val filterThumbnailsAsync = + if (switchHideThumbnails.isChecked) it.filterRemoveThumbnailsAsync() else it + val sortBySizeBigToSmall = + Common.getSortBySizeBigToSmall(filterThumbnailsAsync) + sizeSortAdapter?.setData(sortBySizeBigToSmall) + resetCurrentSizeList(sortBySizeBigToSmall) + } + sortReverse = true + } + + } + + SORT_ASC_SIZE -> { + setSizeAdapter() + lifecycleScope.launch { + initGetCurrentSizeList().let { + val filterThumbnailsAsync = + if (switchHideThumbnails.isChecked) it.filterRemoveThumbnailsAsync() else it + val sortBySizeSmallToBig = + Common.getSortBySizeSmallToBig(filterThumbnailsAsync) + sizeSortAdapter?.setData(sortBySizeSmallToBig) + resetCurrentSizeList(sortBySizeSmallToBig) + } + sortReverse = false + } + } + } + } + sortDialogFragment?.show(supportFragmentManager, "") + } + + //全选按钮 只对当前显示的数据有效 + tvSelectAll.setOnClickListener { + it.isSelected = !it.isSelected + when (binding.recyclerView.adapter) { + is PhotoDisplayDateAdapter -> { + dateAdapter?.setAllSelected(it.isSelected) + dateAdapter?.getCurrentData()?.let { + it as List>> + if (it.size > 0) + Common.showLog("------------全选按钮 日期-${it.size} ${it[0].second[0].path}") + } + + } + + is PhotoDisplayDateChildAdapter -> { + sizeSortAdapter?.setAllSelected(it.isSelected) + sizeSortAdapter?.getCurrentData()?.let { + it as List + if (it.size > 0) + Common.showLog("------------全选按钮 大小-${it.size} ${it[0].path}") + } + } + } + + + } + } + } + + + /** + * 不同类型下的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() { filterSelectedSetList.size.let { binding.tvSelectCounts.text = it.toString() @@ -498,19 +558,20 @@ class PhotoSortingActivity : BaseActivity() { //大小筛选 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,48 +639,61 @@ class PhotoSortingActivity : BaseActivity() { } /** - * 执行筛选结果 + * 执行筛选结果 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 -> - checkRefreshDisPlaySelected(list1 = currentList) - dateAdapter?.resetAllValue(null) - dateAdapter?.setData(currentList) - resetCurrentDateList(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 -> - checkRefreshDisPlaySelected(list2 = currentList) - sizeSortAdapter?.setData(currentList) - resetCurrentSizeList(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() { } - private fun filterSizeCovert(filterSize: Int): Pair { - 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) - else -> Pair(-1L, -1L) + private fun filterSizeCovert(scanType: Int, filterSize: String): Pair { + when (scanType) { + VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> { + val stringArray = resources.getStringArray(R.array.filter_size_photo) + return when (filterSize) { + 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() { } - /** * 删除或者恢复完成 * @param type 0 恢复 1 删除 diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt index 79a9838..9e7ee9c 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt @@ -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 @@ -17,7 +18,7 @@ data class ResultPhotosFiles( val targetFile: File? get() = path?.let { File(it) } - //是否为缩略图文件(宽高任一小于 256) + //是否为缩略图文件(宽高任一小于 256) val isThumbnail: Boolean get() { val parts = resolution.lowercase().split("*").mapNotNull { @@ -29,4 +30,10 @@ data class ResultPhotosFiles( } return false } + + + //音视频时长 + val duration: Long + get() { + return Common.getMediaDuration(path.toString()) } } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt index 2fec3f1..3537692 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt @@ -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() { - private var scanResultAdapter: ScanResultAdapter? = null private var scanType: Int = VALUE_SCAN_TYPE_photo private var exitDialog: ExitDialogFragment? = null + private var list: ArrayList? = null + companion object { val KEY_SCAN_RESULT = "scan_result" @@ -43,50 +40,16 @@ class ScanResultDisplayActivity : BaseActivity override fun initView() { super.initView() - val list: ArrayList? = - 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 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) + } + + + } } - private fun dealExit(){ - exitDialog = exitDialog?:ExitDialogFragment(){ + 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) { + startActivity( + Intent( + this@ScanResultDisplayActivity, + PhotoSortingActivity::class.java + ).apply { + putExtra(KEY_SCAN_TYPE, scanType) + putParcelableArrayListExtra( + PhotoSortingActivity.KEY_PHOTO_FOLDER_FILE, + list + ) + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDocumentsAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDocumentsAdapter.kt new file mode 100644 index 0000000..73d146b --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDocumentsAdapter.kt @@ -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) -> Unit +) : + BaseAdapter(mContext) { + override fun getViewBinding(parent: ViewGroup): ScanResultDocumentsAdapterBinding = + ScanResultDocumentsAdapterBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + + @SuppressLint("SetTextI18n") + override fun bindItem( + holder: VHolder, + 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) + } + } + + } + } + + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultPhotoAdapter.kt similarity index 98% rename from app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt rename to app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultPhotoAdapter.kt index 3951f76..6f6adc5 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultPhotoAdapter.kt @@ -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) -> Unit ) : BaseAdapter(mContext) { diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt index 09595f4..3f29c7d 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt @@ -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() { - 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() { 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() { 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,15 +111,18 @@ class ScanningActivity : BaseActivity() { private fun scanAll() { val total = 800 lifecycleScope.launch { - val root = Environment.getExternalStorageDirectory() - ScanManager.scanAllDocuments(this@ScanningActivity,root, type = scanType).flowOn(Dispatchers.IO).collect { - when (it) { - is ScanState.Progress -> { - updateProgress(it) - } + repeatOnLifecycle(Lifecycle.State.STARTED) { + val root = Environment.getExternalStorageDirectory() + ScanManager.scanAllDocuments(this@ScanningActivity, root, type = scanType) + .flowOn(Dispatchers.IO).collect { + when (it) { + is ScanState.Progress -> { + updateProgress(it) + } - is ScanState.Complete -> { - updateComplete(it) + is ScanState.Complete -> { + updateComplete(it) + } } } } @@ -129,20 +133,23 @@ class ScanningActivity : BaseActivity() { private fun scanDeleted() { lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + val root = Environment.getExternalStorageDirectory() + ScanManager.scanHiddenPhotoAsync(this@ScanningActivity, root, type = scanType) + .flowOn(Dispatchers.IO).collect { + when (it) { + is ScanState.Progress -> { + updateProgress(it) - val root = Environment.getExternalStorageDirectory() - ScanManager.scanHiddenPhotoAsync(this@ScanningActivity,root, type = scanType).flowOn(Dispatchers.IO).collect { - when (it) { - is ScanState.Progress -> { - updateProgress(it) + } - } - - is ScanState.Complete -> { - updateComplete(it) + is ScanState.Complete -> { + updateComplete(it) + } } } } + } } @@ -166,27 +173,32 @@ class ScanningActivity : BaseActivity() { } + @SuppressLint("SetTextI18n") private fun updateComplete(scanState: ScanState.Complete) { - binding.scanProgress.setProgress(100) - scanState.let { - startActivity( - Intent( - this@ScanningActivity, - ScanResultDisplayActivity::class.java - ).apply { - putParcelableArrayListExtra( - ScanResultDisplayActivity.KEY_SCAN_RESULT, - it.result - ) - }) - ScanManager.showLog( - "HiddenScan", - "完成: ${it.result.size}" - ) - + binding.run { + scanProgress.setProgress(100) + scanState.let { + 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() - } } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt index 673230b..a927d39 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt @@ -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)) } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt index cb07409..5a60590 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt @@ -40,21 +40,6 @@ object ExtendFunctions { } - /** - * 按时间筛选:最近 N 个月 - */ -// fun List.filterWithinMonthsList(months: Int): List { -// 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.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.filterBySizeList( - minSize: Long, - maxSize: Long - ): List { - if (minSize == -1L) return this - return this.filter { it.size in minSize..maxSize } - } - /** - * 按时间筛选:最近 N 个月 - */ -// fun List>>.filterWithinMonths(months: Int): List>> { -// 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>>.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.filterBySizeList( + minSize: Long, + maxSize: Long + ): List { + if (minSize == -1L) return this + return this.filter { it.size in minSize..maxSize } + } /** - * 分组数据:按大小筛选 + * 按文件大小筛选:区间 [minSize, maxSize] + */ + fun List.filterByDurationList( + minSize: Long, + maxSize: Long + ): List { + if (minSize == -1L) return this + return this.filter { it.duration in minSize..maxSize } + } + + /** + * 分组数据:按大小筛选 ,图片和文件筛选文件大小 */ fun List>>.filterBySize( minSize: Long, @@ -177,10 +150,34 @@ object ExtendFunctions { if (filtered.isNotEmpty()) date to filtered else null } } + + /** + * 分组数据:按大小筛选 ,音视频筛选时长 + */ + fun List>>.filterByDuration( + minSize: Long, + maxSize: Long + ): List>> { + 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 + } + /** * 移除掉缩略图后的数据 */ diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt index d4eb704..8e7dc8b 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt @@ -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>() 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 { + 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>() 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): Boolean { val ext = file.extension.lowercase() return types.contains(ext) diff --git a/app/src/main/java/com/ux/video/file/filerecovery/video/VideoPlayActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/video/VideoPlayActivity.kt new file mode 100644 index 0000000..7231b0f --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/video/VideoPlayActivity.kt @@ -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() { + + 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) + }) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/back_white.png b/app/src/main/res/drawable/back_white.png new file mode 100644 index 0000000..9a7a0d9 Binary files /dev/null and b/app/src/main/res/drawable/back_white.png differ diff --git a/app/src/main/res/drawable/icon_finished.png b/app/src/main/res/drawable/icon_finished.png new file mode 100644 index 0000000..fc7b43e Binary files /dev/null and b/app/src/main/res/drawable/icon_finished.png differ diff --git a/app/src/main/res/drawable/icon_folder_audio.png b/app/src/main/res/drawable/icon_folder_audio.png new file mode 100644 index 0000000..fd56bf2 Binary files /dev/null and b/app/src/main/res/drawable/icon_folder_audio.png differ diff --git a/app/src/main/res/drawable/icon_folder_documents.png b/app/src/main/res/drawable/icon_folder_documents.png new file mode 100644 index 0000000..23e1485 Binary files /dev/null and b/app/src/main/res/drawable/icon_folder_documents.png differ diff --git a/app/src/main/res/drawable/icon_info_pause.png b/app/src/main/res/drawable/icon_info_pause.png new file mode 100644 index 0000000..1a53755 Binary files /dev/null and b/app/src/main/res/drawable/icon_info_pause.png differ diff --git a/app/src/main/res/drawable/icon_info_play.png b/app/src/main/res/drawable/icon_info_play.png new file mode 100644 index 0000000..e293c44 Binary files /dev/null and b/app/src/main/res/drawable/icon_info_play.png differ diff --git a/app/src/main/res/drawable/icon_item_audio_play.png b/app/src/main/res/drawable/icon_item_audio_play.png new file mode 100644 index 0000000..baf4e3b Binary files /dev/null and b/app/src/main/res/drawable/icon_item_audio_play.png differ diff --git a/app/src/main/res/drawable/icon_small_audio.png b/app/src/main/res/drawable/icon_small_audio.png new file mode 100644 index 0000000..3922693 Binary files /dev/null and b/app/src/main/res/drawable/icon_small_audio.png differ diff --git a/app/src/main/res/drawable/icon_type_photo.png b/app/src/main/res/drawable/icon_type_photo.png new file mode 100644 index 0000000..bad1d06 Binary files /dev/null and b/app/src/main/res/drawable/icon_type_photo.png differ diff --git a/app/src/main/res/drawable/icon_type_video.png b/app/src/main/res/drawable/icon_type_video.png new file mode 100644 index 0000000..a1f7ca0 Binary files /dev/null and b/app/src/main/res/drawable/icon_type_video.png differ diff --git a/app/src/main/res/drawable/im_audio_center_image.png b/app/src/main/res/drawable/im_audio_center_image.png new file mode 100644 index 0000000..5b6561c Binary files /dev/null and b/app/src/main/res/drawable/im_audio_center_image.png differ diff --git a/app/src/main/res/drawable/im_documents_center_image.png b/app/src/main/res/drawable/im_documents_center_image.png new file mode 100644 index 0000000..1fa0708 Binary files /dev/null and b/app/src/main/res/drawable/im_documents_center_image.png differ diff --git a/app/src/main/res/drawable/im_video_center_image.png b/app/src/main/res/drawable/im_video_center_image.png new file mode 100644 index 0000000..08ef0fc Binary files /dev/null and b/app/src/main/res/drawable/im_video_center_image.png differ diff --git a/app/src/main/res/drawable/seekbar_thumb.xml b/app/src/main/res/drawable/seekbar_thumb.xml new file mode 100644 index 0000000..5420ffb --- /dev/null +++ b/app/src/main/res/drawable/seekbar_thumb.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/seekbar_video_play.xml b/app/src/main/res/drawable/seekbar_video_play.xml new file mode 100644 index 0000000..3f5a1f4 --- /dev/null +++ b/app/src/main/res/drawable/seekbar_video_play.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/selector_play_button.xml b/app/src/main/res/drawable/selector_play_button.xml new file mode 100644 index 0000000..3e83ce9 --- /dev/null +++ b/app/src/main/res/drawable/selector_play_button.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_photo_info.xml b/app/src/main/res/layout/activity_photo_info.xml index 5a71be9..f94842a 100644 --- a/app/src/main/res/layout/activity_photo_info.xml +++ b/app/src/main/res/layout/activity_photo_info.xml @@ -48,13 +48,30 @@ android:padding="16dp" app:layout_constraintTop_toTopOf="parent"> - + android:id="@+id/frame_image" + android:layout_height="320dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_photo_sorting.xml b/app/src/main/res/layout/activity_photo_sorting.xml index 8d81bf8..5e54de9 100644 --- a/app/src/main/res/layout/activity_photo_sorting.xml +++ b/app/src/main/res/layout/activity_photo_sorting.xml @@ -52,13 +52,13 @@ android:orientation="horizontal"> @@ -81,13 +81,13 @@ android:orientation="horizontal"> @@ -125,48 +125,55 @@ - + android:id="@+id/relative_thumbnails" + android:layout_below="@id/filter_date_layout"> - + + + + + + - diff --git a/app/src/main/res/layout/activity_scan_result_display.xml b/app/src/main/res/layout/activity_scan_result_display.xml index 160f102..e4f71d0 100644 --- a/app/src/main/res/layout/activity_scan_result_display.xml +++ b/app/src/main/res/layout/activity_scan_result_display.xml @@ -8,6 +8,7 @@ android:background="@color/white" android:orientation="vertical" tools:context=".result.ScanResultDisplayActivity"> + + android:textSize="14sp" + app:fontType="bold" /> + android:textSize="14sp" + app:fontType="bold" /> - + android:layout_height="match_parent" + android:background="@drawable/bg_rectangle_white_top_20" + android:layout_marginTop="20dp"> + + + + - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_scanning.xml b/app/src/main/res/layout/activity_scanning.xml index aa4459b..cee4ad8 100644 --- a/app/src/main/res/layout/activity_scanning.xml +++ b/app/src/main/res/layout/activity_scanning.xml @@ -34,67 +34,71 @@ app:fontType="bold" /> - - - + android:orientation="horizontal" + android:paddingTop="150dp"> + + + 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" /> - + 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" /> - - + 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" /> + + + + + + + + + + - + + + + + - - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_video_play.xml b/app/src/main/res/layout/activity_video_play.xml new file mode 100644 index 0000000..478ab05 --- /dev/null +++ b/app/src/main/res/layout/activity_video_play.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_span_count_three_adapter.xml b/app/src/main/res/layout/file_span_count_three_adapter.xml index 908ef2f..af9fb5d 100644 --- a/app/src/main/res/layout/file_span_count_three_adapter.xml +++ b/app/src/main/res/layout/file_span_count_three_adapter.xml @@ -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"> - + android:layout_alignParentBottom="true"> + + + + + - + android:background="@drawable/photo_size_bg"> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/scan_result_documents_adapter.xml b/app/src/main/res/layout/scan_result_documents_adapter.xml new file mode 100644 index 0000000..1777d40 --- /dev/null +++ b/app/src/main/res/layout/scan_result_documents_adapter.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index eb2472f..03ff289 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -24,6 +24,7 @@ #15787880 #F2F2F7 #D9D9D9 + #99F2F2F7 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb6b458..4bb8853 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,12 +36,16 @@ Exit Allow Scanning… + Photos photos deleted photos + Videos videos deleted videos + Audios audios deleted audios + Documents documents deleted documents Finished! @@ -49,6 +53,7 @@ If you exit,the scanning results will be discarded.Are you sure you want to exit now? Date Size + Duration Layout Hide thumbnails (%d) Thumbnails refer to photos below 256 pixels @@ -64,6 +69,7 @@ OK Name Path + Type Resolution Recovering... It may take a few seconds to recover the file(s), please @@ -78,6 +84,8 @@ wait.. The file(s) will be completely deleted and cannot be recovered. Confirm delete? View + Sorry!No %s found! + All @@ -86,12 +94,27 @@ wait.. within 24 month Customize - + All 0-1 M 1-5 M >5 M + + + All + 0-500 KB + 500 KB-1 M + >1 M + + + + All + 0-5 minutes + 5-20 minutes + 20-60 minutes + >60 minutes + 2 columns 3 columns