From 37e894ad4721777165a2d5980a243d8a5c2bc561 Mon Sep 17 00:00:00 2001 From: litingting Date: Wed, 15 Oct 2025 18:31:33 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9A=90=E8=97=8F=E7=BC=A9=E7=95=A5=E5=9B=BE?= =?UTF-8?q?=E7=9A=84=E5=BC=80=E5=85=B3=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/filerecovery/base/BaseAdapter.kt | 4 +- .../file/filerecovery/base/DiffBaseAdapter.kt | 39 +++ .../photo/PhotoDisplayDateAdapter.kt | 74 +++-- .../photo/PhotoDisplayDateChildAdapter.kt | 54 +++- .../photo/PhotoSortingActivity.kt | 256 ++++++++++++++---- .../filerecovery/photo/ResultPhotosFiles.kt | 14 + .../filerecovery/result/ScanningActivity.kt | 4 +- .../video/file/filerecovery/utils/Common.kt | 122 +++++++-- .../filerecovery/utils/ExtendFunctions.kt | 28 +- .../file/filerecovery/utils/ScanManager.kt | 44 ++- .../file/filerecovery/utils/ScanRepository.kt | 39 +-- .../res/drawable/icon_unselected_28dp.xml | 6 +- .../res/layout/activity_photo_sorting.xml | 8 + 13 files changed, 539 insertions(+), 153 deletions(-) create mode 100644 app/src/main/java/com/ux/video/file/filerecovery/base/DiffBaseAdapter.kt diff --git a/app/src/main/java/com/ux/video/file/filerecovery/base/BaseAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/base/BaseAdapter.kt index e7b280b..ed1e1ed 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/base/BaseAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/base/BaseAdapter.kt @@ -7,7 +7,7 @@ import androidx.viewbinding.ViewBinding import kotlin.let abstract class BaseAdapter( - protected val mContext: Context + protected val mContext: Context, ) : RecyclerView.Adapter>() { protected val data: MutableList = mutableListOf() @@ -28,6 +28,8 @@ abstract class BaseAdapter( } + fun getCurrentData() = data + override fun getItemCount(): Int = data.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VHolder { diff --git a/app/src/main/java/com/ux/video/file/filerecovery/base/DiffBaseAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/base/DiffBaseAdapter.kt new file mode 100644 index 0000000..56e00d5 --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/base/DiffBaseAdapter.kt @@ -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( + protected val mContext: Context, + diffCallback: DiffUtil.ItemCallback +) : ListAdapter>(diffCallback) { + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VHolder { + return VHolder(getViewBinding(parent)) + } + + override fun onBindViewHolder(holder: VHolder, position: Int) { + bindItem(holder, getItem(position)) + } + + /** + * 更新数据(自动触发 DiffUtil 计算) + */ + fun submitData(items: List?) { + submitList(items ?: emptyList()) + } + + protected abstract fun getViewBinding(parent: ViewGroup): T + protected abstract fun bindItem(holder: VHolder, item: K) + + class VHolder(val vb: V) : androidx.recyclerview.widget.RecyclerView.ViewHolder(vb.root) +} diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt index 45abf11..0e975cc 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateAdapter.kt @@ -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>, PhotoDisplayDateAdapterBinding>(mContext) { + private var allSelected: Boolean? = null +// private var hideThumbnails = false override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateAdapterBinding = PhotoDisplayDateAdapterBinding.inflate( LayoutInflater.from(parent.context), @@ -28,11 +33,21 @@ class PhotoDisplayDateAdapter( false ) + fun updateHideThumbnails(isChecked: Boolean) { +// hideThumbnails = isChecked + notifyDataSetChanged() + } + /** * 返回所有嵌套的数据量总数 */ - fun getTotalChildCount(): Int { - return data.sumOf { it.second.size } + fun getTotalChildCount(hideThumbnails: Boolean): Int { + if(hideThumbnails){ + return data.sumOf { it.second.filter { !it.isThumbnail }.size } + }else{ + return data.sumOf { it.second.size } + } + } /** @@ -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, item: Pair> ) { holder.vb.run { - item.run { val (date, files) = item val childAdapter = PhotoDisplayDateChildAdapter( mContext, mColumns, viewModel, - { path, addOrRemove, isDateAllSelected -> - //点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里 - tvDayAllSelect.isSelected = isDateAllSelected - onSelectedUpdate(path.toString(),addOrRemove) - },clickItem).apply { setData(files) } + { resultPhotosFiles, addOrRemove, isDateAllSelected -> + //点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里 + tvDayAllSelect.isSelected = isDateAllSelected + 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>>() { + override fun areItemsTheSame( + oldItem: Pair>, + newItem: Pair> + ): Boolean { + return oldItem.first == newItem.first + } + + override fun areContentsTheSame( + oldItem: Pair>, + newItem: Pair> + ): Boolean { + return oldItem == newItem + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt index 85f8d02..b4ae266 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoDisplayDateChildAdapter.kt @@ -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(mContext) { - +// private var hideThumbnails: Boolean? = null //日期组某一天的数据选择状态维护 - val dateSelectedMap = mutableSetOf() + val dateSelectedMap = mutableSetOf() + + //实际显示数据集合(包含隐藏的缩略图) +// 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 = data, hideThumbnails: Boolean): Int { + if(hideThumbnails){ + return list.filter { !it.isThumbnail }.size + }else{ + return list.size + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt index d3da35f..4b6165d 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/PhotoSortingActivity.kt @@ -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() { @@ -90,12 +91,16 @@ class PhotoSortingActivity : BaseActivity() { private lateinit var sortByDateReverse: List>> private lateinit var sortedByDatePositive: List>> + //最新显示的数据集合(包含缩略图 ,只保存当前筛选后或者排序后显示的数据,不受switch切换影响) + private var currentDateList: List>>? = null + private var currentSizeList: List? = null //选中的所有数据集合(实际选中) - private lateinit var allSelectedSetList: Set + private lateinit var allSelectedSetList: Set //选中的所有数据集合(筛选后的数据实际显示的所有选中) - private lateinit var filterSelectedSetList: Set + private lateinit var filterSelectedSetList: Set + private lateinit var mItemDecoration: GridSpacingItemDecoration @@ -124,11 +129,14 @@ class PhotoSortingActivity : BaseActivity() { 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() { 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() { }) }.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,26 +257,63 @@ class PhotoSortingActivity : BaseActivity() { when (it) { SORT_ASC_DATE -> { setDateAdapter() - dateAdapter?.setData(sortedByDatePositive) - sortReverse = false + 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) - sortReverse = true + 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() - sizeSortAdapter?.setData(sortBySizeBigToSmall) - sortReverse = true + 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() - sizeSortAdapter?.setData(sortBySizeSmallToBig) - sortReverse = false + 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 + } } } } @@ -254,25 +339,50 @@ class PhotoSortingActivity : BaseActivity() { 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 { + currentSizeList = currentSizeList ?: currentDateList?.flatMap { it.second } + return currentSizeList!! + } + private fun resetCurrentSizeList(currentList: List) { + currentSizeList = currentList + currentDateList = null + + } + + private fun initGetCurrentDateList(): List>> { + currentDateList = currentDateList ?: Common.getSortByDayNewToOldInit(currentSizeList!!) + return currentDateList!! + } + private fun resetCurrentDateList(currentList: List>> ) { + 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() { 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() { */ private fun setFilter() { //日期筛选 + binding.run { filterDateLayout.setOnClickListener { setItemSelect(it as LinearLayout, true) @@ -304,40 +415,45 @@ class PhotoSortingActivity : BaseActivity() { 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() { 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() { tvDelete.text = getString(R.string.delete_placeholder, selectedCounts) tvRecover.text = getString(R.string.recover_placeholder, selectedCounts) } - - } /** @@ -452,15 +567,10 @@ class PhotoSortingActivity : BaseActivity() { ) .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() { ) .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>>? = null, + list2: List? = 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 { 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() { 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() { } - 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() { 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() { } } + + /** + * 显示恢复中弹窗 + */ private fun showRecoveringDialog() { dialogRecovering = dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size) { @@ -574,6 +712,10 @@ class PhotoSortingActivity : BaseActivity() { dialogRecovering?.show(supportFragmentManager, "") } + + /** + * 显示删除中弹窗 + */ private fun showDeletingDialog() { dialogDeleting = dialogDeleting ?: DeletingDialogFragment(filterSelectedSetList.size) { complete() diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt index c281f76..79a9838 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/ResultPhotosFiles.kt @@ -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 + } } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt index 73d0cfa..09595f4 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanningActivity.kt @@ -111,7 +111,7 @@ class ScanningActivity : BaseActivity() { 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() { 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) diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt index ee1d4ab..67b6008 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/Common.kt @@ -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): List>> { - - + fun getSortByDayNewToOldInit(list: List): List>> { val grouped = list.groupBy { dateFormat.format(Date(it.lastModified)) } @@ -52,6 +52,16 @@ object Common { return parentData } + fun getSortByDayNewToOld( + list: List>> + ): List>> { + return list.sortedByDescending { pair -> + dateFormat.parse(pair.first)?.time ?: 0L + } + } + + + /** * 按照日期排序, 时间最早的排前面 升序 * @@ -62,14 +72,14 @@ object Common { /** * 按照文件大小排序,将最大的排前面 降序 */ - fun getSortBySizeBigToSmall(list: ArrayList) = list.sortedByDescending { + fun getSortBySizeBigToSmall(list: List) = list.sortedByDescending { it.size } /** * 按照文件大小排序,将最小的排前面 升序 */ - fun getSortBySizeSmallToBig(list: ArrayList) = list.sortedBy { + fun getSortBySizeSmallToBig(list: List) = 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>>, - selected: Set - ): Pair> { - val currentSelected = mutableSetOf() + selected: Set + ): Pair> { + val currentSelected = mutableSetOf() 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,21 +178,75 @@ object Common { return totalSelectedCount to currentSelected } + suspend fun checkSelectListContainDateAsync( + list: List>>, + selected: Set + ): Pair> = withContext(Dispatchers.Default) { + val currentSelected = mutableSetOf() + var totalSelectedCount = 0 - fun checkSelectListContainSize( - list: List, - selected: Set - ): Pair> { - val currentSelected = mutableSetOf() - val totalSelectedCount = list.count { - val isSelected = it.path in selected - if (isSelected) currentSelected.add(it.path.toString()) - isSelected + // 高效遍历外层 + 内层列表 + for (pair in list) { + for (file in pair.second) { + if (file in selected) { + totalSelectedCount++ + currentSelected.add(file) + } + } } - showLog("-------totalSelectedCount 222=${totalSelectedCount}") - return totalSelectedCount to currentSelected + + totalSelectedCount to currentSelected } + +// fun checkSelectListContainSize( +// list: List, +// selected: Set +// ): Pair> { +// val currentSelected = mutableSetOf() +// 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, + selected: Set + ): Pair> = withContext(Dispatchers.Default) { + val currentSelected = mutableSetOf() + var totalSelectedCount = 0 + + // 高效遍历外层 + 内层列表 + for (pair in list) { + if (pair in selected) { + totalSelectedCount++ + currentSelected.add(pair) + } + } + totalSelectedCount to currentSelected + } + + + /** + * 去掉缩略图的集合 + */ + suspend fun filterThumbnailsAsync( + originalList: MutableList>> + ): List>> = 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)) } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt index 211bcee..1b0502d 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/ExtendFunctions.kt @@ -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,8 +178,32 @@ object ExtendFunctions { } } fun Int.mbToBytes(): Long { - return this * 1024L * 1024L + return this * 1000L * 1000L } + /** + * 移除掉缩略图后的数据 + */ + suspend fun List>>.filterThumbnailsAsync(): List>> = + 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.filterRemoveThumbnailsAsync(): List = + withContext(Dispatchers.Default) { + this@filterRemoveThumbnailsAsync.asSequence() + .filter { !it.isThumbnail } // 去掉 isThumbnail = true 的项 + .toList() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt index 9c9aeae..d4eb704 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanManager.kt @@ -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,7 +118,8 @@ object ScanManager { } emit(ScanState.Complete(ArrayList(map))) } - private fun getImageSize(file: File): Pair { + + private fun getImageSize(file: File): Pair { val options = BitmapFactory.Options() options.inJustDecodeBounds = true BitmapFactory.decodeFile(file.absolutePath, options) @@ -125,12 +134,14 @@ object ScanManager { * @param maxFiles // 最大收集的文件数 */ fun scanHiddenPhotoAsync( + context: Context, root: File, maxDepth: Int = 5, maxFiles: Int = 5000, type: Int ): Flow = flow { val result = mutableMapOf>() var fileCount = 0 + @RequiresApi(Build.VERSION_CODES.R) suspend fun scanDir(dir: File, depth: Int, insideHidden: Boolean = false) { if (!dir.exists() || !dir.isDirectory) return 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): Boolean { val ext = file.extension.lowercase() @@ -223,7 +247,7 @@ object ScanManager { * @param folder "AllRecovery/Photo" */ fun CoroutineScope.copySelectedFilesAsync( - selectedSet: Set, + selectedSet: Set, 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, - onProgress: (currentCounts: Int, path: String, success: Boolean) -> Unit, + selectedSet: Set, + 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) { diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanRepository.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanRepository.kt index ec2df5b..6ae0b43 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanRepository.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/ScanRepository.kt @@ -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>(emptySet()) - val selectedLiveData: LiveData> = _selectedLiveData + private val _selectedLiveData = MutableLiveData>(emptySet()) + val selectedLiveData: LiveData> = _selectedLiveData // 当前筛选显示的选中项 - private val _selectedDisplayLiveData = MutableLiveData>(emptySet()) - val selectedDisplayLiveData: LiveData> = _selectedDisplayLiveData + private val _selectedDisplayLiveData = MutableLiveData>(emptySet()) + val selectedDisplayLiveData: LiveData> = _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) - } else { - current.remove(path) - currentDisplay.remove(path) + if (isAdd) { + current.add(it) + currentDisplay.add(it) + } else { + 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){ + fun filterResetDisplayFlow(list: MutableSet){ _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() } - } } diff --git a/app/src/main/res/drawable/icon_unselected_28dp.xml b/app/src/main/res/drawable/icon_unselected_28dp.xml index 1600cfb..bb50f4a 100644 --- a/app/src/main/res/drawable/icon_unselected_28dp.xml +++ b/app/src/main/res/drawable/icon_unselected_28dp.xml @@ -1,9 +1,9 @@ + android:fillColor="@color/black"/> diff --git a/app/src/main/res/layout/activity_photo_sorting.xml b/app/src/main/res/layout/activity_photo_sorting.xml index 967bec9..8d81bf8 100644 --- a/app/src/main/res/layout/activity_photo_sorting.xml +++ b/app/src/main/res/layout/activity_photo_sorting.xml @@ -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" />