diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f927f5b..25f7dc7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,4 +52,6 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) implementation (libs.glide) kapt (libs.compiler) + implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation ("com.google.android.material:material:1.13.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fb9bf25..02dfa57 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ tools:ignore="ScopedStorage" /> ( protected val data: MutableList = mutableListOf() - fun addData(items: List?) { items?.let { val start = data.size diff --git a/app/src/main/java/com/ux/video/file/filerecovery/base/NewBaseAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/base/NewBaseAdapter.kt new file mode 100644 index 0000000..2021620 --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/base/NewBaseAdapter.kt @@ -0,0 +1,50 @@ +package com.ux.video.file.filerecovery.base + +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView + + +abstract class NewBaseAdapter( + protected val mContext: Context +) : RecyclerView.Adapter() { + + protected val data: MutableList = mutableListOf() + + fun addData(items: List?) { + items?.let { + val start = data.size + data.addAll(it) + notifyItemRangeInserted(start, it.size) + } + } + + fun setData(items: List?) { + data.clear() + items?.let { data.addAll(it) } + notifyDataSetChanged() + } + + override fun getItemCount(): Int = data.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return onCreateBinding(parent, viewType) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + onBind(holder, data[position], getItemViewType(position)) + } + + /** + * 子类实现:根据 viewType 创建对应的 ViewHolder + */ + protected abstract fun onCreateBinding( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder + + /** + * 子类实现:绑定数据 + */ + protected abstract fun onBind(holder: RecyclerView.ViewHolder, item: K, viewType: Int) +} diff --git a/app/src/main/java/com/ux/video/file/filerecovery/documents/DocumentsScanResultActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/documents/DocumentsScanResultActivity.kt index 3ea21dd..4949f1e 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/documents/DocumentsScanResultActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/documents/DocumentsScanResultActivity.kt @@ -24,9 +24,9 @@ class DocumentsScanResultActivity : BaseActivity() { @@ -30,19 +39,20 @@ class ScanSelectTypeActivity : BaseActivity() { setSelectType(type) } + override fun initData() { super.initData() binding.imageBack.setOnClickListener { finish() } binding.scanAllFile.setOnClickListener { startActivity(Intent(this@ScanSelectTypeActivity, ScanningActivity::class.java).apply { - putExtra(ScanningActivity.Companion.KEY_SCAN_TYPE, allType) + putExtra(KEY_SCAN_TYPE, allType) }) } binding.scanDeletedFile.setOnClickListener { startActivity(Intent(this@ScanSelectTypeActivity, ScanningActivity::class.java).apply { - putExtra(ScanningActivity.Companion.KEY_SCAN_TYPE, deletedType) + putExtra(KEY_SCAN_TYPE, deletedType) }) } } @@ -50,26 +60,26 @@ class ScanSelectTypeActivity : BaseActivity() { private fun setSelectType(fileType: Int) { when (fileType) { VALUE_PHOTO -> { - allType = ScanningActivity.Companion.VALUE_SCAN_TYPE_photo - deletedType = ScanningActivity.Companion.VALUE_SCAN_TYPE_deleted_photo + allType = VALUE_SCAN_TYPE_photo + deletedType = VALUE_SCAN_TYPE_deleted_photo binding.title.text = getString(R.string.photo_title) } VALUE_VIDEO -> { - allType = ScanningActivity.Companion.VALUE_SCAN_TYPE_video - deletedType = ScanningActivity.Companion.VALUE_SCAN_TYPE_deleted_video + allType = VALUE_SCAN_TYPE_video + deletedType = VALUE_SCAN_TYPE_deleted_video binding.title.text = getString(R.string.video_title) } VALUE_AUDIO -> { - allType = ScanningActivity.Companion.VALUE_SCAN_TYPE_audio - deletedType = ScanningActivity.Companion.VALUE_SCAN_TYPE_deleted_audio + allType = VALUE_SCAN_TYPE_audio + deletedType = VALUE_SCAN_TYPE_deleted_audio binding.title.text = getString(R.string.audio_title) } VALUE_DOCUMENT -> { - allType = ScanningActivity.Companion.VALUE_SCAN_TYPE_documents - deletedType = ScanningActivity.Companion.VALUE_SCAN_TYPE_deleted_documents + allType = VALUE_SCAN_TYPE_documents + deletedType = VALUE_SCAN_TYPE_deleted_documents binding.title.text = getString(R.string.document_title) } } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt new file mode 100644 index 0000000..b4fc24c --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/DatePickerDialogFragment.kt @@ -0,0 +1,68 @@ +package com.ux.video.file.filerecovery.photo + +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import com.ux.video.file.filerecovery.R +import com.ux.video.file.filerecovery.databinding.CommonLayoutSortItemBinding +import com.ux.video.file.filerecovery.databinding.DialogSortBinding +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.DateValidatorPointForward +import java.text.SimpleDateFormat +import java.util.* + +class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFragment() { + + private lateinit var binding: DialogSortBinding + + override fun onStart() { + super.onStart() + dialog?.window?.apply { + setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setGravity(Gravity.BOTTOM) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = DialogSortBinding.inflate(inflater) + + return binding.root + } + + fun showDatePicker() { + // 创建日期选择器构建器 + val builder = MaterialDatePicker.Builder.datePicker() + builder.setTitleText("选择日期") + + // 可选:限制可选日期,比如只能选择今天之后的日期 + val constraintsBuilder = CalendarConstraints.Builder() + constraintsBuilder.setValidator(DateValidatorPointForward.now()) // 今天之后 + builder.setCalendarConstraints(constraintsBuilder.build()) + + val datePicker = builder.build() + + // 显示日期选择器 + datePicker.show(supportFragmentManager, "MATERIAL_DATE_PICKER") + + // 监听用户选择 + datePicker.addOnPositiveButtonClickListener { selection -> + // selection 是 Long 类型的时间戳(UTC 毫秒) + val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val dateString = sdf.format(Date(selection)) + println("用户选择的日期:$dateString") + } + } +} diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/FilterPopupWindows.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/FilterPopupWindows.kt new file mode 100644 index 0000000..2dac8a4 --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/FilterPopupWindows.kt @@ -0,0 +1,128 @@ +package com.ux.video.file.filerecovery.photo + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.PopupWindow +import android.widget.RelativeLayout +import androidx.core.view.forEach +import com.ux.video.file.filerecovery.databinding.CommonLayoutFilterItemBinding +import com.ux.video.file.filerecovery.databinding.PopwindowsFilterBinding +import com.ux.video.file.filerecovery.utils.Common.setItemSelect +import com.ux.video.file.filerecovery.utils.CustomTextView + +class FilterPopupWindows( + context: Context, list: Array, + selectedPos: Int, + onClickConfirm: (value: String) -> Unit, + var onClickDismiss: () -> Unit +) : + PopupWindow(context) { + + private val viewBinding: PopwindowsFilterBinding = + PopwindowsFilterBinding.inflate(LayoutInflater.from(context)) + + init { + + setContentView(viewBinding.root) + + + width = ViewGroup.LayoutParams.MATCH_PARENT + height = ViewGroup.LayoutParams.WRAP_CONTENT + + isFocusable = true + isOutsideTouchable = true + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + + // 动画(可选) +// animationStyle = R.style.PopupAnimation + + viewBinding.viewMask.setOnClickListener { + dismiss() + } + + setData(list, selectedPos, onClickConfirm) + } + + private fun setData( + list: Array, + selectedPos: Int, + onClickConfirm: (value: String) -> Unit + ) { + viewBinding.parentLayout.removeAllViews() + list.forEachIndexed { index, item -> + viewBinding.parentLayout.run { + addView( + CommonLayoutFilterItemBinding.inflate( + LayoutInflater.from(context), + this, + false + ).apply { + textItem.text = item + if (index == selectedPos) { + textItem.isSelected = true + checkImage.isSelected = true + } + }.root + ) + } + + } + setMutualExclusion(onClickConfirm) + + } + + + private fun setMutualExclusion(onClickConfirm: (value: String) -> Unit) { + viewBinding.run { + for (i in 0 until parentLayout.childCount) { + val child = parentLayout.getChildAt(i) + if (child is RelativeLayout) { + child.setOnClickListener { + for (j in 0 until parentLayout.childCount) { + val other = parentLayout.getChildAt(j) + if (other is RelativeLayout) { + setItemSelect(other, false) + + } + } + setItemSelect(child, true) + for (j in 0 until child.childCount) { + val other = child.getChildAt(j) + if (other is CustomTextView) { + onClickConfirm.invoke(other.text.toString()) + } + } + dismiss() + } + } + } + } + + } + + + fun show(anchor: View, xOff: Int = 0, yOff: Int = 0) { + showAsDropDown(anchor, xOff, yOff) + viewBinding.viewMask.apply { + alpha = 0f + animate().alpha(1f).setDuration(200).start() + visibility = View.VISIBLE + } + } + + override fun dismiss() { + Log.d("------------", "--------------dismiss") + viewBinding.viewMask.animate().alpha(0f).setDuration(200) + .withEndAction { + super.dismiss() + onClickDismiss.invoke() + }.start() + } + + +} \ No newline at end of file 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 e0dfbb5..c5b542b 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 @@ -1,61 +1,107 @@ package com.ux.video.file.filerecovery.photo +import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.GridLayoutManager import com.ux.video.file.filerecovery.base.BaseAdapter import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateAdapterBinding -import com.ux.video.file.filerecovery.utils.ExtendFunctions.addItemDecorationOnce -import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx +import com.ux.video.file.filerecovery.utils.Common +import com.ux.video.file.filerecovery.utils.ExtendFunctions.resetItemDecorationOnce import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration +import com.ux.video.file.filerecovery.utils.ScanRepository -class PhotoDisplayDateAdapter(mContext: Context) : +class PhotoDisplayDateAdapter( + mContext: Context, + var mColumns: Int, + var viewModel: ScanRepository, + var onSelectedUpdate: (updatePath: String, isAdd: Boolean) -> Unit +) : BaseAdapter>, PhotoDisplayDateAdapterBinding>(mContext) { private var allSelected: Boolean? = null - private var columns = 3 override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateAdapterBinding = PhotoDisplayDateAdapterBinding.inflate( LayoutInflater.from(parent.context), parent, false ) + + /** + * 返回所有嵌套的数据量总数 + */ + fun getTotalChildCount(): Int { + return data.sumOf { it.second.size } + } + + /** + * activity页面上点击全选按钮执行 + */ fun setAllSelected(allSelect: Boolean) { allSelected = allSelect + data.forEach { + it.second.forEach { item -> + onSelectedUpdate(item.path.toString(),allSelect) + } + } notifyDataSetChanged() } - fun setColumns(int: Int){ - columns = int + + fun resetAllValue(b: Boolean?){ + allSelected = b + } + + fun setColumns(int: Int) { + mColumns = int notifyDataSetChanged() } + + + + + @SuppressLint("SetTextI18n") override fun bindItem( holder: VHolder, item: Pair> ) { holder.vb.run { - val photoDisplayDateChildAdapter = PhotoDisplayDateChildAdapter(mContext) + item.run { - allSelected?.let { - imSelectStatus.isSelected = it - photoDisplayDateChildAdapter.setAllSelected(it) - } - imSelectStatus.setOnClickListener { - it.isSelected = !it.isSelected - photoDisplayDateChildAdapter.setAllSelected(it.isSelected) - } val (date, files) = item + val childAdapter = PhotoDisplayDateChildAdapter( + mContext, + mColumns, + viewModel + ) { path, addOrRemove, isDateAllSelected -> + //点击当前Adapter某一天的全选或者子Item上的选中都会回调到这里 + tvDayAllSelect.isSelected = isDateAllSelected + onSelectedUpdate(path.toString(),addOrRemove) + }.apply { setData(files) } + + allSelected?.let { + childAdapter.setAllSelected(it) + } + tvDayAllSelect.setOnClickListener { + it.isSelected = !it.isSelected + childAdapter.setAllSelected(it.isSelected) + } + textDate.text = date + textChildCounts.text = "(${files.size})" recyclerChild.apply { - layoutManager = GridLayoutManager(context, columns) + layoutManager = GridLayoutManager(context, mColumns) val gridSpacingItemDecoration = - GridSpacingItemDecoration(4, 8.dpToPx(mContext), true) - addItemDecorationOnce(gridSpacingItemDecoration) - adapter = photoDisplayDateChildAdapter.apply { setData(files) } + GridSpacingItemDecoration(mColumns, Common.itemSpacing, Common.horizontalSpacing) + Common.showLog("---------mColumns=${mColumns}") +// resetItemDecorationOnce(gridSpacingItemDecoration) + adapter = childAdapter isNestedScrollingEnabled = false } } } } + + } \ 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 9dfe517..7ee9c0b 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 @@ -3,7 +3,11 @@ package com.ux.video.file.filerecovery.photo import android.content.Context import android.graphics.drawable.Drawable import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.RelativeLayout +import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException @@ -12,89 +16,193 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.Target -import com.ux.video.file.filerecovery.base.BaseAdapter -import com.ux.video.file.filerecovery.databinding.PhotoDisplayDateChildAdapterBinding +import com.ux.video.file.filerecovery.App +import com.ux.video.file.filerecovery.base.NewBaseAdapter +import com.ux.video.file.filerecovery.databinding.FileSpanCountThreeAdapterBinding +import com.ux.video.file.filerecovery.databinding.FileSpanCountTwoAdapterBinding import com.ux.video.file.filerecovery.utils.Common +import com.ux.video.file.filerecovery.utils.CustomTextView import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.utils.ScanRepository -import kotlin.collections.remove -class PhotoDisplayDateChildAdapter(mContext: Context) : - BaseAdapter(mContext) { - - private var allSelected: Boolean? = null - override fun getViewBinding(parent: ViewGroup): PhotoDisplayDateChildAdapterBinding = - PhotoDisplayDateChildAdapterBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) +class PhotoDisplayDateChildAdapter( + mContext: Context, + var mColumns: Int, + var viewModel: ScanRepository, + /** + * 通知外层某天全选的按钮更新状态 + * @param updatePath 当前点击的item + * @param addOrRemove 选中还是取消选中 + * @param dateAllSelected 这组数据是否全部选中(某一天) + */ + var onSelectedUpdate: (updatePath: String, addOrRemove: Boolean, dateAllSelected: Boolean) -> Unit +) : + NewBaseAdapter(mContext) { - fun setAllSelected(allselect: Boolean) { - allSelected = allselect + //日期组某一天的数据选择状态维护 + val dateSelectedMap = mutableSetOf() + + companion object { + private const val TYPE_TWO = 2 + private const val TYPE_THREE = 3 + private const val TYPE_FOUR = 4 + } + + + fun setAllSelected(isAdd: Boolean) { + data.forEach { + addOrRemove(it.path.toString(), isAdd) + } notifyDataSetChanged() } - override fun bindItem( - holder: VHolder, - item: ResultPhotosFiles - ) { - - holder.vb.run { - item.run { - imSelectStatus.isSelected = ScanRepository.instance.checkIsSelect(path.toString()) - allSelected?.let { - imSelectStatus.isSelected = it - //全选按钮手动触发,需要更新 - updateSetList(it, path.toString()) - } - imSelectStatus.setOnClickListener { - it.isSelected = !it.isSelected - updateSetList(it.isSelected, path.toString()) - - } - textSize.text = Common.formatFileSize(mContext, size) - Glide.with(mContext) - .load(targetFile) - .apply( - RequestOptions() - .transform(CenterCrop(), RoundedCorners(15.dpToPx(mContext))) - ) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target, - isFirstResource: Boolean - ): Boolean { - ScanManager.showLog( - "加载图片", - "-------path = ${path} file=${targetFile}" - ) - return false - } - - override fun onResourceReady( - resource: Drawable, - model: Any, - target: Target?, - dataSource: DataSource, - isFirstResource: Boolean - ): Boolean { - - return false - } - - }) - .into(imageThumbnail) - - } + override fun getItemViewType(position: Int): Int { + return when (mColumns) { + 2 -> TYPE_TWO + 3 -> TYPE_THREE + 4 -> TYPE_FOUR + else -> TYPE_THREE } } - private fun updateSetList(boolean: Boolean, path: String) { - ScanRepository.instance.toggleSelection(boolean,path) + fun setColumns(int: Int) { + mColumns = int + notifyDataSetChanged() } + + /** + * 设置View为正方形 + */ + private fun setHeight(view: View) { + val layoutParams = view.layoutParams + val screenWidth = view.context.resources.displayMetrics.widthPixels + val i = (Common.itemSpacing).dpToPx(App.mAppContext) * (mColumns - 1) + val itemSize = + (screenWidth - i - 2 * Common.horizontalSpacing.dpToPx(App.mAppContext) )/ mColumns + view.layoutParams = layoutParams.apply { + height = itemSize + } + } + + override fun onCreateBinding( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + TYPE_TWO -> TwoHolder( + FileSpanCountTwoAdapterBinding.inflate( + inflater, + parent, + false + ) + ) + + else -> ThreeHolder( + FileSpanCountThreeAdapterBinding.inflate( + inflater, + parent, + false + ) + ) + } + + } + + override fun onBind( + holder: RecyclerView.ViewHolder, + item: ResultPhotosFiles, + viewType: Int + ) { + + when (holder) { + is TwoHolder -> holder.vb.run { + initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item) + } + + is ThreeHolder -> holder.vb.run { + initDateView(rootLayout, imageSelect, textSize, imageThumbnail, item) + } + } + + } + + class ThreeHolder(val vb: FileSpanCountThreeAdapterBinding) : + RecyclerView.ViewHolder(vb.root) + + class TwoHolder(val vb: FileSpanCountTwoAdapterBinding) : + RecyclerView.ViewHolder(vb.root) + + + private fun initDateView( + rootLayout: RelativeLayout, + imageSelectStatus: ImageView, + textSize: CustomTextView, + imageThumbnail: ImageView, + item: ResultPhotosFiles + ) { + item.run { + viewModel.checkIsSelect(path.toString()).let { + imageSelectStatus.isSelected = it + addOrRemove(path.toString(), it) + } + imageSelectStatus.setOnClickListener { + it.isSelected = !it.isSelected + it.isSelected.let { newStatus -> + addOrRemove(path.toString(), newStatus) + } + } + textSize.text = Common.formatFileSize(mContext, size) + + Glide.with(mContext) + .load(targetFile) + .apply( + RequestOptions() + .transform(CenterCrop(), RoundedCorners(8.dpToPx(mContext))) + ) + .listener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + ScanManager.showLog( + "加载图片", + "-------path = ${path} file=${targetFile}" + ) + return false + } + + override fun onResourceReady( + resource: Drawable, + model: Any, + target: Target?, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + + return false + } + + }) + .into(imageThumbnail) + setHeight(rootLayout) + + } + + } + + private fun addOrRemove(path: String, boolean: Boolean) { + if (boolean) { + dateSelectedMap.add(path) + } else { + dateSelectedMap.remove(path) + } + onSelectedUpdate.invoke(path, boolean, dateSelectedMap.size == itemCount) + } + + } \ 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 a5ea72b..f0a3d62 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 @@ -1,14 +1,19 @@ package com.ux.video.file.filerecovery.photo import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.base.BaseActivity import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding import com.ux.video.file.filerecovery.utils.Common -import com.ux.video.file.filerecovery.utils.ExtendFunctions.addItemDecorationOnce +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 @@ -16,6 +21,7 @@ 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.ExtendFunctions.resetItemDecorationOnce import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync @@ -38,12 +44,19 @@ class PhotoSortingActivity : BaseActivity() { val FILTER_SIZE_1 = 1 val FILTER_SIZE_5 = 2 val FILTER_SIZE_OVER_5 = 3 + + val SORT_ASC_SIZE = 0 + val SORT_DESC_SIZE = 1 + val SORT_ASC_DATE = 2 + val SORT_DESC_DATE = 3 } + private var sortDialogFragment: SortDialogFragment? = null private var columns = 3 private var dateAdapter: PhotoDisplayDateAdapter? = null - private var sizeAdapter: PhotoDisplayDateChildAdapter? = null - val selectedSet = mutableSetOf() // 保存选中状态 + + //文件大小排序使用的适配器 + private var sizeSortAdapter: PhotoDisplayDateChildAdapter? = null //默认倒序排序 @@ -55,12 +68,26 @@ class PhotoSortingActivity : BaseActivity() { //筛选大小,默认全部-1 private var filterSize = FILTER_SIZE_ALL + private var filterDatePopupWindows: FilterPopupWindows? = null + private var filterSizePopupWindows: FilterPopupWindows? = null + private var filterLayoutPopupWindows: FilterPopupWindows? = null + private lateinit var sortBySizeBigToSmall: List private lateinit var sortBySizeSmallToBig: List - private lateinit var sortByDayReverse: List>> - private lateinit var sortedByPositive: List>> + private lateinit var sortByDateReverse: List>> + private lateinit var sortedByDatePositive: List>> + + //选中的所有数据集合(实际选中) + private lateinit var allSelectedSetList: Set + + //选中的所有数据集合(筛选后的数据实际显示的所有选中) + private lateinit var filterSelectedSetList: Set + private lateinit var mItemDecoration: GridSpacingItemDecoration + + + private lateinit var viewModel: ScanRepository override fun inflateBinding(inflater: LayoutInflater): ActivityPhotoSortingBinding = ActivityPhotoSortingBinding.inflate(inflater) @@ -69,32 +96,57 @@ class PhotoSortingActivity : BaseActivity() { val list: ArrayList? = intent.getParcelableArrayListExtraCompat(KEY_PHOTO_FOLDER_FILE) + mItemDecoration = + GridSpacingItemDecoration(columns, Common.itemSpacing, Common.horizontalSpacing) + updateButtonCounts(0) + viewModel = ViewModelProvider(this).get(ScanRepository::class.java) + + viewModel.selectedLiveData.observe(this) { selectedSet -> + allSelectedSetList = selectedSet + Common.showLog("所有数据 选中状态更新: ${selectedSet.size}") + } + + viewModel.selectedDisplayLiveData.observe(this) { displaySet -> + filterSelectedSetList = displaySet + Common.showLog("当前显示筛选数据 选中状态更新: ${displaySet.size}") + updateCurrentIsAllSelectStatus() + } list?.let { - //倒序(最近的在前面) - sortByDayReverse = Common.getSortByDayNewToOld(it) - //正序(最远的在前面) - sortedByPositive = Common.getSortByDayOldToNew(sortByDayReverse) + + //降序(最近的在前面) + sortByDateReverse = Common.getSortByDayNewToOld(it) + //升序(时间最远的在前面) + sortedByDatePositive = Common.getSortByDayOldToNew(sortByDateReverse) sortBySizeBigToSmall = Common.getSortBySizeBigToSmall(it) sortBySizeSmallToBig = Common.getSortBySizeSmallToBig(it) - sizeAdapter = PhotoDisplayDateChildAdapter(this@PhotoSortingActivity) - dateAdapter = PhotoDisplayDateAdapter(this@PhotoSortingActivity).apply { - setData(sortByDayReverse) + sizeSortAdapter = PhotoDisplayDateChildAdapter( + this@PhotoSortingActivity, + columns, viewModel + ) { path, isAdd, allSelected -> +// updateSelectedList(isAdd, path) +// updateCurrentIsAllSelectStatus() + viewModel.toggleSelection(isAdd, path) } + dateAdapter = + PhotoDisplayDateAdapter( + this@PhotoSortingActivity, + columns, + viewModel + ) { actionPath, isAdd -> +// updateSelectedList(isAdd, actionPath) +// updateCurrentIsAllSelectStatus() + viewModel.toggleSelection(isAdd, actionPath) + }.apply { + setData(sortByDateReverse) + } setDateAdapter() setFilter() binding.run { - - lifecycleScope.launch { - ScanRepository.instance.selectedFlow.collect { newSet -> - println("选中集合变化:$newSet") - tvSelectCounts.text = newSet.size.toString() - } - } - tvSelectCounts.setOnClickListener { + tvRecover.setOnClickListener { lifecycleScope.copySelectedFilesAsync( - selectedSet = ScanRepository.instance.selectedFlow.value, + selectedSet = filterSelectedSetList, folder = Common.recoveryPhotoDir, onProgress = { currentCounts: Int, fileName: String, success: Boolean -> @@ -109,10 +161,10 @@ class PhotoSortingActivity : BaseActivity() { } } - btnDelete.setOnClickListener { + tvDelete.setOnClickListener { lifecycleScope.deleteFilesAsync( - selectedSet = ScanRepository.instance.selectedFlow.value, - onProgress = { currentCounts: Int, path:String, success: Boolean -> + selectedSet = filterSelectedSetList, + onProgress = { currentCounts: Int, path: String, success: Boolean -> ScanManager.showLog( "--------删除图片 ", "----------${currentCounts} ${path}" @@ -124,136 +176,203 @@ class PhotoSortingActivity : BaseActivity() { } } - linear.setOnCheckedChangeListener { group, checkedId -> - when (checkedId) { - R.id.btn_date_old_to_new -> { - setDateAdapter() - dateAdapter?.setData(sortedByPositive) - sortReverse = false - } + imSort.setOnClickListener { + sortDialogFragment = sortDialogFragment ?: SortDialogFragment { + when (it) { + SORT_ASC_DATE -> { + setDateAdapter() + dateAdapter?.setData(sortedByDatePositive) + sortReverse = false + } - R.id.btn_date_new_to_old -> { - setDateAdapter() - dateAdapter?.setData(sortByDayReverse) - sortReverse = true - } + SORT_DESC_DATE -> { + setDateAdapter() + dateAdapter?.setData(sortByDateReverse) + sortReverse = true + } - R.id.btn_size_big_to_small -> { - setSizeAdapter() - sizeAdapter?.setData(sortBySizeBigToSmall) - sortReverse = true - } + SORT_DESC_SIZE -> { + setSizeAdapter() + sizeSortAdapter?.setData(sortBySizeBigToSmall) + sortReverse = true + } - R.id.btn_size_small_to_big -> { - setSizeAdapter() - sizeAdapter?.setData(sortBySizeSmallToBig) - sortReverse = false + SORT_ASC_SIZE -> { + setSizeAdapter() + sizeSortAdapter?.setData(sortBySizeSmallToBig) + sortReverse = false + } } } - - + sortDialogFragment?.show(supportFragmentManager, "") } - imSelectAll.setOnClickListener { - imSelectAll.isSelected = !imSelectAll.isSelected - sizeAdapter?.setAllSelected(imSelectAll.isSelected) - dateAdapter?.setAllSelected(imSelectAll.isSelected) - } + //全选按钮 只对当前显示的数据有效 + tvSelectAll.setOnClickListener { + it.isSelected = !it.isSelected + dateAdapter?.setAllSelected(it.isSelected) + sizeSortAdapter?.setAllSelected(it.isSelected) + } } - - } } + private fun updateCurrentIsAllSelectStatus() { + filterSelectedSetList.size.let { + binding.tvSelectCounts.text = it.toString() + updateButtonCounts(it) + when (binding.recyclerView.adapter) { + is PhotoDisplayDateAdapter -> { + val adapter = binding.recyclerView.adapter as PhotoDisplayDateAdapter + binding.tvSelectAll.isSelected = it == adapter.getTotalChildCount() + } + + is PhotoDisplayDateChildAdapter -> { + binding.tvSelectAll.isSelected = it == binding.recyclerView.adapter?.itemCount + } + } + } + } + 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) + clipToPadding = false + + } } private fun setSizeAdapter() { binding.recyclerView.run { - adapter = sizeAdapter + val aPx = 16.dpToPx(context) + val bPx = 6.dpToPx(context) + setPadding(aPx, 0, bPx, 0) + clipToPadding = false + layoutManager = GridLayoutManager(context, columns) - val gridSpacingItemDecoration = - GridSpacingItemDecoration(4, 8.dpToPx(this@PhotoSortingActivity), true) - addItemDecorationOnce(gridSpacingItemDecoration) +// resetItemDecorationOnce(mItemDecoration) + adapter = sizeSortAdapter + } } /** - * 筛选 + * 筛选和布局设置 */ private fun setFilter() { - binding.linearDate.setOnCheckedChangeListener { group, checkedId -> - when (checkedId) { - R.id.btn_date_all -> { - filterDate = FILTER_DATE_ALL + //日期筛选 + binding.run { + filterDateLayout.setOnClickListener { + setItemSelect(it as LinearLayout, true) + resources.getStringArray(R.array.filter_date).let { data -> + filterDatePopupWindows = filterDatePopupWindows ?: FilterPopupWindows( + this@PhotoSortingActivity, + data, + 0, + { clickValue -> + when (clickValue) { + data[0] -> filterDate = FILTER_DATE_ALL + data[1] -> filterDate = FILTER_DATE_1 + data[2] -> filterDate = FILTER_DATE_6 + data[3] -> filterDate = FILTER_DATE_24 + data[4] -> {} + } + startFilter() + }) { + setItemSelect(it, false) + } + + filterDatePopupWindows?.show(it) } - R.id.btn_date_within_1 -> { - filterDate = FILTER_DATE_1 - } - R.id.btn_date_customize -> { - - } } - startFilter() + //大小筛选 + filterSizeLayout.setOnClickListener { + setItemSelect(it as LinearLayout, true) + resources.getStringArray(R.array.filter_size).let { data -> + filterSizePopupWindows = filterSizePopupWindows ?: FilterPopupWindows( + this@PhotoSortingActivity, + data, + 0, + { clickValue -> + when (clickValue) { + data[0] -> filterSize = FILTER_SIZE_ALL + data[1] -> filterSize = FILTER_SIZE_1 + data[2] -> filterSize = FILTER_SIZE_5 + data[3] -> filterSize = FILTER_SIZE_OVER_5 + } + startFilter() + }) { + setItemSelect(it, false) + } + filterSizePopupWindows?.show(it) + + } + + + } + + //布局设置 + filterLayoutLinearlayout.setOnClickListener { + setItemSelect(it as LinearLayout, true) + resources.getStringArray(R.array.filter_layout).let { data -> + filterLayoutPopupWindows = filterLayoutPopupWindows ?: FilterPopupWindows( + this@PhotoSortingActivity, + data, + 1, + { clickValue -> + when (clickValue) { + data[0] -> columns = 2 + data[1] -> columns = 3 + data[2] -> columns = 4 + } + when (binding.recyclerView.adapter) { + is PhotoDisplayDateAdapter -> { + dateAdapter?.setColumns(columns) + } + + is PhotoDisplayDateChildAdapter -> { + binding.recyclerView.layoutManager = + GridLayoutManager(this@PhotoSortingActivity, columns) + sizeSortAdapter?.setColumns(columns) + } + } + }) { + setItemSelect(it, false) + } + } + filterLayoutPopupWindows?.show(it) + + } + + } - binding.linearSize.setOnCheckedChangeListener { group, checkedId -> - when (checkedId) { - R.id.btn_size_all -> { - filterSize = FILTER_SIZE_ALL - } + } - R.id.btn_size_1m -> { - filterSize = FILTER_SIZE_1 - } - R.id.btn_size_5m -> { - filterSize = FILTER_SIZE_5 - } - - R.id.btn_size_over_5m -> { - filterSize = FILTER_SIZE_OVER_5 - } + /** + * 根据当前显示的实际数据选中的数量来更新按钮文字和状态 + */ + private fun updateButtonCounts(selectedCounts: Int) { + binding.run { + if (selectedCounts <= 0) { + tvDelete.isEnabled = false + tvRecover.isEnabled = false + } else { + tvDelete.isEnabled = true + tvRecover.isEnabled = true } - startFilter() + tvDelete.text = getString(R.string.delete_placeholder, selectedCounts) + tvRecover.text = getString(R.string.recover_placeholder, selectedCounts) } - binding.rgLayout.setOnCheckedChangeListener { group, checkedId -> - - when (checkedId) { - R.id.rb_columns_2 -> { - columns = 2 - } - - R.id.rb_columns_3 -> { - columns = 3 - } - - R.id.rb_columns_4 -> { - columns = 4 - } - - } - - when (binding.recyclerView.adapter) { - is PhotoDisplayDateAdapter -> { - dateAdapter?.setColumns(columns) - } - - is PhotoDisplayDateChildAdapter -> { - binding.recyclerView.layoutManager = - GridLayoutManager(this@PhotoSortingActivity, columns) - - } - } - } } @@ -262,33 +381,41 @@ class PhotoSortingActivity : BaseActivity() { */ private fun startFilter() { when (binding.recyclerView.adapter) { + //当前是时间排序 is PhotoDisplayDateAdapter -> { - val list = if (sortReverse) sortByDayReverse else sortedByPositive + //确定当前排序 + val list = if (sortReverse) sortByDateReverse else sortedByDatePositive val filterSizeCovert = filterSizeCovert(filterSize) list.filterWithinMonths(filterDate) - .filterBySize(filterSizeCovert.first, filterSizeCovert.second).let { - dateAdapter?.setData(it) - ScanManager.showLog( - "---", - "---date-----${it.size} filterDate=${filterDate} first=${filterSizeCovert.first} second=${filterSizeCovert.second} dateAdapter=${dateAdapter}" - ) + .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}") + dateAdapter?.resetAllValue(null) + dateAdapter?.setData(currentList) + } } - + //当前是大小排序 is PhotoDisplayDateChildAdapter -> { val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig val filterSizeCovert = filterSizeCovert(filterSize) list.filterWithinMonthsList(filterDate) - .filterBySizeList(filterSizeCovert.first, filterSizeCovert.second).let { - sizeAdapter?.setData(it) - ScanManager.showLog( - "---", - "----size----${it.size} filterDate=${filterDate} first=${filterSizeCovert.first} second=${filterSizeCovert.second} sizeAdapter=${sizeAdapter}" - ) + .filterBySizeList(filterSizeCovert.first, filterSizeCovert.second) + .let { currentList -> + //对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据 + val checkSelectListContain = + Common.checkSelectListContainSize(currentList, allSelectedSetList) + updateButtonCounts(checkSelectListContain.first) + viewModel.filterResetDisplayFlow(checkSelectListContain.second) + sizeSortAdapter?.setData(currentList) } - - } } } @@ -306,4 +433,5 @@ class PhotoSortingActivity : BaseActivity() { } + } \ No newline at end of file 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 8cdc0ba..5545853 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 @@ -11,7 +11,7 @@ data class ResultPhotosFiles( val path: String?= null, val size: Long, // 字节 val lastModified: Long, // 时间戳 - var isSelected: Boolean = false // 选中状态 +// var isSelected: Boolean = false // 选中状态 ): Parcelable{ val targetFile: File? get() = path?.let { File(it) } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/photo/SortDialogFragment.kt b/app/src/main/java/com/ux/video/file/filerecovery/photo/SortDialogFragment.kt new file mode 100644 index 0000000..14dabe3 --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/photo/SortDialogFragment.kt @@ -0,0 +1,116 @@ +package com.ux.video.file.filerecovery.photo + +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import com.ux.video.file.filerecovery.R +import com.ux.video.file.filerecovery.databinding.CommonLayoutSortItemBinding +import com.ux.video.file.filerecovery.databinding.DialogSortBinding + + +class SortDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFragment() { + + private lateinit var binding: DialogSortBinding + private var clickType = PhotoSortingActivity.SORT_DESC_DATE + + private lateinit var LayoutList: List + override fun onStart() { + super.onStart() + dialog?.window?.apply { + setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setGravity(Gravity.BOTTOM) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = DialogSortBinding.inflate(inflater) + binding.run { + LayoutList = listOf( + ascendingSizeLayout, + descendingSizeLayout, + ascendingDateLayout, + descendingDateLayout + ) + setMutualExclusion(LayoutList) + resetSelectedSortType(clickType) + setIconText( + LayoutList[0], + R.drawable.icon_ascending_size, + getString(R.string.size), + getString(R.string.asc_size_describe) + ) + setIconText( + LayoutList[1], + R.drawable.icon_descending_size, + getString(R.string.size), + getString(R.string.desc_size_describe) + ) + setIconText( + LayoutList[2], + R.drawable.icon_ascending_date, + getString(R.string.date), + getString(R.string.asc_date_describe) + ) + setIconText( + LayoutList[3], + R.drawable.icon_descending_date, + getString(R.string.date), + getString(R.string.desc_date_describe) + ) + + tvCancel.setOnClickListener { dismiss() } + tvOk.setOnClickListener { + onClickSort(clickType) + dismiss() + } + } + return binding.root + } + + + fun resetSelectedSortType(type: Int) { + LayoutList.forEachIndexed { index, item -> + item.checkImage.isSelected = index == type + } + } + + + private fun setIconText( + itemBinding: CommonLayoutSortItemBinding, + iconId: Int, + text: String, + subText: String + ) { + itemBinding.run { + sortType.text = text + sortSubType.text = subText + icon.setImageResource(iconId) + } + } + + private fun setMutualExclusion(listOf: List) { + for (i in 0 until listOf.size) { + val binding = listOf[i] + binding.root.setOnClickListener { + for (j in 0 until listOf.size) { + listOf[j].checkImage.isSelected = false + } + clickType = i + binding.checkImage.isSelected = true + } + } + + } +} diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ExitDialogFragment.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ExitDialogFragment.kt new file mode 100644 index 0000000..bdcf522 --- /dev/null +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ExitDialogFragment.kt @@ -0,0 +1,42 @@ +package com.ux.video.file.filerecovery.result + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import com.ux.video.file.filerecovery.databinding.DialogExitBinding +import com.ux.video.file.filerecovery.databinding.DialogPermissionBinding + + +class ExitDialogFragment(val onClickExit: () -> Unit) : DialogFragment() { + + private lateinit var binding: DialogExitBinding + override fun onStart() { + super.onStart() + dialog?.window?.apply { + setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = DialogExitBinding.inflate(inflater) + binding.run { + cancel.setOnClickListener { dismiss() } + exit.setOnClickListener { + onClickExit() + dismiss() + } + } + return binding.root + } +} diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt index 37201d5..3951f76 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultAdapter.kt @@ -1,5 +1,6 @@ package com.ux.video.file.filerecovery.result +import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup @@ -27,15 +28,19 @@ class ScanResultAdapter( false ) + @SuppressLint("SetTextI18n") override fun bindItem( holder: VHolder, item: ResultPhotos ) { holder.vb.run { + val imageViews = listOf(im1, im2, im3) item.run { - textDirNameCount.text = "$dirName(${allFiles.size})" + imageViewEnter.setOnClickListener { onClickItem(allFiles) } + textDirNameCount.text = dirName + textFileCounts.text = "(${allFiles.size})" val takeFiles = allFiles.take(3) imageViews.forEachIndexed { index, imageView -> if (index < takeFiles.size) { @@ -59,7 +64,7 @@ class ScanResultAdapter( .load(file) .apply( RequestOptions() - .transform(CenterCrop(), RoundedCorners(15.dpToPx(context))) + .transform(CenterCrop(), RoundedCorners(8.dpToPx(context))) ) .into(imageView) } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt index 6dcb1fc..2fec3f1 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/result/ScanResultDisplayActivity.kt @@ -2,13 +2,27 @@ package com.ux.video.file.filerecovery.result import android.content.Intent import android.view.LayoutInflater +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.ux.video.file.filerecovery.R import com.ux.video.file.filerecovery.base.BaseActivity import com.ux.video.file.filerecovery.databinding.ActivityScanResultDisplayBinding import com.ux.video.file.filerecovery.photo.PhotoSortingActivity import com.ux.video.file.filerecovery.photo.ResultPhotos import com.ux.video.file.filerecovery.result.ScanningActivity +import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat +import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.utils.ScanRepository /** @@ -16,9 +30,12 @@ import com.ux.video.file.filerecovery.utils.ScanRepository */ class ScanResultDisplayActivity : BaseActivity() { private var scanResultAdapter: ScanResultAdapter? = null + private var scanType: Int = VALUE_SCAN_TYPE_photo + private var exitDialog: ExitDialogFragment? = null companion object { val KEY_SCAN_RESULT = "scan_result" + } override fun inflateBinding(inflater: LayoutInflater): ActivityScanResultDisplayBinding = @@ -28,10 +45,17 @@ class ScanResultDisplayActivity : BaseActivity super.initView() val list: ArrayList? = intent.getParcelableArrayListExtraCompat(KEY_SCAN_RESULT) - scanResultAdapter = ScanResultAdapter(this@ScanResultDisplayActivity){ folderLists-> - startActivity(Intent(this@ScanResultDisplayActivity, PhotoSortingActivity::class.java).apply { - putParcelableArrayListExtra(PhotoSortingActivity.KEY_PHOTO_FOLDER_FILE, folderLists) - }) + scanResultAdapter = ScanResultAdapter(this@ScanResultDisplayActivity) { folderLists -> + startActivity( + Intent( + this@ScanResultDisplayActivity, + PhotoSortingActivity::class.java + ).apply { + putParcelableArrayListExtra( + PhotoSortingActivity.KEY_PHOTO_FOLDER_FILE, + folderLists + ) + }) } binding.recyclerResult.run { adapter = scanResultAdapter @@ -56,4 +80,50 @@ class ScanResultDisplayActivity : BaseActivity // scanResultAdapter?.setData(it) // } } + + override fun initData() { + super.initData() + scanType = intent.getIntExtra(KEY_SCAN_TYPE, VALUE_SCAN_TYPE_photo) + setSelectTypeTitle(scanType) + binding.imageViewBack.setOnClickListener { dealExit() } + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + dealExit() + } + }) + } + + + private fun dealExit(){ + exitDialog = exitDialog?:ExitDialogFragment(){ + finish() + } + exitDialog?.show(supportFragmentManager,"") + } + private fun setSelectTypeTitle(fileType: Int) { + binding.run { + when (fileType) { + VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_deleted_photo -> { + title.text = getString(R.string.photo_title) + } + + VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_deleted_video -> { + title.text = getString(R.string.video_title) + } + + VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_deleted_audio -> { + title.text = getString(R.string.audio_title) + } + + VALUE_SCAN_TYPE_documents, VALUE_SCAN_TYPE_deleted_documents -> { + title.text = getString(R.string.document_title) + } + } + } + + + } } \ No newline at end of file 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 9ae5876..73d0cfa 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 @@ -11,6 +11,15 @@ import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALU import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_DOCUMENT import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_PHOTO import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity.Companion.VALUE_VIDEO +import com.ux.video.file.filerecovery.utils.Common.KEY_SCAN_TYPE +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video import com.ux.video.file.filerecovery.utils.ScanManager import com.ux.video.file.filerecovery.utils.ScanRepository import com.ux.video.file.filerecovery.utils.ScanState @@ -21,15 +30,15 @@ import kotlinx.coroutines.launch class ScanningActivity : BaseActivity() { companion object { - val KEY_SCAN_TYPE = "scan_type" - val VALUE_SCAN_TYPE_photo = 0 - val VALUE_SCAN_TYPE_deleted_photo = 1 - val VALUE_SCAN_TYPE_video = 2 - val VALUE_SCAN_TYPE_deleted_video = 3 - val VALUE_SCAN_TYPE_audio = 4 - val VALUE_SCAN_TYPE_deleted_audio = 5 - val VALUE_SCAN_TYPE_documents = 6 - val VALUE_SCAN_TYPE_deleted_documents = 7 +// val KEY_SCAN_TYPE = "scan_type" +// val VALUE_SCAN_TYPE_photo = 0 +// val VALUE_SCAN_TYPE_deleted_photo = 1 +// val VALUE_SCAN_TYPE_video = 2 +// val VALUE_SCAN_TYPE_deleted_video = 3 +// val VALUE_SCAN_TYPE_audio = 4 +// val VALUE_SCAN_TYPE_deleted_audio = 5 +// val VALUE_SCAN_TYPE_documents = 6 +// val VALUE_SCAN_TYPE_deleted_documents = 7 } private var scanType: Int = VALUE_SCAN_TYPE_photo @@ -44,7 +53,7 @@ class ScanningActivity : BaseActivity() { VALUE_SCAN_TYPE_photo, VALUE_SCAN_TYPE_video, VALUE_SCAN_TYPE_audio, VALUE_SCAN_TYPE_documents -> scanAll() VALUE_SCAN_TYPE_deleted_photo, VALUE_SCAN_TYPE_deleted_video, VALUE_SCAN_TYPE_deleted_audio, VALUE_SCAN_TYPE_deleted_documents -> scanDeleted() } - binding.scanProgress.setCenterImage(R.drawable.ic_scan_file) + binding.scanProgress.setCenterImage(R.drawable.im_photo_center_image) binding.imageViewBack.setOnClickListener { finish() } } @@ -176,6 +185,7 @@ class ScanningActivity : BaseActivity() { ) } + finish() } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/CircleImageProgressView.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/CircleImageProgressView.kt index d5dba7c..00f12f6 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/CircleImageProgressView.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/CircleImageProgressView.kt @@ -7,6 +7,7 @@ import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint +import android.graphics.Path import android.graphics.RectF import android.util.AttributeSet import android.view.View @@ -19,7 +20,7 @@ class CircleImageProgressView @JvmOverloads constructor( private val paintBg = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE strokeWidth = 20f - color = Color.parseColor("#C9DDEF") // 背景浅蓝 + color = Color.parseColor("#9CC0EC") // 背景浅蓝 } private val paintProgress = Paint(Paint.ANTI_ALIAS_FLAG).apply { @@ -57,44 +58,42 @@ class CircleImageProgressView @JvmOverloads constructor( override fun onDraw(canvas: Canvas) { super.onDraw(canvas) + val stroke = paintBg.strokeWidth - val rect = RectF(stroke, stroke, width - stroke, height - stroke) + val radius = (width / 2f) - (stroke / 2f) - // 背景圆环 + val rect = RectF( + (width / 2f) - radius, + (height / 2f) - radius, + (width / 2f) + radius, + (height / 2f) + radius + ) + + // 1. 背景圆环 canvas.drawArc(rect, 0f, 360f, false, paintBg) - // 中心图片 + + // 2. 中心图片(圆形裁剪) centerBitmap?.let { -// val left = (width - it.width) / 2f -// val top = (height - it.height) / 2f -// canvas.drawBitmap(it, left, top, null) + val innerRadius = radius - stroke / 2f // 图片只占内圆,不盖住圆环 -// val desiredSize = width * imageScale -// val rectDst = RectF( -// (width - desiredSize) / 2f, -// (height - desiredSize) / 2f, -// (width + desiredSize) / 2f, -// (height + desiredSize) / 2f -// ) -// canvas.drawBitmap(it, null, rectDst, null) - - val innerRadius = (width / 2f) - stroke // 圆环内半径 val left = (width / 2f) - innerRadius val top = (height / 2f) - innerRadius val right = (width / 2f) + innerRadius val bottom = (height / 2f) + innerRadius - val rectDst = RectF(left, top, right, bottom) - // 缩放图片填满内圆区域 -// canvas.drawBitmap(it, null, rectDst, null) + val saveCount = canvas.save() + val path = Path().apply { + addCircle(width / 2f, height / 2f, innerRadius, Path.Direction.CW) + } + canvas.clipPath(path) + canvas.drawBitmap(it, null, rectDst, null) + canvas.restoreToCount(saveCount) } - - - // 进度圆弧 + // 3. 进度圆弧 val sweepAngle = progress * 360f / 100f canvas.drawArc(rect, -90f, sweepAngle, false, paintProgress) - } } 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 af59f33..f77e4a9 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 @@ -4,19 +4,38 @@ import android.content.Context import android.icu.text.SimpleDateFormat import android.icu.util.Calendar import android.os.Environment +import android.util.Log +import android.view.ViewGroup +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 java.util.Date import java.util.Locale import kotlin.collections.sortedBy object Common { + + val itemSpacing = 10 + val horizontalSpacing = 16 + + val KEY_SCAN_TYPE = "scan_type" + val VALUE_SCAN_TYPE_photo = 0 + val VALUE_SCAN_TYPE_deleted_photo = 1 + val VALUE_SCAN_TYPE_video = 2 + val VALUE_SCAN_TYPE_deleted_video = 3 + val VALUE_SCAN_TYPE_audio = 4 + val VALUE_SCAN_TYPE_deleted_audio = 5 + val VALUE_SCAN_TYPE_documents = 6 + val VALUE_SCAN_TYPE_deleted_documents = 7 + + val rootDir = Environment.getExternalStorageDirectory() - val dateFormat = SimpleDateFormat("MMM dd yyyy", Locale.ENGLISH) - val recoveryPhotoDir = "MyAllRecovery/Photo" + val dateFormat = SimpleDateFormat("MMMM d,yyyy", Locale.ENGLISH) + val recoveryPhotoDir = "MyAllRecovery/Photo" /** - * 默认按照日期分类,将最新的排前面 + * 默认按照日期分类,将最新的排前面 降序 */ fun getSortByDayNewToOld(list: ArrayList): List>> { @@ -31,21 +50,21 @@ object Common { } /** - * 按照日期排序, 时间最早的排前面 + * 按照日期排序, 时间最早的排前面 升序 * */ fun getSortByDayOldToNew(list: List>>) = list.sortedBy { dateFormat.parse(it.first)?.time ?: 0L } /** - * 按照文件大小排序,将最大的排前面 + * 按照文件大小排序,将最大的排前面 降序 */ fun getSortBySizeBigToSmall(list: ArrayList) = list.sortedByDescending { it.size } /** - * 按照文件大小排序,将最大的排前面 + * 按照文件大小排序,将最小的排前面 升序 */ fun getSortBySizeSmallToBig(list: ArrayList) = list.sortedBy { it.size @@ -77,7 +96,7 @@ object Common { * @param months 筛选months月之内的数据 */ fun filterWithinOneMonthByDay( - grouped: List>>,months: Int + grouped: List>>, months: Int ): List>> { val today = Calendar.getInstance() val oneMonthAgo = Calendar.getInstance().apply { @@ -89,10 +108,11 @@ object Common { day != null && !day.before(oneMonthAgo.time) && !day.after(today.time) } } + /** * @param months 筛选months月之内的数据 */ - fun filterWithinOneMonth(list: List,months: Int): List { + fun filterWithinOneMonth(list: List, months: Int): List { val today = Calendar.getInstance() val oneMonthAgo = Calendar.getInstance().apply { add(Calendar.MONTH, -months) @@ -104,4 +124,55 @@ object Common { } } + /** + * 设置全部子View的选中 + */ + fun setItemSelect(view: ViewGroup, boolean: Boolean) { + for (i in 0 until view.childCount) { + val child = view.getChildAt(i) + child.isSelected = boolean + } + } + + + + + /** + * @param list 筛选后的显示的数据 + * @param selected 实际选中的数据集合 + * @return 显示的选中数量和选中集合 + */ + fun checkSelectListContainDate( + list: List>>, + 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()) + isSelected + } + } + + showLog("-------totalSelectedCount = $totalSelectedCount currentSelected=${currentSelected.size}") + return 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 + } + + fun showLog(msg: String) { + Log.d("============", msg) + } } \ No newline at end of file diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/CustomTextView.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/CustomTextView.kt index 2a365b9..bbeeb85 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/CustomTextView.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/CustomTextView.kt @@ -17,10 +17,15 @@ class CustomTextView @JvmOverloads constructor( private var alimama: Typeface? = null } + private var selectedText: String? = null + private var normalText: String? = null + init { attrs?.let { val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomTextView) val type = typedArray.getInt(R.styleable.CustomTextView_fontType, 0) + selectedText = typedArray.getString(R.styleable.CustomTextView_selected_text) + normalText = typedArray.getString(R.styleable.CustomTextView_normal_text) typedArray.recycle() Typeface.create("sans-serif-light", Typeface.NORMAL) // Roboto Light @@ -31,9 +36,10 @@ class CustomTextView @JvmOverloads constructor( 0 -> { typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) } + 1 -> { - typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL) } 2 -> { @@ -43,12 +49,19 @@ class CustomTextView @JvmOverloads constructor( typeface = alimama } - 3 -> { - typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL) - } else -> typeface = Typeface.DEFAULT } } } + + override fun setSelected(selected: Boolean) { + super.setSelected(selected) + updateText() + } + + private fun updateText() { +// Common.showLog("--updateText:$isSelected selectedText=${selectedText} normalText=${normalText} text=${text}") + text = if (isSelected) selectedText ?: text else normalText ?: text + } } 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 ef6e30d..676e568 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 @@ -29,11 +29,9 @@ object ExtendFunctions { } } - fun RecyclerView.addItemDecorationOnce(decoration: RecyclerView.ItemDecoration) { - for (i in 0 until itemDecorationCount) { - if (getItemDecorationAt(i)::class == decoration::class) { - return // 已经有同类型,避免重复 - } + fun RecyclerView.resetItemDecorationOnce(decoration: RecyclerView.ItemDecoration) { + while (itemDecorationCount > 0) { + removeItemDecorationAt(0) } addItemDecoration(decoration) } diff --git a/app/src/main/java/com/ux/video/file/filerecovery/utils/GridSpacingItemDecoration.kt b/app/src/main/java/com/ux/video/file/filerecovery/utils/GridSpacingItemDecoration.kt index 6ce22e0..7e2b760 100644 --- a/app/src/main/java/com/ux/video/file/filerecovery/utils/GridSpacingItemDecoration.kt +++ b/app/src/main/java/com/ux/video/file/filerecovery/utils/GridSpacingItemDecoration.kt @@ -3,11 +3,19 @@ package com.ux.video.file.filerecovery.utils import android.graphics.Rect import android.view.View import androidx.recyclerview.widget.RecyclerView +import com.ux.video.file.filerecovery.App +import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx + +/** + * @param spanCount 列数 + * @param spacing 列与列之间的间距(dp) + * @param edgeSpacing RecyclerView 左右两边的边距(dp) + */ class GridSpacingItemDecoration( private val spanCount: Int, - private val spacing: Int, - private val includeEdge: Boolean + private val itemSpacing: Int, + private val edgeSpacing: Int ) : RecyclerView.ItemDecoration() { override fun getItemOffsets( @@ -16,20 +24,31 @@ class GridSpacingItemDecoration( val position = parent.getChildAdapterPosition(view) val column = position % spanCount - if (includeEdge) { - outRect.left = spacing - column * spacing / spanCount - outRect.right = (column + 1) * spacing / spanCount + val itemSpacingPx = itemSpacing.dpToPx(App.mAppContext) + val edgePx = edgeSpacing.dpToPx(App.mAppContext) - if (position < spanCount) { - outRect.top = spacing + when (column) { + 0 -> { // 第一列 + outRect.left = edgePx + outRect.right = itemSpacingPx / 2 } - outRect.bottom = spacing - } else { - outRect.left = column * spacing / spanCount - outRect.right = spacing - (column + 1) * spacing / spanCount - if (position >= spanCount) { - outRect.top = spacing + spanCount - 1 -> { // 最后一列 + outRect.left = itemSpacingPx / 2 + outRect.right = edgePx + } + else -> { // 中间列 + outRect.left = itemSpacingPx / 2 + outRect.right = itemSpacingPx / 2 } } + + // 顶部间距(第一行也加) + if (position < spanCount) { + outRect.top = itemSpacingPx + } + // 底部间距 + outRect.bottom = itemSpacingPx } } + + 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 57f5747..efe790b 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 @@ -6,6 +6,14 @@ import android.util.Log import com.ux.video.file.filerecovery.photo.ResultPhotos import com.ux.video.file.filerecovery.photo.ResultPhotosFiles import com.ux.video.file.filerecovery.result.ScanningActivity +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_audio +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_audio +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_documents +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_photo +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_deleted_video +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_documents +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_photo +import com.ux.video.file.filerecovery.utils.Common.VALUE_SCAN_TYPE_video import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -56,19 +64,19 @@ object ScanManager { } else { var fileCheckBoolean: Boolean = false when (type) { - ScanningActivity.VALUE_SCAN_TYPE_photo -> { + VALUE_SCAN_TYPE_photo -> { fileCheckBoolean = isFormatFile(file, IMAGE_FILE) && isValidImage(file) } - ScanningActivity.VALUE_SCAN_TYPE_video -> { + VALUE_SCAN_TYPE_video -> { fileCheckBoolean = isFormatFile(file, VIDEO_FILE) } - ScanningActivity.VALUE_SCAN_TYPE_audio -> { + VALUE_SCAN_TYPE_audio -> { fileCheckBoolean = isFormatFile(file, AUDIO_FILE) } - ScanningActivity.VALUE_SCAN_TYPE_documents -> { + VALUE_SCAN_TYPE_documents -> { fileCheckBoolean = file.isFile && isFormatFile(file, DOCUMENT_FILE) } } @@ -126,20 +134,20 @@ object ScanManager { if (insideHidden) { var fileCheckBoolean: Boolean = false when (type) { - ScanningActivity.VALUE_SCAN_TYPE_deleted_photo -> { + VALUE_SCAN_TYPE_deleted_photo -> { fileCheckBoolean = isFormatFile(file, IMAGE_FILE) && isValidImage(file) } - ScanningActivity.VALUE_SCAN_TYPE_deleted_video -> { + VALUE_SCAN_TYPE_deleted_video -> { fileCheckBoolean = isFormatFile(file, VIDEO_FILE) } - ScanningActivity.VALUE_SCAN_TYPE_deleted_audio -> { + VALUE_SCAN_TYPE_deleted_audio -> { fileCheckBoolean = isFormatFile(file, AUDIO_FILE) } - ScanningActivity.VALUE_SCAN_TYPE_deleted_documents -> { + VALUE_SCAN_TYPE_deleted_documents -> { fileCheckBoolean = file.isFile && isFormatFile(file, DOCUMENT_FILE) } } 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 5fa5695..999c10d 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 @@ -2,49 +2,78 @@ package com.ux.video.file.filerecovery.utils import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel import com.ux.video.file.filerecovery.photo.ResultPhotos import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class ScanRepository private constructor() { - - //---------扫描结果 - private val _photoResults = MutableLiveData>() - val photoResults: LiveData> get() = _photoResults - - private val _videoResults = MutableLiveData>() - val videoResults: LiveData> get() = _videoResults +class ScanRepository : ViewModel() { +// private val _selectedFlow = MutableStateFlow>(mutableSetOf()) +// val selectedFlow: StateFlow> = _selectedFlow +// +// +// private val _selectedDisplayFlow = MutableStateFlow>(mutableSetOf()) +// val selectedDisplayFlow: StateFlow> = _selectedDisplayFlow - //----------查看指定目录里面的文件 - fun setPhotoResults(data: List) { - _photoResults.value = data - } + private val _selectedLiveData = MutableLiveData>(emptySet()) + val selectedLiveData: LiveData> = _selectedLiveData - fun setVideoResults(data: List) { - _videoResults.value = data - } + // 当前筛选显示的选中项 + private val _selectedDisplayLiveData = MutableLiveData>(emptySet()) + val selectedDisplayLiveData: LiveData> = _selectedDisplayLiveData - private val _selectedFlow = MutableStateFlow>(emptySet()) - val selectedFlow: StateFlow> = _selectedFlow + fun toggleSelection(isAdd: Boolean, path: String) { + val current = _selectedLiveData.value?.toMutableSet() ?: mutableSetOf() + val currentDisplay = _selectedDisplayLiveData.value?.toMutableSet() ?: mutableSetOf() - fun toggleSelection(boolean: Boolean, path: String) { - val current = _selectedFlow.value.toMutableSet() - if (boolean) { + if (isAdd) { current.add(path) - ScanManager.showLog("_------", "add selected ${path}") + currentDisplay.add(path) +// Common.showLog( "add selected ${path}") } else { current.remove(path) - ScanManager.showLog("_------", "remove selected ${path}") + currentDisplay.remove(path) +// Common.showLog( "remove selected ${path}") } - _selectedFlow.value = current + Common.showLog( "toggleSelection------------ _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ") + // LiveData 使用新对象赋值,保证 observer 触发 + _selectedLiveData.value = current.toSet() + _selectedDisplayLiveData.value = currentDisplay.toSet() } - fun checkIsSelect(path: String) : Boolean{ - val current = _selectedFlow.value.toMutableSet() - return current.contains(path) +// fun toggleSelection(isAdd: Boolean, path: String) { +// val current = _selectedFlow.value.toMutableSet() +// val currentDisplay = _selectedDisplayFlow.value.toMutableSet() +// if (isAdd) { +// current.add(path) +// currentDisplay.add(path) +// Common.showLog( "add selected ${path}") +// } else { +// current.remove(path) +// currentDisplay.remove(path) +// Common.showLog( "remove selected ${path}") +// } +// _selectedFlow.value = current +// _selectedDisplayFlow.value = currentDisplay +// } + + /** + * 数据筛选后重置当前显示的选中集合 + */ + fun filterResetDisplayFlow(list: MutableSet){ + _selectedDisplayLiveData.value = list.toSet() + + Common.showLog( "筛选后重置 _selectedDisplayFlow=${_selectedDisplayLiveData.value?.size} _selectedFlow=${_selectedLiveData.value?.size} ") + } + + + + fun checkIsSelect(path: String): Boolean { + val current = _selectedDisplayLiveData.value + return current?.contains(path) == true } diff --git a/app/src/main/res/color/selector_black_blue.xml b/app/src/main/res/color/selector_black_blue.xml new file mode 100644 index 0000000..b02353b --- /dev/null +++ b/app/src/main/res/color/selector_black_blue.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_c7c7cc_blue.xml b/app/src/main/res/color/selector_c7c7cc_blue.xml new file mode 100644 index 0000000..f208423 --- /dev/null +++ b/app/src/main/res/color/selector_c7c7cc_blue.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_delete_button_enable.xml b/app/src/main/res/color/selector_delete_button_enable.xml new file mode 100644 index 0000000..da652f9 --- /dev/null +++ b/app/src/main/res/color/selector_delete_button_enable.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/color/selector_recover_button_enable.xml b/app/src/main/res/color/selector_recover_button_enable.xml new file mode 100644 index 0000000..da652f9 --- /dev/null +++ b/app/src/main/res/color/selector_recover_button_enable.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/color/selector_switch_thumb_color.xml b/app/src/main/res/color/selector_switch_thumb_color.xml new file mode 100644 index 0000000..32915a7 --- /dev/null +++ b/app/src/main/res/color/selector_switch_thumb_color.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/color/selector_switch_track_color.xml b/app/src/main/res/color/selector_switch_track_color.xml new file mode 100644 index 0000000..fc7f5ec --- /dev/null +++ b/app/src/main/res/color/selector_switch_track_color.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_dialog_btn_allow_solid_8.xml b/app/src/main/res/drawable/bg_dialog_btn_allow_solid_8.xml index 3fe8798..b122147 100644 --- a/app/src/main/res/drawable/bg_dialog_btn_allow_solid_8.xml +++ b/app/src/main/res/drawable/bg_dialog_btn_allow_solid_8.xml @@ -2,6 +2,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_dialog_btn_cancel_stoke_8.xml b/app/src/main/res/drawable/bg_dialog_btn_cancel_stoke_8.xml index 63cb687..d091084 100644 --- a/app/src/main/res/drawable/bg_dialog_btn_cancel_stoke_8.xml +++ b/app/src/main/res/drawable/bg_dialog_btn_cancel_stoke_8.xml @@ -2,6 +2,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_rectangle_blue_right_8.xml b/app/src/main/res/drawable/bg_rectangle_blue_right_8.xml new file mode 100644 index 0000000..416d06a --- /dev/null +++ b/app/src/main/res/drawable/bg_rectangle_blue_right_8.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_rectangle_d5ebff_right_8.xml b/app/src/main/res/drawable/bg_rectangle_d5ebff_right_8.xml new file mode 100644 index 0000000..2e907cf --- /dev/null +++ b/app/src/main/res/drawable/bg_rectangle_d5ebff_right_8.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_rectangle_f5f5fa_left_8.xml b/app/src/main/res/drawable/bg_rectangle_f5f5fa_left_8.xml new file mode 100644 index 0000000..98b7434 --- /dev/null +++ b/app/src/main/res/drawable/bg_rectangle_f5f5fa_left_8.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_dialog_permission_white_20.xml b/app/src/main/res/drawable/bg_rectangle_white_20.xml similarity index 100% rename from app/src/main/res/drawable/bg_dialog_permission_white_20.xml rename to app/src/main/res/drawable/bg_rectangle_white_20.xml diff --git a/app/src/main/res/drawable/bg_rectangle_white_bottom_20.xml b/app/src/main/res/drawable/bg_rectangle_white_bottom_20.xml new file mode 100644 index 0000000..ab5d04f --- /dev/null +++ b/app/src/main/res/drawable/bg_rectangle_white_bottom_20.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_rectangle_white_left_8.xml b/app/src/main/res/drawable/bg_rectangle_white_left_8.xml new file mode 100644 index 0000000..0ebe77e --- /dev/null +++ b/app/src/main/res/drawable/bg_rectangle_white_left_8.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_rectangle_white_top_20.xml b/app/src/main/res/drawable/bg_rectangle_white_top_20.xml new file mode 100644 index 0000000..9587995 --- /dev/null +++ b/app/src/main/res/drawable/bg_rectangle_white_top_20.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_arrow_down_gray.png b/app/src/main/res/drawable/icon_arrow_down_gray.png new file mode 100644 index 0000000..5f901da Binary files /dev/null and b/app/src/main/res/drawable/icon_arrow_down_gray.png differ diff --git a/app/src/main/res/drawable/icon_arrow_up_blue.png b/app/src/main/res/drawable/icon_arrow_up_blue.png new file mode 100644 index 0000000..50b8271 Binary files /dev/null and b/app/src/main/res/drawable/icon_arrow_up_blue.png differ diff --git a/app/src/main/res/drawable/icon_ascending_date.png b/app/src/main/res/drawable/icon_ascending_date.png new file mode 100644 index 0000000..d7927be Binary files /dev/null and b/app/src/main/res/drawable/icon_ascending_date.png differ diff --git a/app/src/main/res/drawable/icon_ascending_size.png b/app/src/main/res/drawable/icon_ascending_size.png new file mode 100644 index 0000000..86e2d94 Binary files /dev/null and b/app/src/main/res/drawable/icon_ascending_size.png differ diff --git a/app/src/main/res/drawable/icon_checkmark_16dp.xml b/app/src/main/res/drawable/icon_checkmark_16dp.xml new file mode 100644 index 0000000..e72314f --- /dev/null +++ b/app/src/main/res/drawable/icon_checkmark_16dp.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_checkmark_28dp.xml b/app/src/main/res/drawable/icon_checkmark_28dp.xml new file mode 100644 index 0000000..618c31e --- /dev/null +++ b/app/src/main/res/drawable/icon_checkmark_28dp.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_checkmark_file.png b/app/src/main/res/drawable/icon_checkmark_file.png new file mode 100644 index 0000000..bbe81e6 Binary files /dev/null and b/app/src/main/res/drawable/icon_checkmark_file.png differ diff --git a/app/src/main/res/drawable/icon_checkmark_filter.png b/app/src/main/res/drawable/icon_checkmark_filter.png new file mode 100644 index 0000000..c946327 Binary files /dev/null and b/app/src/main/res/drawable/icon_checkmark_filter.png differ diff --git a/app/src/main/res/drawable/icon_descending_date.png b/app/src/main/res/drawable/icon_descending_date.png new file mode 100644 index 0000000..d229ee8 Binary files /dev/null and b/app/src/main/res/drawable/icon_descending_date.png differ diff --git a/app/src/main/res/drawable/icon_descending_size.png b/app/src/main/res/drawable/icon_descending_size.png new file mode 100644 index 0000000..fe5ab33 Binary files /dev/null and b/app/src/main/res/drawable/icon_descending_size.png differ diff --git a/app/src/main/res/drawable/icon_finish.png b/app/src/main/res/drawable/icon_finish.png new file mode 100644 index 0000000..811ced3 Binary files /dev/null and b/app/src/main/res/drawable/icon_finish.png differ diff --git a/app/src/main/res/drawable/icon_selected.xml b/app/src/main/res/drawable/icon_selected.xml deleted file mode 100644 index d175613..0000000 --- a/app/src/main/res/drawable/icon_selected.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_sort.png b/app/src/main/res/drawable/icon_sort.png new file mode 100644 index 0000000..8c7c9cc Binary files /dev/null and b/app/src/main/res/drawable/icon_sort.png differ diff --git a/app/src/main/res/drawable/icon_unselected.xml b/app/src/main/res/drawable/icon_unselected_16dp.xml similarity index 81% rename from app/src/main/res/drawable/icon_unselected.xml rename to app/src/main/res/drawable/icon_unselected_16dp.xml index 729986b..72cbe0c 100644 --- a/app/src/main/res/drawable/icon_unselected.xml +++ b/app/src/main/res/drawable/icon_unselected_16dp.xml @@ -1,9 +1,9 @@ + android:fillColor="@color/black"/> diff --git a/app/src/main/res/drawable/icon_unselected_28dp.xml b/app/src/main/res/drawable/icon_unselected_28dp.xml new file mode 100644 index 0000000..1600cfb --- /dev/null +++ b/app/src/main/res/drawable/icon_unselected_28dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_vector.png b/app/src/main/res/drawable/icon_vector.png new file mode 100644 index 0000000..c512922 Binary files /dev/null and b/app/src/main/res/drawable/icon_vector.png differ diff --git a/app/src/main/res/drawable/im_photo_center_image.png b/app/src/main/res/drawable/im_photo_center_image.png new file mode 100644 index 0000000..858fbbc Binary files /dev/null and b/app/src/main/res/drawable/im_photo_center_image.png differ diff --git a/app/src/main/res/drawable/imag_no_photo.png b/app/src/main/res/drawable/imag_no_photo.png new file mode 100644 index 0000000..37d7393 Binary files /dev/null and b/app/src/main/res/drawable/imag_no_photo.png differ diff --git a/app/src/main/res/drawable/photo_size_bg.xml b/app/src/main/res/drawable/photo_size_bg.xml index aecf70b..3067255 100644 --- a/app/src/main/res/drawable/photo_size_bg.xml +++ b/app/src/main/res/drawable/photo_size_bg.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_arrow_up_down.xml b/app/src/main/res/drawable/selector_arrow_up_down.xml new file mode 100644 index 0000000..f24ce1f --- /dev/null +++ b/app/src/main/res/drawable/selector_arrow_up_down.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_filter_image_view.xml b/app/src/main/res/drawable/selector_filter_image_view.xml new file mode 100644 index 0000000..dea1331 --- /dev/null +++ b/app/src/main/res/drawable/selector_filter_image_view.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_icon_checkmark_28dp.xml b/app/src/main/res/drawable/selector_icon_checkmark_28dp.xml new file mode 100644 index 0000000..24e71cc --- /dev/null +++ b/app/src/main/res/drawable/selector_icon_checkmark_28dp.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_icon_select.xml b/app/src/main/res/drawable/selector_icon_select.xml deleted file mode 100644 index c3dd063..0000000 --- a/app/src/main/res/drawable/selector_icon_select.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_icon_select_16.xml b/app/src/main/res/drawable/selector_icon_select_16.xml new file mode 100644 index 0000000..a17cba6 --- /dev/null +++ b/app/src/main/res/drawable/selector_icon_select_16.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_recover_button_enable.xml b/app/src/main/res/drawable/selector_recover_button_enable.xml new file mode 100644 index 0000000..0c463cd --- /dev/null +++ b/app/src/main/res/drawable/selector_recover_button_enable.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_photo_sorting.xml b/app/src/main/res/layout/activity_photo_sorting.xml index 33042ef..967bec9 100644 --- a/app/src/main/res/layout/activity_photo_sorting.xml +++ b/app/src/main/res/layout/activity_photo_sorting.xml @@ -1,169 +1,353 @@ - - - - -