隐藏缩略图的开关切换
This commit is contained in:
parent
592918d6ce
commit
37e894ad47
@ -7,7 +7,7 @@ import androidx.viewbinding.ViewBinding
|
||||
import kotlin.let
|
||||
|
||||
abstract class BaseAdapter<K, T : ViewBinding>(
|
||||
protected val mContext: Context
|
||||
protected val mContext: Context,
|
||||
) : RecyclerView.Adapter<BaseAdapter.VHolder<T>>() {
|
||||
|
||||
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 onCreateViewHolder(parent: ViewGroup, viewType: Int): VHolder<T> {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -4,8 +4,11 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import 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.utils.Common
|
||||
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
|
||||
@ -15,12 +18,14 @@ class PhotoDisplayDateAdapter(
|
||||
mContext: Context,
|
||||
var mColumns: Int,
|
||||
var viewModel: ScanRepository,
|
||||
var onSelectedUpdate: (updatePath: String, isAdd: Boolean) -> Unit,
|
||||
var clickItem:(item:ResultPhotosFiles)-> Unit
|
||||
var onSelectedUpdate: (resultPhotosFiles: ResultPhotosFiles, isAdd: Boolean) -> Unit,
|
||||
var clickItem: (item: ResultPhotosFiles) -> Unit
|
||||
) :
|
||||
BaseAdapter<Pair<String, List<ResultPhotosFiles>>, PhotoDisplayDateAdapterBinding>(mContext) {
|
||||
|
||||
|
||||
private var allSelected: Boolean? = null
|
||||
// private var hideThumbnails = false
|
||||
override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateAdapterBinding =
|
||||
PhotoDisplayDateAdapterBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
@ -28,13 +33,23 @@ class PhotoDisplayDateAdapter(
|
||||
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 }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* activity页面上点击全选按钮执行
|
||||
*/
|
||||
@ -42,14 +57,14 @@ class PhotoDisplayDateAdapter(
|
||||
allSelected = allSelect
|
||||
data.forEach {
|
||||
it.second.forEach { item ->
|
||||
onSelectedUpdate(item.path.toString(),allSelect)
|
||||
onSelectedUpdate(item, allSelect)
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
allSelected = null
|
||||
}
|
||||
|
||||
fun resetAllValue(b: Boolean?){
|
||||
fun resetAllValue(b: Boolean?) {
|
||||
allSelected = b
|
||||
}
|
||||
|
||||
@ -59,26 +74,36 @@ class PhotoDisplayDateAdapter(
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun bindItem(
|
||||
holder: VHolder<PhotoDisplayDateAdapterBinding>,
|
||||
item: Pair<String, List<ResultPhotosFiles>>
|
||||
) {
|
||||
holder.vb.run {
|
||||
|
||||
item.run {
|
||||
val (date, files) = item
|
||||
val childAdapter = PhotoDisplayDateChildAdapter(
|
||||
mContext,
|
||||
mColumns,
|
||||
viewModel,
|
||||
{ path, addOrRemove, isDateAllSelected ->
|
||||
{ resultPhotosFiles, addOrRemove, isDateAllSelected ->
|
||||
//点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里
|
||||
tvDayAllSelect.isSelected = isDateAllSelected
|
||||
onSelectedUpdate(path.toString(),addOrRemove)
|
||||
},clickItem).apply { setData(files) }
|
||||
onSelectedUpdate(resultPhotosFiles, addOrRemove)
|
||||
}, { 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 {
|
||||
childAdapter.setAllSelected(it)
|
||||
@ -90,12 +115,9 @@ class PhotoDisplayDateAdapter(
|
||||
|
||||
textDate.text = date
|
||||
textChildCounts.text = "(${files.size})"
|
||||
|
||||
recyclerChild.apply {
|
||||
layoutManager = GridLayoutManager(context, mColumns)
|
||||
val gridSpacingItemDecoration =
|
||||
GridSpacingItemDecoration(mColumns, Common.itemSpacing, Common.horizontalSpacing)
|
||||
Common.showLog("---------mColumns=${mColumns}")
|
||||
// resetItemDecorationOnce(gridSpacingItemDecoration)
|
||||
adapter = childAdapter
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -7,6 +7,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
@ -36,14 +37,18 @@ class PhotoDisplayDateChildAdapter(
|
||||
* @param addOrRemove 选中还是取消选中
|
||||
* @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
|
||||
) :
|
||||
NewBaseAdapter<ResultPhotosFiles>(mContext) {
|
||||
|
||||
|
||||
// private var hideThumbnails: Boolean? = null
|
||||
//日期组某一天的数据选择状态维护
|
||||
val dateSelectedMap = mutableSetOf<String>()
|
||||
val dateSelectedMap = mutableSetOf<ResultPhotosFiles>()
|
||||
|
||||
//实际显示数据集合(包含隐藏的缩略图)
|
||||
// val visibleList = data.toMutableSet()
|
||||
|
||||
companion object {
|
||||
private const val TYPE_TWO = 2
|
||||
@ -51,10 +56,17 @@ class PhotoDisplayDateChildAdapter(
|
||||
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) {
|
||||
data.forEach {
|
||||
addOrRemove(it.path.toString(), isAdd)
|
||||
addOrRemove(it, isAdd)
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
@ -120,10 +132,12 @@ class PhotoDisplayDateChildAdapter(
|
||||
|
||||
when (holder) {
|
||||
is TwoHolder -> holder.vb.run {
|
||||
// root.isVisible = !(hideThumbnails == true && item.isThumbnail)
|
||||
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item)
|
||||
}
|
||||
|
||||
is ThreeHolder -> holder.vb.run {
|
||||
// root.isVisible = !(hideThumbnails == true && item.isThumbnail)
|
||||
initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item)
|
||||
}
|
||||
}
|
||||
@ -145,17 +159,17 @@ class PhotoDisplayDateChildAdapter(
|
||||
item: ResultPhotosFiles
|
||||
) {
|
||||
item.run {
|
||||
viewModel.checkIsSelect(path.toString()).let {
|
||||
viewModel.checkIsSelect(this).let {
|
||||
imageSelectStatus.isSelected = it
|
||||
addOrRemove(path.toString(), it)
|
||||
addOrRemove(this, it)
|
||||
}
|
||||
imageSelectStatus.setOnClickListener {
|
||||
it.isSelected = !it.isSelected
|
||||
it.isSelected.let { newStatus ->
|
||||
addOrRemove(path.toString(), newStatus)
|
||||
addOrRemove(this, newStatus)
|
||||
}
|
||||
}
|
||||
textSize.text = Common.formatFileSize(mContext, size)
|
||||
textSize.text = sizeString
|
||||
|
||||
Glide.with(mContext)
|
||||
.load(targetFile)
|
||||
@ -197,14 +211,30 @@ class PhotoDisplayDateChildAdapter(
|
||||
|
||||
}
|
||||
|
||||
private fun addOrRemove(path: String, boolean: Boolean) {
|
||||
private fun addOrRemove(resultPhotosFiles: ResultPhotosFiles, boolean: Boolean) {
|
||||
if (boolean) {
|
||||
dateSelectedMap.add(path)
|
||||
dateSelectedMap.add(resultPhotosFiles)
|
||||
} 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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.filterBySize
|
||||
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.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.mbToBytes
|
||||
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.deleteFilesAsync
|
||||
import com.ux.video.file.filerecovery.utils.ScanRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
|
||||
class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
@ -90,12 +91,16 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
private lateinit var sortByDateReverse: 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
|
||||
|
||||
|
||||
@ -124,11 +129,14 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}")
|
||||
updateCurrentIsAllSelectStatus()
|
||||
}
|
||||
binding.imageViewBack.setOnClickListener { finish() }
|
||||
|
||||
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)
|
||||
sortBySizeBigToSmall = Common.getSortBySizeBigToSmall(it)
|
||||
@ -137,8 +145,11 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
sizeSortAdapter = PhotoDisplayDateChildAdapter(
|
||||
this@PhotoSortingActivity,
|
||||
columns, viewModel,
|
||||
{ path, isAdd, allSelected ->
|
||||
viewModel.toggleSelection(isAdd, path)
|
||||
{ resultPhotosFiles, isAdd, allSelected ->
|
||||
viewModel.toggleSelection(isAdd, resultPhotosFiles)
|
||||
}, { hide ->
|
||||
|
||||
|
||||
}) { item ->
|
||||
startActivity(
|
||||
Intent(
|
||||
@ -166,10 +177,47 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
})
|
||||
}.apply {
|
||||
setData(sortByDateReverse)
|
||||
resetCurrentDateList(sortByDateReverse)
|
||||
}
|
||||
setDateAdapter()
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
lifecycleScope.copySelectedFilesAsync(
|
||||
@ -209,29 +257,66 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
when (it) {
|
||||
SORT_ASC_DATE -> {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SORT_DESC_DATE -> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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, "")
|
||||
}
|
||||
|
||||
@ -254,25 +339,50 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
when (binding.recyclerView.adapter) {
|
||||
is PhotoDisplayDateAdapter -> {
|
||||
val adapter = binding.recyclerView.adapter as PhotoDisplayDateAdapter
|
||||
binding.tvSelectAll.isSelected = it == adapter.getTotalChildCount()
|
||||
binding.tvSelectAll.isSelected = it == adapter.getTotalChildCount(false)
|
||||
}
|
||||
|
||||
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() {
|
||||
binding.recyclerView.run {
|
||||
adapter = dateAdapter?.apply { setColumns(columns) }
|
||||
layoutManager = LinearLayoutManager(this@PhotoSortingActivity)
|
||||
val bPx = 16.dpToPx(context)
|
||||
setPadding(0, 0, 0, 0)
|
||||
setPadding(0, 0, 0, 70.dpToPx(context))
|
||||
clipToPadding = false
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +390,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
binding.recyclerView.run {
|
||||
val aPx = 16.dpToPx(context)
|
||||
val bPx = 6.dpToPx(context)
|
||||
setPadding(aPx, 0, bPx, 0)
|
||||
setPadding(aPx, 0, bPx, 70.dpToPx(context))
|
||||
clipToPadding = false
|
||||
layoutManager = GridLayoutManager(context, columns)
|
||||
adapter = sizeSortAdapter
|
||||
@ -293,6 +403,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
*/
|
||||
private fun setFilter() {
|
||||
//日期筛选
|
||||
|
||||
binding.run {
|
||||
filterDateLayout.setOnClickListener {
|
||||
setItemSelect(it as LinearLayout, true)
|
||||
@ -304,40 +415,45 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
when (clickValue) {
|
||||
data[0] -> {
|
||||
filterDate = FILTER_DATE_ALL
|
||||
titleDate.text = clickValue
|
||||
startFilter()
|
||||
}
|
||||
|
||||
data[1] -> {
|
||||
filterDate = FILTER_DATE_1
|
||||
titleDate.text = clickValue
|
||||
startFilter()
|
||||
}
|
||||
|
||||
data[2] -> {
|
||||
filterDate = FILTER_DATE_6
|
||||
titleDate.text = clickValue
|
||||
startFilter()
|
||||
}
|
||||
|
||||
data[3] -> {
|
||||
filterDate = FILTER_DATE_24
|
||||
titleDate.text = clickValue
|
||||
startFilter()
|
||||
}
|
||||
|
||||
data[4] -> {
|
||||
filterDate = FILTER_DATE_CUSTOMER
|
||||
if (showDialog)
|
||||
showStartDateDialog(true,null)
|
||||
showStartDateDialog(true, null)
|
||||
else {
|
||||
titleDate.text = clickValue
|
||||
startFilter()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}, onResetDate = { isStart,currentDate ->
|
||||
}, onResetDate = { isStart, currentDate ->
|
||||
if (isStart) {
|
||||
showStartDateDialog(false,currentDate)
|
||||
showStartDateDialog(false, currentDate)
|
||||
} else {
|
||||
showEndDateDialog(false,currentDate)
|
||||
showEndDateDialog(false, currentDate)
|
||||
}
|
||||
|
||||
}) {
|
||||
@ -360,6 +476,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
data,
|
||||
0,
|
||||
{ clickValue ->
|
||||
titleSize.text = clickValue
|
||||
when (clickValue) {
|
||||
data[0] -> filterSize = FILTER_SIZE_ALL
|
||||
data[1] -> filterSize = FILTER_SIZE_1
|
||||
@ -430,8 +547,6 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
tvDelete.text = getString(R.string.delete_placeholder, selectedCounts)
|
||||
tvRecover.text = getString(R.string.recover_placeholder, selectedCounts)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -452,15 +567,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
)
|
||||
.filterBySize(filterSizeCovert.first, filterSizeCovert.second)
|
||||
.let { 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}")
|
||||
checkRefreshDisPlaySelected(list1 = currentList)
|
||||
dateAdapter?.resetAllValue(null)
|
||||
dateAdapter?.setData(currentList)
|
||||
resetCurrentDateList(currentList)
|
||||
|
||||
}
|
||||
|
||||
@ -476,31 +586,58 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
)
|
||||
.filterBySizeList(filterSizeCovert.first, filterSizeCovert.second)
|
||||
.let { currentList ->
|
||||
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
||||
val checkSelectListContain =
|
||||
Common.checkSelectListContainSize(currentList, allSelectedSetList)
|
||||
updateButtonCounts(checkSelectListContain.first)
|
||||
viewModel.filterResetDisplayFlow(checkSelectListContain.second)
|
||||
checkRefreshDisPlaySelected(list2 = 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> {
|
||||
return when (filterSize) {
|
||||
FILTER_SIZE_ALL -> Pair(-1L, -1L)
|
||||
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)
|
||||
else -> Pair(-1L, -1L)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun showStartDateDialog(isNeedSetSelected: Boolean,currentDate: Date?) {
|
||||
/**
|
||||
* 显示筛选开始日期弹窗
|
||||
*/
|
||||
private fun showStartDateDialog(isNeedSetSelected: Boolean, currentDate: Date?) {
|
||||
|
||||
dialogCustomerDateStart = dialogCustomerDateStart ?: DatePickerDialogFragment(
|
||||
this,
|
||||
@ -514,16 +651,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
filterStartDate = selectedDate
|
||||
// filterDatePopupWindows?.updateStartEndDate(start = selectedDate)
|
||||
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 = {
|
||||
|
||||
@ -534,7 +664,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
|
||||
}
|
||||
|
||||
private fun showEndDateDialog(isNeedSetSelected: Boolean,currentDate: Date?) {
|
||||
/**
|
||||
* 显示筛选结束日期弹窗
|
||||
*/
|
||||
private fun showEndDateDialog(isNeedSetSelected: Boolean, currentDate: Date?) {
|
||||
dialogCustomerDateEnd = dialogCustomerDateEnd ?: DatePickerDialogFragment(
|
||||
this,
|
||||
getString(R.string.end_date),
|
||||
@ -546,12 +679,13 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
filterStartDate?.let { setRangeDate(it) }
|
||||
onPickerChooseListener = { selectedDate ->
|
||||
filterEndDate = selectedDate
|
||||
if(isNeedSetSelected){
|
||||
if (isNeedSetSelected) {
|
||||
filterDatePopupWindows?.updateSelectPos(4, filterStartDate!!, filterEndDate!!)
|
||||
startFilter()
|
||||
filterDatePopupWindows?.dismiss()
|
||||
}else{
|
||||
filterDatePopupWindows?.updateStartEndDate(filterStartDate,filterEndDate)
|
||||
binding.titleDate.text = resources.getStringArray(R.array.filter_date)[4]
|
||||
} else {
|
||||
filterDatePopupWindows?.updateStartEndDate(filterStartDate, filterEndDate)
|
||||
if (filterDate == FILTER_DATE_CUSTOMER) {
|
||||
startFilter()
|
||||
filterDatePopupWindows?.dismiss()
|
||||
@ -566,6 +700,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示恢复中弹窗
|
||||
*/
|
||||
private fun showRecoveringDialog() {
|
||||
dialogRecovering =
|
||||
dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size) {
|
||||
@ -574,6 +712,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
dialogRecovering?.show(supportFragmentManager, "")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示删除中弹窗
|
||||
*/
|
||||
private fun showDeletingDialog() {
|
||||
dialogDeleting = dialogDeleting ?: DeletingDialogFragment(filterSelectedSetList.size) {
|
||||
complete()
|
||||
|
||||
@ -10,9 +10,23 @@ data class ResultPhotosFiles(
|
||||
val name: String,
|
||||
val path: String? = null,
|
||||
val size: Long, // 字节
|
||||
val sizeString: String,
|
||||
val lastModified: Long, // 时间戳
|
||||
var resolution: String // 尺寸
|
||||
) : Parcelable {
|
||||
val targetFile: File?
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
|
||||
val total = 800
|
||||
lifecycleScope.launch {
|
||||
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) {
|
||||
is ScanState.Progress -> {
|
||||
updateProgress(it)
|
||||
@ -131,7 +131,7 @@ class ScanningActivity : BaseActivity<ActivityScanningBinding>() {
|
||||
lifecycleScope.launch {
|
||||
|
||||
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) {
|
||||
is ScanState.Progress -> {
|
||||
updateProgress(it)
|
||||
|
||||
@ -12,6 +12,8 @@ import com.ux.video.file.filerecovery.App
|
||||
import com.ux.video.file.filerecovery.R
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
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 {
|
||||
dateFormat.format(Date(it.lastModified))
|
||||
}
|
||||
@ -52,6 +52,16 @@ object Common {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照文件大小排序,将最小的排前面 升序
|
||||
*/
|
||||
fun getSortBySizeSmallToBig(list: ArrayList<ResultPhotosFiles>) = list.sortedBy {
|
||||
fun getSortBySizeSmallToBig(list: List<ResultPhotosFiles>) = list.sortedBy {
|
||||
it.size
|
||||
}
|
||||
|
||||
@ -81,16 +91,19 @@ object Common {
|
||||
if (size < 1024) {
|
||||
return "$size B"
|
||||
}
|
||||
|
||||
val kb = size / 1024.0
|
||||
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
|
||||
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
|
||||
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的选中
|
||||
*/
|
||||
// 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) {
|
||||
view.isSelected = selected
|
||||
if (view is ViewGroup) {
|
||||
@ -154,14 +162,14 @@ object Common {
|
||||
*/
|
||||
fun checkSelectListContainDate(
|
||||
list: List<Pair<String, List<ResultPhotosFiles>>>,
|
||||
selected: Set<String>
|
||||
): Pair<Int, MutableSet<String>> {
|
||||
val currentSelected = mutableSetOf<String>()
|
||||
selected: Set<ResultPhotosFiles>
|
||||
): Pair<Int, MutableSet<ResultPhotosFiles>> {
|
||||
val currentSelected = mutableSetOf<ResultPhotosFiles>()
|
||||
|
||||
val totalSelectedCount = list.sumOf { pair ->
|
||||
pair.second.count {
|
||||
val isSelected = it.path in selected
|
||||
if (isSelected) currentSelected.add(it.path.toString())
|
||||
val isSelected = it in selected
|
||||
if (isSelected) currentSelected.add(it)
|
||||
isSelected
|
||||
}
|
||||
}
|
||||
@ -170,20 +178,74 @@ object Common {
|
||||
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>,
|
||||
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
|
||||
selected: Set<ResultPhotosFiles>
|
||||
): Pair<Int, MutableSet<ResultPhotosFiles>> = withContext(Dispatchers.Default) {
|
||||
val currentSelected = mutableSetOf<ResultPhotosFiles>()
|
||||
var totalSelectedCount = 0
|
||||
|
||||
// 高效遍历外层 + 内层列表
|
||||
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 {
|
||||
return dateFormat.format(Date(time))
|
||||
|
||||
@ -8,6 +8,8 @@ import android.os.Parcelable
|
||||
import android.util.TypedValue
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Date
|
||||
|
||||
object ExtendFunctions {
|
||||
@ -176,7 +178,31 @@ object ExtendFunctions {
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
package com.ux.video.file.filerecovery.utils
|
||||
|
||||
|
||||
import android.content.Context
|
||||
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 androidx.annotation.RequiresApi
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotos
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||
import com.ux.video.file.filerecovery.result.ScanningActivity
|
||||
@ -48,6 +54,7 @@ object ScanManager {
|
||||
* 扫描所有文件
|
||||
*/
|
||||
fun scanAllDocuments(
|
||||
context: Context,
|
||||
root: File, maxDepth: Int = 5,
|
||||
maxFiles: Int = 5000,
|
||||
type: Int
|
||||
@ -100,6 +107,7 @@ object ScanManager {
|
||||
name = file.name,
|
||||
path = file.absolutePath,
|
||||
size = file.length(),
|
||||
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()),
|
||||
lastModified = file.lastModified(),
|
||||
resolution = getImageSize(file).run {
|
||||
"$first*$second"
|
||||
@ -110,6 +118,7 @@ object ScanManager {
|
||||
}
|
||||
emit(ScanState.Complete(ArrayList(map)))
|
||||
}
|
||||
|
||||
private fun getImageSize(file: File): Pair<Int, Int> {
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
@ -125,12 +134,14 @@ object ScanManager {
|
||||
* @param maxFiles // 最大收集的文件数
|
||||
*/
|
||||
fun scanHiddenPhotoAsync(
|
||||
context: Context,
|
||||
root: File, maxDepth: Int = 5,
|
||||
maxFiles: Int = 5000, type: Int
|
||||
): Flow<ScanState> = flow {
|
||||
|
||||
val result = mutableMapOf<String, MutableList<File>>()
|
||||
var fileCount = 0
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) {
|
||||
if (!dir.exists() || !dir.isDirectory) return
|
||||
if (depth > maxDepth || fileCount >= maxFiles) return
|
||||
@ -181,17 +192,30 @@ object ScanManager {
|
||||
name = file.name,
|
||||
path = file.absolutePath,
|
||||
size = file.length(),
|
||||
sizeString = android.text.format.Formatter.formatFileSize(context, file.length()),
|
||||
lastModified = file.lastModified(),
|
||||
resolution = getImageSize(file).run {
|
||||
"$first*$second"
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
ResultPhotos(dir, ArrayList(resultPhotosFilesList))
|
||||
}
|
||||
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 {
|
||||
val ext = file.extension.lowercase()
|
||||
@ -223,7 +247,7 @@ object ScanManager {
|
||||
* @param folder "AllRecovery/Photo"
|
||||
*/
|
||||
fun CoroutineScope.copySelectedFilesAsync(
|
||||
selectedSet: Set<String>,
|
||||
selectedSet: Set<ResultPhotosFiles>,
|
||||
rootDir: File = Common.rootDir,
|
||||
folder: String,
|
||||
onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit,
|
||||
@ -232,8 +256,8 @@ object ScanManager {
|
||||
launch(Dispatchers.IO) {
|
||||
val targetDir = File(rootDir, folder)
|
||||
if (!targetDir.exists()) targetDir.mkdirs()
|
||||
selectedSet.forEachIndexed { index, path ->
|
||||
val srcFile = File(path)
|
||||
selectedSet.forEachIndexed { index, resultPhotosFiles ->
|
||||
val srcFile = File(resultPhotosFiles.path!!)
|
||||
if (srcFile.exists() && srcFile.isFile) {
|
||||
val destFile = File(targetDir, srcFile.name)
|
||||
var success = false
|
||||
@ -265,23 +289,23 @@ object ScanManager {
|
||||
*
|
||||
*/
|
||||
fun CoroutineScope.deleteFilesAsync(
|
||||
selectedSet: Set<String>,
|
||||
onProgress: (currentCounts: Int, path: String, success: Boolean) -> Unit,
|
||||
selectedSet: Set<ResultPhotosFiles>,
|
||||
onProgress: (currentCounts: Int, fileName: String, success: Boolean) -> Unit,
|
||||
onComplete: (currentCounts: Int) -> Unit
|
||||
) {
|
||||
launch(Dispatchers.IO) {
|
||||
var deletedCount = 0
|
||||
selectedSet.forEachIndexed { index, path ->
|
||||
selectedSet.forEachIndexed { index, resultPhotosFiles ->
|
||||
try {
|
||||
val file = File(path)
|
||||
val file = File(resultPhotosFiles.path!!)
|
||||
if (file.exists() && file.delete()) {
|
||||
deletedCount++
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
onProgress(index + 1, path, true)
|
||||
onProgress(index + 1, file.name, true)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
onProgress(index + 1, path, false)
|
||||
onProgress(index + 1, resultPhotosFiles.path!!, false)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotos
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@ -19,24 +20,27 @@ class ScanRepository : ViewModel() {
|
||||
|
||||
|
||||
|
||||
private val _selectedLiveData = MutableLiveData<Set<String>>(emptySet())
|
||||
val selectedLiveData: LiveData<Set<String>> = _selectedLiveData
|
||||
private val _selectedLiveData = MutableLiveData<Set<ResultPhotosFiles>>(emptySet())
|
||||
val selectedLiveData: LiveData<Set<ResultPhotosFiles>> = _selectedLiveData
|
||||
|
||||
// 当前筛选显示的选中项
|
||||
private val _selectedDisplayLiveData = MutableLiveData<Set<String>>(emptySet())
|
||||
val selectedDisplayLiveData: LiveData<Set<String>> = _selectedDisplayLiveData
|
||||
private val _selectedDisplayLiveData = MutableLiveData<Set<ResultPhotosFiles>>(emptySet())
|
||||
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 currentDisplay = _selectedDisplayLiveData.value?.toMutableSet() ?: mutableSetOf()
|
||||
resultPhotosFiles.let {
|
||||
|
||||
if (isAdd) {
|
||||
current.add(path)
|
||||
currentDisplay.add(path)
|
||||
current.add(it)
|
||||
currentDisplay.add(it)
|
||||
} else {
|
||||
current.remove(path)
|
||||
currentDisplay.remove(path)
|
||||
current.remove(it)
|
||||
currentDisplay.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
Common.showLog( "toggleSelection------------ _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ")
|
||||
_selectedLiveData.value = current.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()
|
||||
|
||||
Common.showLog( "筛选后重置 _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ")
|
||||
@ -57,13 +61,10 @@ class ScanRepository : ViewModel() {
|
||||
|
||||
|
||||
|
||||
fun checkIsSelect(path: String): Boolean {
|
||||
val current = _selectedDisplayLiveData.value
|
||||
return current?.contains(path) == true
|
||||
fun checkIsSelect(resultPhotosFiles: ResultPhotosFiles): Boolean {
|
||||
val current = _selectedLiveData.value
|
||||
return current?.contains(resultPhotosFiles) == true
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val instance by lazy { ScanRepository() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<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:fillColor="@color/white"/>
|
||||
android:fillColor="@color/black"/>
|
||||
</vector>
|
||||
|
||||
@ -55,6 +55,10 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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:textSize="16sp"
|
||||
app:fontType="bold" />
|
||||
@ -80,6 +84,10 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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:textSize="16sp"
|
||||
app:fontType="bold" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user