隐藏缩略图的开关切换

This commit is contained in:
litingting 2025-10-15 18:31:33 +08:00
parent 592918d6ce
commit 37e894ad47
13 changed files with 539 additions and 153 deletions

View File

@ -7,7 +7,7 @@ import androidx.viewbinding.ViewBinding
import kotlin.let import kotlin.let
abstract class BaseAdapter<K, T : ViewBinding>( abstract class BaseAdapter<K, T : ViewBinding>(
protected val mContext: Context protected val mContext: Context,
) : RecyclerView.Adapter<BaseAdapter.VHolder<T>>() { ) : RecyclerView.Adapter<BaseAdapter.VHolder<T>>() {
protected val data: MutableList<K> = mutableListOf() protected val data: MutableList<K> = mutableListOf()
@ -28,6 +28,8 @@ abstract class BaseAdapter<K, T : ViewBinding>(
} }
fun getCurrentData() = data
override fun getItemCount(): Int = data.size override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VHolder<T> { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VHolder<T> {

View File

@ -0,0 +1,39 @@
package com.ux.video.file.filerecovery.base
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.viewbinding.ViewBinding
/**
* 高性能 RecyclerView 通用适配器基类支持 DiffUtil + ViewBinding
*/
abstract class DiffBaseAdapter<K, T : ViewBinding>(
protected val mContext: Context,
diffCallback: DiffUtil.ItemCallback<K>
) : ListAdapter<K, DiffBaseAdapter.VHolder<T>>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VHolder<T> {
return VHolder(getViewBinding(parent))
}
override fun onBindViewHolder(holder: VHolder<T>, position: Int) {
bindItem(holder, getItem(position))
}
/**
* 更新数据自动触发 DiffUtil 计算
*/
fun submitData(items: List<K>?) {
submitList(items ?: emptyList())
}
protected abstract fun getViewBinding(parent: ViewGroup): T
protected abstract fun bindItem(holder: VHolder<T>, item: K)
class VHolder<V : ViewBinding>(val vb: V) : androidx.recyclerview.widget.RecyclerView.ViewHolder(vb.root)
}

View File

@ -4,8 +4,11 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.ux.video.file.filerecovery.base.BaseAdapter import com.ux.video.file.filerecovery.base.BaseAdapter
import com.ux.video.file.filerecovery.base.DiffBaseAdapter
import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding
import com.ux.video.file.filerecovery.utils.Common import com.ux.video.file.filerecovery.utils.Common
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
@ -15,12 +18,14 @@ class PhotoDisplayDateAdapter(
mContext: Context, mContext: Context,
var mColumns: Int, var mColumns: Int,
var viewModel: ScanRepository, var viewModel: ScanRepository,
var onSelectedUpdate: (updatePath: String, isAdd: Boolean) -> Unit, var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, isAdd: Boolean) -> Unit,
var clickItem: (item: ResultPhotosFiles) -> Unit var clickItem: (item: ResultPhotosFiles) -> Unit
) : ) :
BaseAdapter<Pair<String, List<ResultPhotosFiles>>, PhotoDisplayDateAdapterBinding>(mContext) { BaseAdapter<Pair<String, List<ResultPhotosFiles>>, PhotoDisplayDateAdapterBinding>(mContext) {
private var allSelected: Boolean? = null private var allSelected: Boolean? = null
// private var hideThumbnails = false
override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateAdapterBinding = override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateAdapterBinding =
PhotoDisplayDateAdapterBinding.inflate( PhotoDisplayDateAdapterBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
@ -28,13 +33,23 @@ class PhotoDisplayDateAdapter(
false false
) )
fun updateHideThumbnails(isChecked: Boolean) {
// hideThumbnails = isChecked
notifyDataSetChanged()
}
/** /**
* 返回所有嵌套的数据量总数 * 返回所有嵌套的数据量总数
*/ */
fun getTotalChildCount(): Int { fun getTotalChildCount(hideThumbnails: Boolean): Int {
if(hideThumbnails){
return data.sumOf { it.second.filter { !it.isThumbnail }.size }
}else{
return data.sumOf { it.second.size } return data.sumOf { it.second.size }
} }
}
/** /**
* activity页面上点击全选按钮执行 * activity页面上点击全选按钮执行
*/ */
@ -42,7 +57,7 @@ class PhotoDisplayDateAdapter(
allSelected = allSelect allSelected = allSelect
data.forEach { data.forEach {
it.second.forEach { item -> it.second.forEach { item ->
onSelectedUpdate(item.path.toString(),allSelect) onSelectedUpdate(item, allSelect)
} }
} }
notifyDataSetChanged() notifyDataSetChanged()
@ -59,26 +74,36 @@ class PhotoDisplayDateAdapter(
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun bindItem( override fun bindItem(
holder: VHolder<PhotoDisplayDateAdapterBinding>, holder: VHolder<PhotoDisplayDateAdapterBinding>,
item: Pair<String, List<ResultPhotosFiles>> item: Pair<String, List<ResultPhotosFiles>>
) { ) {
holder.vb.run { holder.vb.run {
item.run { item.run {
val (date, files) = item val (date, files) = item
val childAdapter = PhotoDisplayDateChildAdapter( val childAdapter = PhotoDisplayDateChildAdapter(
mContext, mContext,
mColumns, mColumns,
viewModel, viewModel,
{ path, addOrRemove, isDateAllSelected -> { resultPhotosFiles, addOrRemove, isDateAllSelected ->
//点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里 //点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里
tvDayAllSelect.isSelected = isDateAllSelected tvDayAllSelect.isSelected = isDateAllSelected
onSelectedUpdate(path.toString(),addOrRemove) onSelectedUpdate(resultPhotosFiles, addOrRemove)
},clickItem).apply { setData(files) } }, { updateHideThumbnails->
tvDayAllSelect.isSelected = updateHideThumbnails
},clickItem
).apply { setData(files) }
// if (hideThumbnails && files.filter { !it.isThumbnail }.isEmpty()) {
// holder.vb.root.isVisible = false
// return
// }else{
// holder.vb.root.isVisible = true
// childAdapter.updateHideThumbnails(hideThumbnails)
// }
allSelected?.let { allSelected?.let {
childAdapter.setAllSelected(it) childAdapter.setAllSelected(it)
@ -90,12 +115,9 @@ class PhotoDisplayDateAdapter(
textDate.text = date textDate.text = date
textChildCounts.text = "(${files.size})" textChildCounts.text = "(${files.size})"
recyclerChild.apply { recyclerChild.apply {
layoutManager = GridLayoutManager(context, mColumns) layoutManager = GridLayoutManager(context, mColumns)
val gridSpacingItemDecoration =
GridSpacingItemDecoration(mColumns, Common.itemSpacing, Common.horizontalSpacing)
Common.showLog("---------mColumns=${mColumns}")
// resetItemDecorationOnce(gridSpacingItemDecoration)
adapter = childAdapter adapter = childAdapter
isNestedScrollingEnabled = false isNestedScrollingEnabled = false
} }
@ -104,5 +126,21 @@ class PhotoDisplayDateAdapter(
} }
} }
object ItemDiffCallback : DiffUtil.ItemCallback<Pair<String, List<ResultPhotosFiles>>>() {
override fun areItemsTheSame(
oldItem: Pair<String, List<ResultPhotosFiles>>,
newItem: Pair<String, List<ResultPhotosFiles>>
): Boolean {
return oldItem.first == newItem.first
}
override fun areContentsTheSame(
oldItem: Pair<String, List<ResultPhotosFiles>>,
newItem: Pair<String, List<ResultPhotosFiles>>
): Boolean {
return oldItem == newItem
}
}
} }

View File

@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
@ -36,14 +37,18 @@ class PhotoDisplayDateChildAdapter(
* @param addOrRemove 选中还是取消选中 * @param addOrRemove 选中还是取消选中
* @param dateAllSelected 这组数据是否全部选中某一天 * @param dateAllSelected 这组数据是否全部选中某一天
*/ */
var onSelectedUpdate: (updatePath: String, addOrRemove: Boolean, dateAllSelected: Boolean) -> Unit, var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, addOrRemove: Boolean, dateAllSelected: Boolean) -> Unit,
var hideThumbnailsUpdate:(dateAllSelected: Boolean)-> Unit,
var clickItem:(item:ResultPhotosFiles)-> Unit var clickItem:(item:ResultPhotosFiles)-> Unit
) : ) :
NewBaseAdapter<ResultPhotosFiles>(mContext) { NewBaseAdapter<ResultPhotosFiles>(mContext) {
// private var hideThumbnails: Boolean? = null
//日期组某一天的数据选择状态维护 //日期组某一天的数据选择状态维护
val dateSelectedMap = mutableSetOf<String>() val dateSelectedMap = mutableSetOf<ResultPhotosFiles>()
//实际显示数据集合(包含隐藏的缩略图)
// val visibleList = data.toMutableSet()
companion object { companion object {
private const val TYPE_TWO = 2 private const val TYPE_TWO = 2
@ -51,10 +56,17 @@ class PhotoDisplayDateChildAdapter(
private const val TYPE_FOUR = 4 private const val TYPE_FOUR = 4
} }
// fun getVisibleList() = visibleList
fun updateHideThumbnails(isChecked: Boolean){
// hideThumbnails = isChecked
notifyDataSetChanged()
hideThumbnailsUpdate.invoke(getVisibleCount(dateSelectedMap.toMutableList(),isChecked) == getVisibleCount(data,isChecked))
}
fun setAllSelected(isAdd: Boolean) { fun setAllSelected(isAdd: Boolean) {
data.forEach { data.forEach {
addOrRemove(it.path.toString(), isAdd) addOrRemove(it, isAdd)
} }
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -120,10 +132,12 @@ class PhotoDisplayDateChildAdapter(
when (holder) { when (holder) {
is TwoHolder -> holder.vb.run { is TwoHolder -> holder.vb.run {
// root.isVisible = !(hideThumbnails == true && item.isThumbnail)
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item) initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item)
} }
is ThreeHolder -> holder.vb.run { is ThreeHolder -> holder.vb.run {
// root.isVisible = !(hideThumbnails == true && item.isThumbnail)
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item) initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item)
} }
} }
@ -145,17 +159,17 @@ class PhotoDisplayDateChildAdapter(
item: ResultPhotosFiles item: ResultPhotosFiles
) { ) {
item.run { item.run {
viewModel.checkIsSelect(path.toString()).let { viewModel.checkIsSelect(this).let {
imageSelectStatus.isSelected = it imageSelectStatus.isSelected = it
addOrRemove(path.toString(), it) addOrRemove(this, it)
} }
imageSelectStatus.setOnClickListener { imageSelectStatus.setOnClickListener {
it.isSelected = !it.isSelected it.isSelected = !it.isSelected
it.isSelected.let { newStatus -> it.isSelected.let { newStatus ->
addOrRemove(path.toString(), newStatus) addOrRemove(this, newStatus)
} }
} }
textSize.text = Common.formatFileSize(mContext, size) textSize.text = sizeString
Glide.with(mContext) Glide.with(mContext)
.load(targetFile) .load(targetFile)
@ -197,14 +211,30 @@ class PhotoDisplayDateChildAdapter(
} }
private fun addOrRemove(path: String, boolean: Boolean) { private fun addOrRemove(resultPhotosFiles: ResultPhotosFiles, boolean: Boolean) {
if (boolean) { if (boolean) {
dateSelectedMap.add(path) dateSelectedMap.add(resultPhotosFiles)
} else { } else {
dateSelectedMap.remove(path) dateSelectedMap.remove(resultPhotosFiles)
} }
onSelectedUpdate.invoke(path, boolean, dateSelectedMap.size == itemCount) onSelectedUpdate.invoke(resultPhotosFiles, boolean, dateSelectedMap.size == data.size)
// updateSelected(resultPhotosFiles,boolean)
} }
private fun updateSelected(resultPhotosFiles: ResultPhotosFiles,boolean: Boolean){
// hideThumbnails?.let {
// onSelectedUpdate.invoke(resultPhotosFiles, boolean, getVisibleCount(dateSelectedMap.toMutableList(),it) == getVisibleCount(data,it))
// }
}
fun getVisibleCount(list: MutableList<ResultPhotosFiles> = data, hideThumbnails: Boolean): Int {
if(hideThumbnails){
return list.filter { !it.isThumbnail }.size
}else{
return list.size
}
}
} }

View File

@ -17,10 +17,10 @@ import com.ux.video.file.filerecovery.utils.Common.setItemSelect
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySize import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySize
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySizeList import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySizeList
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterRemoveThumbnailsAsync
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterThumbnailsAsync
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRange import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRange
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRangeList import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRangeList
//import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinMonths
//import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinMonthsList
import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat
import com.ux.video.file.filerecovery.utils.ExtendFunctions.mbToBytes import com.ux.video.file.filerecovery.utils.ExtendFunctions.mbToBytes
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
@ -28,6 +28,7 @@ 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.copySelectedFilesAsync
import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync
import com.ux.video.file.filerecovery.utils.ScanRepository import com.ux.video.file.filerecovery.utils.ScanRepository
import kotlinx.coroutines.launch
import java.util.Date import java.util.Date
class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() { class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
@ -90,12 +91,16 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
private lateinit var sortByDateReverse: List<Pair<String, List<ResultPhotosFiles>>> private lateinit var sortByDateReverse: List<Pair<String, List<ResultPhotosFiles>>>
private lateinit var sortedByDatePositive: List<Pair<String, List<ResultPhotosFiles>>> private lateinit var sortedByDatePositive: List<Pair<String, List<ResultPhotosFiles>>>
//最新显示的数据集合(包含缩略图 ,只保存当前筛选后或者排序后显示的数据不受switch切换影响
private var currentDateList: List<Pair<String, List<ResultPhotosFiles>>>? = null
private var currentSizeList: List<ResultPhotosFiles>? = null
//选中的所有数据集合(实际选中) //选中的所有数据集合(实际选中)
private lateinit var allSelectedSetList: Set<String> private lateinit var allSelectedSetList: Set<ResultPhotosFiles>
//选中的所有数据集合(筛选后的数据实际显示的所有选中) //选中的所有数据集合(筛选后的数据实际显示的所有选中)
private lateinit var filterSelectedSetList: Set<String> private lateinit var filterSelectedSetList: Set<ResultPhotosFiles>
private lateinit var mItemDecoration: GridSpacingItemDecoration private lateinit var mItemDecoration: GridSpacingItemDecoration
@ -124,11 +129,14 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}") Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
updateCurrentIsAllSelectStatus() updateCurrentIsAllSelectStatus()
} }
binding.imageViewBack.setOnClickListener { finish() }
list?.let { list?.let {
binding.tvThumbnailCounts.text =
getString(R.string.hide_thumbnails, it.filter { it.isThumbnail }.size)
//降序(最近的在前面) //降序(最近的在前面)
sortByDateReverse = Common.getSortByDayNewToOld(it) sortByDateReverse = Common.getSortByDayNewToOldInit(it)
//升序(时间最远的在前面) //升序(时间最远的在前面)
sortedByDatePositive = Common.getSortByDayOldToNew(sortByDateReverse) sortedByDatePositive = Common.getSortByDayOldToNew(sortByDateReverse)
sortBySizeBigToSmall = Common.getSortBySizeBigToSmall(it) sortBySizeBigToSmall = Common.getSortBySizeBigToSmall(it)
@ -137,8 +145,11 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
sizeSortAdapter = PhotoDisplayDateChildAdapter( sizeSortAdapter = PhotoDisplayDateChildAdapter(
this@PhotoSortingActivity, this@PhotoSortingActivity,
columns, viewModel, columns, viewModel,
{ path, isAdd, allSelected -> { resultPhotosFiles, isAdd, allSelected ->
viewModel.toggleSelection(isAdd, path) viewModel.toggleSelection(isAdd, resultPhotosFiles)
}, { hide ->
}) { item -> }) { item ->
startActivity( startActivity(
Intent( Intent(
@ -166,10 +177,47 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
}) })
}.apply { }.apply {
setData(sortByDateReverse) setData(sortByDateReverse)
resetCurrentDateList(sortByDateReverse)
} }
setDateAdapter() setDateAdapter()
setFilter() setFilter()
binding.run { 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 { tvRecover.setOnClickListener {
showRecoveringDialog() showRecoveringDialog()
lifecycleScope.copySelectedFilesAsync( lifecycleScope.copySelectedFilesAsync(
@ -209,29 +257,66 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
when (it) { when (it) {
SORT_ASC_DATE -> { SORT_ASC_DATE -> {
setDateAdapter() setDateAdapter()
dateAdapter?.setData(sortedByDatePositive) 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 sortReverse = false
} }
}
SORT_DESC_DATE -> { SORT_DESC_DATE -> {
setDateAdapter() setDateAdapter()
dateAdapter?.setData(sortByDateReverse) 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 sortReverse = true
} }
}
SORT_DESC_SIZE -> { SORT_DESC_SIZE -> {
setSizeAdapter() setSizeAdapter()
lifecycleScope.launch {
initGetCurrentSizeList().let {
val filterThumbnailsAsync =
if (switchHideThumbnails.isChecked) it.filterRemoveThumbnailsAsync() else it
val sortBySizeBigToSmall =
Common.getSortBySizeBigToSmall(filterThumbnailsAsync)
sizeSortAdapter?.setData(sortBySizeBigToSmall) sizeSortAdapter?.setData(sortBySizeBigToSmall)
resetCurrentSizeList(sortBySizeBigToSmall)
}
sortReverse = true sortReverse = true
} }
}
SORT_ASC_SIZE -> { SORT_ASC_SIZE -> {
setSizeAdapter() setSizeAdapter()
lifecycleScope.launch {
initGetCurrentSizeList().let {
val filterThumbnailsAsync =
if (switchHideThumbnails.isChecked) it.filterRemoveThumbnailsAsync() else it
val sortBySizeSmallToBig =
Common.getSortBySizeSmallToBig(filterThumbnailsAsync)
sizeSortAdapter?.setData(sortBySizeSmallToBig) sizeSortAdapter?.setData(sortBySizeSmallToBig)
resetCurrentSizeList(sortBySizeSmallToBig)
}
sortReverse = false sortReverse = false
} }
} }
} }
}
sortDialogFragment?.show(supportFragmentManager, "") sortDialogFragment?.show(supportFragmentManager, "")
} }
@ -254,25 +339,50 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
when (binding.recyclerView.adapter) { when (binding.recyclerView.adapter) {
is PhotoDisplayDateAdapter -> { is PhotoDisplayDateAdapter -> {
val adapter = binding.recyclerView.adapter as PhotoDisplayDateAdapter val adapter = binding.recyclerView.adapter as PhotoDisplayDateAdapter
binding.tvSelectAll.isSelected = it == adapter.getTotalChildCount() binding.tvSelectAll.isSelected = it == adapter.getTotalChildCount(false)
} }
is PhotoDisplayDateChildAdapter -> { is PhotoDisplayDateChildAdapter -> {
binding.tvSelectAll.isSelected = it == binding.recyclerView.adapter?.itemCount val adapter = binding.recyclerView.adapter as PhotoDisplayDateChildAdapter
} binding.tvSelectAll.isSelected =
it == adapter.itemCount
} }
} }
} }
}
private fun initGetCurrentSizeList(): List<ResultPhotosFiles> {
currentSizeList = currentSizeList ?: currentDateList?.flatMap { it.second }
return currentSizeList!!
}
private fun resetCurrentSizeList(currentList: List<ResultPhotosFiles>) {
currentSizeList = currentList
currentDateList = null
}
private fun initGetCurrentDateList(): List<Pair<String, List<ResultPhotosFiles>>> {
currentDateList = currentDateList ?: Common.getSortByDayNewToOldInit(currentSizeList!!)
return currentDateList!!
}
private fun resetCurrentDateList(currentList: List<Pair<String, List<ResultPhotosFiles>>> ) {
currentDateList = currentList
currentSizeList = null
}
/**
* 重置适配器 日期显示和大小显示适配器切换
*/
private fun setDateAdapter() { private fun setDateAdapter() {
binding.recyclerView.run { binding.recyclerView.run {
adapter = dateAdapter?.apply { setColumns(columns) } adapter = dateAdapter?.apply { setColumns(columns) }
layoutManager = LinearLayoutManager(this@PhotoSortingActivity) layoutManager = LinearLayoutManager(this@PhotoSortingActivity)
val bPx = 16.dpToPx(context) setPadding(0, 0, 0, 70.dpToPx(context))
setPadding(0, 0, 0, 0)
clipToPadding = false clipToPadding = false
} }
} }
@ -280,7 +390,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
binding.recyclerView.run { binding.recyclerView.run {
val aPx = 16.dpToPx(context) val aPx = 16.dpToPx(context)
val bPx = 6.dpToPx(context) val bPx = 6.dpToPx(context)
setPadding(aPx, 0, bPx, 0) setPadding(aPx, 0, bPx, 70.dpToPx(context))
clipToPadding = false clipToPadding = false
layoutManager = GridLayoutManager(context, columns) layoutManager = GridLayoutManager(context, columns)
adapter = sizeSortAdapter adapter = sizeSortAdapter
@ -293,6 +403,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
*/ */
private fun setFilter() { private fun setFilter() {
//日期筛选 //日期筛选
binding.run { binding.run {
filterDateLayout.setOnClickListener { filterDateLayout.setOnClickListener {
setItemSelect(it as LinearLayout, true) setItemSelect(it as LinearLayout, true)
@ -304,21 +415,25 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
when (clickValue) { when (clickValue) {
data[0] -> { data[0] -> {
filterDate = FILTER_DATE_ALL filterDate = FILTER_DATE_ALL
titleDate.text = clickValue
startFilter() startFilter()
} }
data[1] -> { data[1] -> {
filterDate = FILTER_DATE_1 filterDate = FILTER_DATE_1
titleDate.text = clickValue
startFilter() startFilter()
} }
data[2] -> { data[2] -> {
filterDate = FILTER_DATE_6 filterDate = FILTER_DATE_6
titleDate.text = clickValue
startFilter() startFilter()
} }
data[3] -> { data[3] -> {
filterDate = FILTER_DATE_24 filterDate = FILTER_DATE_24
titleDate.text = clickValue
startFilter() startFilter()
} }
@ -327,6 +442,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
if (showDialog) if (showDialog)
showStartDateDialog(true, null) showStartDateDialog(true, null)
else { else {
titleDate.text = clickValue
startFilter() startFilter()
} }
@ -360,6 +476,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
data, data,
0, 0,
{ clickValue -> { clickValue ->
titleSize.text = clickValue
when (clickValue) { when (clickValue) {
data[0] -> filterSize = FILTER_SIZE_ALL data[0] -> filterSize = FILTER_SIZE_ALL
data[1] -> filterSize = FILTER_SIZE_1 data[1] -> filterSize = FILTER_SIZE_1
@ -430,8 +547,6 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
tvDelete.text = getString(R.string.delete_placeholder, selectedCounts) tvDelete.text = getString(R.string.delete_placeholder, selectedCounts)
tvRecover.text = getString(R.string.recover_placeholder, selectedCounts) tvRecover.text = getString(R.string.recover_placeholder, selectedCounts)
} }
} }
/** /**
@ -452,15 +567,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
) )
.filterBySize(filterSizeCovert.first, filterSizeCovert.second) .filterBySize(filterSizeCovert.first, filterSizeCovert.second)
.let { currentList -> .let { currentList ->
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据 checkRefreshDisPlaySelected(list1 = currentList)
val checkSelectListContain =
Common.checkSelectListContainDate(currentList, allSelectedSetList)
updateButtonCounts(checkSelectListContain.first)
viewModel.filterResetDisplayFlow(checkSelectListContain.second)
Common.showLog("筛选后重置 allSelectedSetList=${allSelectedSetList.size} filterSelectedSetList=${filterSelectedSetList.size} Thread=${Thread.currentThread().name}")
dateAdapter?.resetAllValue(null) dateAdapter?.resetAllValue(null)
dateAdapter?.setData(currentList) dateAdapter?.setData(currentList)
resetCurrentDateList(currentList)
} }
@ -476,30 +586,57 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
) )
.filterBySizeList(filterSizeCovert.first, filterSizeCovert.second) .filterBySizeList(filterSizeCovert.first, filterSizeCovert.second)
.let { currentList -> .let { currentList ->
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据 checkRefreshDisPlaySelected(list2 = currentList)
val checkSelectListContain =
Common.checkSelectListContainSize(currentList, allSelectedSetList)
updateButtonCounts(checkSelectListContain.first)
viewModel.filterResetDisplayFlow(checkSelectListContain.second)
sizeSortAdapter?.setData(currentList) sizeSortAdapter?.setData(currentList)
resetCurrentSizeList(currentList)
} }
} }
} }
} }
/**
* 数据筛选或者缩略图切换显示后对比刷新实际显示的选中数据
*/
private fun checkRefreshDisPlaySelected(
list1: List<Pair<String, List<ResultPhotosFiles>>>? = null,
list2: List<ResultPhotosFiles>? = null
) {
lifecycleScope.launch {
list1?.let {
val checkSelectListContain =
Common.checkSelectListContainDateAsync(it, allSelectedSetList)
viewModel.filterResetDisplayFlow(checkSelectListContain.second)
}
list2?.let {
val checkSelectListContain =
Common.checkSelectListContainSize(it, allSelectedSetList)
viewModel.filterResetDisplayFlow(checkSelectListContain.second)
}
}
}
private fun filterSizeCovert(filterSize: Int): Pair<Long, Long> { private fun filterSizeCovert(filterSize: Int): Pair<Long, Long> {
return when (filterSize) { return when (filterSize) {
FILTER_SIZE_ALL -> Pair(-1L, -1L) FILTER_SIZE_ALL -> Pair(-1L, -1L)
FILTER_SIZE_1 -> Pair(0L, 1.mbToBytes()) FILTER_SIZE_1 -> Pair(0L, 1.mbToBytes())
FILTER_SIZE_5 -> Pair(1L, 5.mbToBytes()) FILTER_SIZE_5 -> Pair(1.mbToBytes(), 5.mbToBytes())
FILTER_SIZE_OVER_5 -> Pair(5.mbToBytes(), Long.MAX_VALUE) FILTER_SIZE_OVER_5 -> Pair(5.mbToBytes(), Long.MAX_VALUE)
else -> Pair(-1L, -1L) else -> Pair(-1L, -1L)
} }
} }
/**
* 显示筛选开始日期弹窗
*/
private fun showStartDateDialog(isNeedSetSelected: Boolean, currentDate: Date?) { private fun showStartDateDialog(isNeedSetSelected: Boolean, currentDate: Date?) {
dialogCustomerDateStart = dialogCustomerDateStart ?: DatePickerDialogFragment( dialogCustomerDateStart = dialogCustomerDateStart ?: DatePickerDialogFragment(
@ -516,14 +653,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
Log.d("showStartDateDialog", "isFirst=${isNeedSetSelected}--------") Log.d("showStartDateDialog", "isFirst=${isNeedSetSelected}--------")
showEndDateDialog(isNeedSetSelected, null) showEndDateDialog(isNeedSetSelected, null)
// if (isFirst) {
// showEndDateDialog(true)
// } else {
// if (filterDate == FILTER_DATE_CUSTOMER) {
// startFilter()
// filterDatePopupWindows?.dismiss()
// }
// }
} }
onClickCancel = { onClickCancel = {
@ -534,6 +664,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
/**
* 显示筛选结束日期弹窗
*/
private fun showEndDateDialog(isNeedSetSelected: Boolean, currentDate: Date?) { private fun showEndDateDialog(isNeedSetSelected: Boolean, currentDate: Date?) {
dialogCustomerDateEnd = dialogCustomerDateEnd ?: DatePickerDialogFragment( dialogCustomerDateEnd = dialogCustomerDateEnd ?: DatePickerDialogFragment(
this, this,
@ -550,6 +683,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
filterDatePopupWindows?.updateSelectPos(4, filterStartDate!!, filterEndDate!!) filterDatePopupWindows?.updateSelectPos(4, filterStartDate!!, filterEndDate!!)
startFilter() startFilter()
filterDatePopupWindows?.dismiss() filterDatePopupWindows?.dismiss()
binding.titleDate.text = resources.getStringArray(R.array.filter_date)[4]
} else { } else {
filterDatePopupWindows?.updateStartEndDate(filterStartDate, filterEndDate) filterDatePopupWindows?.updateStartEndDate(filterStartDate, filterEndDate)
if (filterDate == FILTER_DATE_CUSTOMER) { if (filterDate == FILTER_DATE_CUSTOMER) {
@ -566,6 +700,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
} }
} }
/**
* 显示恢复中弹窗
*/
private fun showRecoveringDialog() { private fun showRecoveringDialog() {
dialogRecovering = dialogRecovering =
dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size) { dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size) {
@ -574,6 +712,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
dialogRecovering?.show(supportFragmentManager, "") dialogRecovering?.show(supportFragmentManager, "")
} }
/**
* 显示删除中弹窗
*/
private fun showDeletingDialog() { private fun showDeletingDialog() {
dialogDeleting = dialogDeleting ?: DeletingDialogFragment(filterSelectedSetList.size) { dialogDeleting = dialogDeleting ?: DeletingDialogFragment(filterSelectedSetList.size) {
complete() complete()

View File

@ -10,9 +10,23 @@ data class ResultPhotosFiles(
val name: String, val name: String,
val path: String? = null, val path: String? = null,
val size: Long, // 字节 val size: Long, // 字节
val sizeString: String,
val lastModified: Long, // 时间戳 val lastModified: Long, // 时间戳
var resolution: String // 尺寸 var resolution: String // 尺寸
) : Parcelable { ) : Parcelable {
val targetFile: File? val targetFile: File?
get() = path?.let { File(it) } get() = path?.let { File(it) }
//是否为缩略图文件(宽高任一小于 256
val isThumbnail: Boolean
get() {
val parts = resolution.lowercase().split("*").mapNotNull {
it.trim().toIntOrNull()
}
if (parts.size == 2) {
val (width, height) = parts
return width < 256 || height < 256
}
return false
}
} }

View File

@ -111,7 +111,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
val total = 800 val total = 800
lifecycleScope.launch { lifecycleScope.launch {
val root = Environment.getExternalStorageDirectory() val root = Environment.getExternalStorageDirectory()
ScanManager.scanAllDocuments(root, type = scanType).flowOn(Dispatchers.IO).collect { ScanManager.scanAllDocuments(this@ScanningActivity,root, type = scanType).flowOn(Dispatchers.IO).collect {
when (it) { when (it) {
is ScanState.Progress -> { is ScanState.Progress -> {
updateProgress(it) updateProgress(it)
@ -131,7 +131,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
lifecycleScope.launch { lifecycleScope.launch {
val root = Environment.getExternalStorageDirectory() val root = Environment.getExternalStorageDirectory()
ScanManager.scanHiddenPhotoAsync(root, type = scanType).flowOn(Dispatchers.IO).collect { ScanManager.scanHiddenPhotoAsync(this@ScanningActivity,root, type = scanType).flowOn(Dispatchers.IO).collect {
when (it) { when (it) {
is ScanState.Progress -> { is ScanState.Progress -> {
updateProgress(it) updateProgress(it)

View File

@ -12,6 +12,8 @@ import com.ux.video.file.filerecovery.App
import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.R
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.collections.sortedBy import kotlin.collections.sortedBy
@ -40,9 +42,7 @@ object Common {
/** /**
* 默认按照日期分类将最新的排前面 降序 * 默认按照日期分类将最新的排前面 降序
*/ */
fun getSortByDayNewToOld(list: ArrayList<ResultPhotosFiles>): List<Pair<String, List<ResultPhotosFiles>>> { fun getSortByDayNewToOldInit(list: List<ResultPhotosFiles>): List<Pair<String, List<ResultPhotosFiles>>> {
val grouped = list.groupBy { val grouped = list.groupBy {
dateFormat.format(Date(it.lastModified)) dateFormat.format(Date(it.lastModified))
} }
@ -52,6 +52,16 @@ object Common {
return parentData return parentData
} }
fun getSortByDayNewToOld(
list: List<Pair<String, List<ResultPhotosFiles>>>
): List<Pair<String, List<ResultPhotosFiles>>> {
return list.sortedByDescending { pair ->
dateFormat.parse(pair.first)?.time ?: 0L
}
}
/** /**
* 按照日期排序 时间最早的排前面 升序 * 按照日期排序 时间最早的排前面 升序
* *
@ -62,14 +72,14 @@ object Common {
/** /**
* 按照文件大小排序将最大的排前面 降序 * 按照文件大小排序将最大的排前面 降序
*/ */
fun getSortBySizeBigToSmall(list: ArrayList<ResultPhotosFiles>) = list.sortedByDescending { fun getSortBySizeBigToSmall(list: List<ResultPhotosFiles>) = list.sortedByDescending {
it.size it.size
} }
/** /**
* 按照文件大小排序将最小的排前面 升序 * 按照文件大小排序将最小的排前面 升序
*/ */
fun getSortBySizeSmallToBig(list: ArrayList<ResultPhotosFiles>) = list.sortedBy { fun getSortBySizeSmallToBig(list: List<ResultPhotosFiles>) = list.sortedBy {
it.size it.size
} }
@ -81,16 +91,19 @@ object Common {
if (size < 1024) { if (size < 1024) {
return "$size B" return "$size B"
} }
val kb = size / 1024.0 val kb = size / 1024.0
if (kb < 1024) { if (kb < 1024) {
return String.format(context.getString(R.string.size_kb), kb) return String.format(Locale.CHINA, context.getString(R.string.size_kb), kb)
} }
val mb = kb / 1024.0 val mb = kb / 1024.0
if (mb < 1024) { if (mb < 1024) {
return String.format(context.getString(R.string.size_kb), mb) return String.format(Locale.CHINA, context.getString(R.string.size_mb), mb)
} }
val gb = mb / 1024.0 val gb = mb / 1024.0
return String.format(context.getString(R.string.size_gb), gb) return String.format(Locale.CHINA, context.getString(R.string.size_gb), gb)
} }
@ -130,12 +143,7 @@ object Common {
/** /**
* 设置全部子View的选中 * 设置全部子View的选中
*/ */
// fun setItemSelect(view: ViewGroup, boolean: Boolean) {
// for (i in 0 until view.childCount) {
// val child = view.getChildAt(i)
// child.isSelected = boolean
// }
// }
fun setItemSelect(view: View, selected: Boolean) { fun setItemSelect(view: View, selected: Boolean) {
view.isSelected = selected view.isSelected = selected
if (view is ViewGroup) { if (view is ViewGroup) {
@ -154,14 +162,14 @@ object Common {
*/ */
fun checkSelectListContainDate( fun checkSelectListContainDate(
list: List<Pair<String, List<ResultPhotosFiles>>>, list: List<Pair<String, List<ResultPhotosFiles>>>,
selected: Set<String> selected: Set<ResultPhotosFiles>
): Pair<Int, MutableSet<String>> { ): Pair<Int, MutableSet<ResultPhotosFiles>> {
val currentSelected = mutableSetOf<String>() val currentSelected = mutableSetOf<ResultPhotosFiles>()
val totalSelectedCount = list.sumOf { pair -> val totalSelectedCount = list.sumOf { pair ->
pair.second.count { pair.second.count {
val isSelected = it.path in selected val isSelected = it in selected
if (isSelected) currentSelected.add(it.path.toString()) if (isSelected) currentSelected.add(it)
isSelected isSelected
} }
} }
@ -170,20 +178,74 @@ object Common {
return totalSelectedCount to currentSelected return totalSelectedCount to currentSelected
} }
suspend fun checkSelectListContainDateAsync(
list: List<Pair<String, List<ResultPhotosFiles>>>,
selected: Set<ResultPhotosFiles>
): Pair<Int, MutableSet<ResultPhotosFiles>> = withContext(Dispatchers.Default) {
val currentSelected = mutableSetOf<ResultPhotosFiles>()
var totalSelectedCount = 0
fun checkSelectListContainSize( // 高效遍历外层 + 内层列表
for (pair in list) {
for (file in pair.second) {
if (file in selected) {
totalSelectedCount++
currentSelected.add(file)
}
}
}
totalSelectedCount to currentSelected
}
// fun checkSelectListContainSize(
// list: List<ResultPhotosFiles>,
// selected: Set<String>
// ): Pair<Int, MutableSet<String>> {
// val currentSelected = mutableSetOf<String>()
// val totalSelectedCount = list.count {
// val isSelected = it.path in selected
// if (isSelected) currentSelected.add(it.path.toString())
// isSelected
// }
// showLog("-------totalSelectedCount 222=${totalSelectedCount}")
// return totalSelectedCount to currentSelected
// }
suspend fun checkSelectListContainSize(
list: List<ResultPhotosFiles>, list: List<ResultPhotosFiles>,
selected: Set<String> selected: Set<ResultPhotosFiles>
): Pair<Int, MutableSet<String>> { ): Pair<Int, MutableSet<ResultPhotosFiles>> = withContext(Dispatchers.Default) {
val currentSelected = mutableSetOf<String>() val currentSelected = mutableSetOf<ResultPhotosFiles>()
val totalSelectedCount = list.count { var totalSelectedCount = 0
val isSelected = it.path in selected
if (isSelected) currentSelected.add(it.path.toString()) // 高效遍历外层 + 内层列表
isSelected for (pair in list) {
if (pair in selected) {
totalSelectedCount++
currentSelected.add(pair)
} }
showLog("-------totalSelectedCount 222=${totalSelectedCount}")
return totalSelectedCount to currentSelected
} }
totalSelectedCount to currentSelected
}
/**
* 去掉缩略图的集合
*/
suspend fun filterThumbnailsAsync(
originalList: MutableList<Pair<String, List<ResultPhotosFiles>>>
): List<Pair<String, List<ResultPhotosFiles>>> = withContext(Dispatchers.Default) {
originalList.asSequence()
.map { (key, files) ->
key to files.asSequence().filter { !it.isThumbnail }.toList()
}
.filter { it.second.isNotEmpty() }
.toList()
}
fun getFormatDate(time: Long): String { fun getFormatDate(time: Long): String {
return dateFormat.format(Date(time)) return dateFormat.format(Date(time))

View File

@ -8,6 +8,8 @@ import android.os.Parcelable
import android.util.TypedValue import android.util.TypedValue
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Date import java.util.Date
object ExtendFunctions { object ExtendFunctions {
@ -176,7 +178,31 @@ object ExtendFunctions {
} }
} }
fun Int.mbToBytes(): Long { fun Int.mbToBytes(): Long {
return this * 1024L * 1024L return this * 1000L * 1000L
}
/**
* 移除掉缩略图后的数据
*/
suspend fun List<Pair<String, List<ResultPhotosFiles>>>.filterThumbnailsAsync(): List<Pair<String, List<ResultPhotosFiles>>> =
withContext(Dispatchers.Default) {
this@filterThumbnailsAsync.asSequence()
.mapNotNull { (key, files) ->
val filtered = files.asSequence().filter { !it.isThumbnail }.toList()
if (filtered.isNotEmpty()) key to filtered else null
}
.toList()
}
/**
* 移除掉缩略图后的数据
*/
suspend fun List<ResultPhotosFiles>.filterRemoveThumbnailsAsync(): List<ResultPhotosFiles> =
withContext(Dispatchers.Default) {
this@filterRemoveThumbnailsAsync.asSequence()
.filter { !it.isThumbnail } // 去掉 isThumbnail = true 的项
.toList()
} }

View File

@ -1,8 +1,14 @@
package com.ux.video.file.filerecovery.utils package com.ux.video.file.filerecovery.utils
import android.content.Context
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.OpenableColumns
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import com.ux.video.file.filerecovery.photo.ResultPhotos import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import com.ux.video.file.filerecovery.result.ScanningActivity import com.ux.video.file.filerecovery.result.ScanningActivity
@ -48,6 +54,7 @@ object ScanManager {
* 扫描所有文件 * 扫描所有文件
*/ */
fun scanAllDocuments( fun scanAllDocuments(
context: Context,
root: File, maxDepth: Int = 5, root: File, maxDepth: Int = 5,
maxFiles: Int = 5000, maxFiles: Int = 5000,
type: Int type: Int
@ -100,6 +107,7 @@ object ScanManager {
name = file.name, name = file.name,
path = file.absolutePath, path = file.absolutePath,
size = file.length(), size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()),
lastModified = file.lastModified(), lastModified = file.lastModified(),
resolution = getImageSize(file).run { resolution = getImageSize(file).run {
"$first*$second" "$first*$second"
@ -110,6 +118,7 @@ object ScanManager {
} }
emit(ScanState.Complete(ArrayList(map))) emit(ScanState.Complete(ArrayList(map)))
} }
private fun getImageSize(file: File): Pair<Int, Int> { private fun getImageSize(file: File): Pair<Int, Int> {
val options = BitmapFactory.Options() val options = BitmapFactory.Options()
options.inJustDecodeBounds = true options.inJustDecodeBounds = true
@ -125,12 +134,14 @@ object ScanManager {
* @param maxFiles // 最大收集的文件数 * @param maxFiles // 最大收集的文件数
*/ */
fun scanHiddenPhotoAsync( fun scanHiddenPhotoAsync(
context: Context,
root: File, maxDepth: Int = 5, root: File, maxDepth: Int = 5,
maxFiles: Int = 5000, type: Int maxFiles: Int = 5000, type: Int
): Flow<ScanState> = flow { ): Flow<ScanState> = flow {
val result = mutableMapOf<String, MutableList<File>>() val result = mutableMapOf<String, MutableList<File>>()
var fileCount = 0 var fileCount = 0
@RequiresApi(Build.VERSION_CODES.R)
suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) { suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) {
if (!dir.exists() || !dir.isDirectory) return if (!dir.exists() || !dir.isDirectory) return
if (depth > maxDepth || fileCount >= maxFiles) return if (depth > maxDepth || fileCount >= maxFiles) return
@ -181,17 +192,30 @@ object ScanManager {
name = file.name, name = file.name,
path = file.absolutePath, path = file.absolutePath,
size = file.length(), size = file.length(),
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()),
lastModified = file.lastModified(), lastModified = file.lastModified(),
resolution = getImageSize(file).run { resolution = getImageSize(file).run {
"$first*$second" "$first*$second"
} }
) )
} }
ResultPhotos(dir, ArrayList(resultPhotosFilesList)) ResultPhotos(dir, ArrayList(resultPhotosFilesList))
} }
emit(ScanState.Complete(ArrayList(map))) emit(ScanState.Complete(ArrayList(map)))
} }
private fun getFileSizeByMediaStore(context: Context, file: File): Long {
val uri = Uri.fromFile(file)
context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
?.use { cursor ->
val index = cursor.getColumnIndex(OpenableColumns.SIZE)
if (cursor.moveToFirst()) {
return cursor.getLong(index)
}
}
return file.length() // fallback
}
private fun isFormatFile(file: File, types: List<String>): Boolean { private fun isFormatFile(file: File, types: List<String>): Boolean {
val ext = file.extension.lowercase() val ext = file.extension.lowercase()
@ -223,7 +247,7 @@ object ScanManager {
* @param folder "AllRecovery/Photo" * @param folder "AllRecovery/Photo"
*/ */
fun CoroutineScope.copySelectedFilesAsync( fun CoroutineScope.copySelectedFilesAsync(
selectedSet: Set<String>, selectedSet: Set<ResultPhotosFiles>,
rootDir: File = Common.rootDir, rootDir: File = Common.rootDir,
folder: String, folder: String,
onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit, onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit,
@ -232,8 +256,8 @@ object ScanManager {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val targetDir = File(rootDir, folder) val targetDir = File(rootDir, folder)
if (!targetDir.exists()) targetDir.mkdirs() if (!targetDir.exists()) targetDir.mkdirs()
selectedSet.forEachIndexed { index, path -> selectedSet.forEachIndexed { index, resultPhotosFiles ->
val srcFile = File(path) val srcFile = File(resultPhotosFiles.path!!)
if (srcFile.exists() && srcFile.isFile) { if (srcFile.exists() && srcFile.isFile) {
val destFile = File(targetDir, srcFile.name) val destFile = File(targetDir, srcFile.name)
var success = false var success = false
@ -265,23 +289,23 @@ object ScanManager {
* *
*/ */
fun CoroutineScope.deleteFilesAsync( fun CoroutineScope.deleteFilesAsync(
selectedSet: Set<String>, selectedSet: Set<ResultPhotosFiles>,
onProgress: (currentCounts: Int, path: String, success: Boolean) -> Unit, onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit,
onComplete: (currentCounts: Int) -> Unit onComplete: (currentCounts: Int) -> Unit
) { ) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
var deletedCount = 0 var deletedCount = 0
selectedSet.forEachIndexed { index, path -> selectedSet.forEachIndexed { index, resultPhotosFiles ->
try { try {
val file = File(path) val file = File(resultPhotosFiles.path!!)
if (file.exists() && file.delete()) { if (file.exists() && file.delete()) {
deletedCount++ deletedCount++
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onProgress(index + 1, path, true) onProgress(index + 1, file.name, true)
} }
} catch (e: Exception) { } catch (e: Exception) {
onProgress(index + 1, path, false) onProgress(index + 1, resultPhotosFiles.path!!, false)
} }
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.ux.video.file.filerecovery.photo.ResultPhotos import com.ux.video.file.filerecovery.photo.ResultPhotos
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -19,24 +20,27 @@ class ScanRepository : ViewModel() {
private val _selectedLiveData = MutableLiveData<Set<String>>(emptySet()) private val _selectedLiveData = MutableLiveData<Set<ResultPhotosFiles>>(emptySet())
val selectedLiveData: LiveData<Set<String>> = _selectedLiveData val selectedLiveData: LiveData<Set<ResultPhotosFiles>> = _selectedLiveData
// 当前筛选显示的选中项 // 当前筛选显示的选中项
private val _selectedDisplayLiveData = MutableLiveData<Set<String>>(emptySet()) private val _selectedDisplayLiveData = MutableLiveData<Set<ResultPhotosFiles>>(emptySet())
val selectedDisplayLiveData: LiveData<Set<String>> = _selectedDisplayLiveData val selectedDisplayLiveData: LiveData<Set<ResultPhotosFiles>> = _selectedDisplayLiveData
fun toggleSelection(isAdd: Boolean, path: String) { fun toggleSelection(isAdd: Boolean, resultPhotosFiles: ResultPhotosFiles) {
val current = _selectedLiveData.value?.toMutableSet() ?: mutableSetOf() val current = _selectedLiveData.value?.toMutableSet() ?: mutableSetOf()
val currentDisplay = _selectedDisplayLiveData.value?.toMutableSet() ?: mutableSetOf() val currentDisplay = _selectedDisplayLiveData.value?.toMutableSet() ?: mutableSetOf()
resultPhotosFiles.let {
if (isAdd) { if (isAdd) {
current.add(path) current.add(it)
currentDisplay.add(path) currentDisplay.add(it)
} else { } else {
current.remove(path) current.remove(it)
currentDisplay.remove(path) currentDisplay.remove(it)
} }
}
Common.showLog( "toggleSelection------------ _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ") Common.showLog( "toggleSelection------------ _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ")
_selectedLiveData.value = current.toSet() _selectedLiveData.value = current.toSet()
_selectedDisplayLiveData.value = currentDisplay.toSet() _selectedDisplayLiveData.value = currentDisplay.toSet()
@ -47,9 +51,9 @@ class ScanRepository : ViewModel() {
/** /**
* 数据筛选后重置当前显示的选中集合 * 数据筛选后或者缩略图显示开关切换后 重置当前显示的选中集合
*/ */
fun filterResetDisplayFlow(list: MutableSet<String>){ fun filterResetDisplayFlow(list: MutableSet<ResultPhotosFiles>){
_selectedDisplayLiveData.value = list.toSet() _selectedDisplayLiveData.value = list.toSet()
Common.showLog( "筛选后重置 _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ") Common.showLog( "筛选后重置 _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ")
@ -57,13 +61,10 @@ class ScanRepository : ViewModel() {
fun checkIsSelect(path: String): Boolean { fun checkIsSelect(resultPhotosFiles: ResultPhotosFiles): Boolean {
val current = _selectedDisplayLiveData.value val current = _selectedLiveData.value
return current?.contains(path) == true return current?.contains(resultPhotosFiles) == true
} }
companion object {
val instance by lazy { ScanRepository() }
}
} }

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp" android:width="28dp"
android:height="16dp" android:height="28dp"
android:viewportWidth="1024" android:viewportWidth="1024"
android:viewportHeight="1024"> android:viewportHeight="1024">
<path <path
android:pathData="M518,87.1c-235.3,0 -426,190.7 -426,426s190.7,426 426,426 426,-190.7 426,-426 -190.7,-426 -426,-426zM518,855.1c-188.9,0 -342.1,-153.2 -342.1,-342.1S329,170.9 518,170.9s342.1,153.2 342.1,342.1 -153.2,342.1 -342.1,342.1z" android:pathData="M518,87.1c-235.3,0 -426,190.7 -426,426s190.7,426 426,426 426,-190.7 426,-426 -190.7,-426 -426,-426zM518,855.1c-188.9,0 -342.1,-153.2 -342.1,-342.1S329,170.9 518,170.9s342.1,153.2 342.1,342.1 -153.2,342.1 -342.1,342.1z"
android:fillColor="@color/white"/> android:fillColor="@color/black"/>
</vector> </vector>

View File

@ -55,6 +55,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/date" android:text="@string/date"
android:maxWidth="85dp"
android:maxLines="1"
android:ellipsize="end"
android:id="@+id/title_date"
android:textColor="@color/selector_black_blue" android:textColor="@color/selector_black_blue"
android:textSize="16sp" android:textSize="16sp"
app:fontType="bold" /> app:fontType="bold" />
@ -80,6 +84,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/size" android:text="@string/size"
android:maxWidth="85dp"
android:maxLines="1"
android:id="@+id/title_size"
android:ellipsize="end"
android:textColor="@color/selector_black_blue" android:textColor="@color/selector_black_blue"
android:textSize="16sp" android:textSize="16sp"
app:fontType="bold" /> app:fontType="bold" />