Skip to content

Commit

Permalink
First draft of the path picker
Browse files Browse the repository at this point in the history
  • Loading branch information
d4rken committed Feb 6, 2025
1 parent cd65b26 commit adf1f0a
Show file tree
Hide file tree
Showing 20 changed files with 884 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package eu.darken.sdmse.common.areas

import android.content.Context
import eu.darken.sdmse.R
import eu.darken.sdmse.common.ca.CaString
import eu.darken.sdmse.common.ca.toCaString
import kotlinx.coroutines.flow.first


Expand All @@ -21,7 +23,13 @@ fun DataArea.hasFlags(vararg lookup: DataArea.Flag): Boolean = flags.containsAll

suspend fun DataAreaManager.currentAreas(): Collection<DataArea> = state.first().areas

fun DataArea.Type.getShortLabel(context: Context): String = when (this) {
// TODO nicer names
else -> this.raw
}
val DataArea.Type.label: CaString
get() = when (this) {
DataArea.Type.SDCARD -> R.string.area_type_sdcard_label.toCaString()
DataArea.Type.PUBLIC_MEDIA -> R.string.area_type_public_media_label.toCaString()
DataArea.Type.PUBLIC_DATA -> R.string.area_type_public_data_label.toCaString()
DataArea.Type.PUBLIC_OBB -> R.string.area_type_public_obb_label.toCaString()
DataArea.Type.PRIVATE_DATA -> R.string.area_type_private_data_label.toCaString()
DataArea.Type.PORTABLE -> R.string.area_type_portable_label.toCaString()
else -> this.raw.toCaString()
}
42 changes: 42 additions & 0 deletions app/src/main/java/eu/darken/sdmse/common/picker/PickerAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package eu.darken.sdmse.common.picker

import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.viewbinding.ViewBinding
import eu.darken.sdmse.common.lists.BindableVH
import eu.darken.sdmse.common.lists.differ.AsyncDiffer
import eu.darken.sdmse.common.lists.differ.DifferItem
import eu.darken.sdmse.common.lists.differ.HasAsyncDiffer
import eu.darken.sdmse.common.lists.differ.setupDiffer
import eu.darken.sdmse.common.lists.modular.ModularAdapter
import eu.darken.sdmse.common.lists.modular.mods.DataBinderMod
import eu.darken.sdmse.common.lists.modular.mods.TypedVHCreatorMod
import javax.inject.Inject


class PickerAdapter @Inject constructor() :
ModularAdapter<PickerAdapter.BaseVH<PickerAdapter.Item, ViewBinding>>(),
HasAsyncDiffer<PickerAdapter.Item> {

override val asyncDiffer: AsyncDiffer<*, Item> = setupDiffer()

override fun getItemCount(): Int = data.size

init {
addMod(DataBinderMod(data))
addMod(TypedVHCreatorMod({ data[it] is PickerItemVH.Item }) { PickerItemVH(it) })
}

abstract class BaseVH<D : Item, B : ViewBinding>(
@LayoutRes layoutId: Int,
parent: ViewGroup
) : VH(layoutId, parent), BindableVH<D, B>

interface Item : DifferItem {

override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
get() = { old, new ->
if (new::class.isInstance(old)) new else null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package eu.darken.sdmse.common.picker

sealed class PickerEvents {
data object ExitConfirmation : PickerEvents()
}
126 changes: 126 additions & 0 deletions app/src/main/java/eu/darken/sdmse/common/picker/PickerFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package eu.darken.sdmse.common.picker

import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import androidx.activity.OnBackPressedCallback
import androidx.core.view.isInvisible
import androidx.core.view.iterator
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.sdmse.R
import eu.darken.sdmse.common.debug.logging.log
import eu.darken.sdmse.common.debug.logging.logTag
import eu.darken.sdmse.common.dpToPx
import eu.darken.sdmse.common.getQuantityString2
import eu.darken.sdmse.common.lists.differ.update
import eu.darken.sdmse.common.lists.setupDefaults
import eu.darken.sdmse.common.navigation.getSpanCount
import eu.darken.sdmse.common.uix.Fragment3
import eu.darken.sdmse.common.viewbinding.viewBinding
import eu.darken.sdmse.databinding.CommonPickerFragmentBinding
import kotlin.math.roundToInt

@AndroidEntryPoint
class PickerFragment : Fragment3(R.layout.common_picker_fragment) {

override val vm: PickerViewModel by viewModels()
override val ui: CommonPickerFragmentBinding by viewBinding()

private lateinit var selectedContainer: BottomSheetBehavior<LinearLayout>

private val onBackPressedcallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
vm.goBack()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedcallback)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
selectedContainer = BottomSheetBehavior.from(ui.selectedContainer).apply {
isHideable = false
state = BottomSheetBehavior.STATE_COLLAPSED
peekHeight = requireContext().dpToPx(64f)
ui.root.post {
maxHeight = ((ui.root.height - ui.toolbar.height) * 0.5f).roundToInt()
}
}

ui.toolbar.apply {
setNavigationOnClickListener { vm.cancel(confirmed = false) }
setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_action_save -> {
vm.save()
true
}

R.id.menu_action_home -> {
vm.home()
true
}

R.id.menu_action_select_all -> {
vm.selectAll()
true
}

else -> false
}
}
}

val pickerAdapter = PickerAdapter()
ui.list.setupDefaults(
pickerAdapter,
horizontalDividers = true,
layouter = GridLayoutManager(context, getSpanCount(), GridLayoutManager.VERTICAL, false),
)
val selectedAdapter = PickerSelectedAdapter()
ui.selectedList.setupDefaults(
selectedAdapter,
horizontalDividers = true,
)
vm.state.observe2(ui) { state ->
log(TAG) { "updating with new state: $state" }
toolbar.subtitle = state.current?.lookup?.path ?: ""
toolbar.menu.iterator().forEach { it.isVisible = state.progress == null }

loadingOverlay.setProgress(state.progress)
if (state.progress == null) pickerAdapter.update(state.items)
list.isInvisible = state.progress != null

selectedList.isInvisible = state.progress != null
selectedSecondary.text = requireContext().getQuantityString2(
R.plurals.picker_selected_paths_subtitle,
state.selected.size,
)
if (state.progress == null) selectedAdapter.update(state.selected)
}

vm.events.observe2 { event ->
when (event) {
PickerEvents.ExitConfirmation -> MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.picker_unsaved_confirmation_message)
setPositiveButton(eu.darken.sdmse.common.R.string.general_discard_action) { _, _ ->
vm.cancel(confirmed = true)
}
setNegativeButton(eu.darken.sdmse.common.R.string.general_cancel_action) { _, _ -> }
}.show()
}
}

super.onViewCreated(view, savedInstanceState)
}

companion object {
private val TAG = logTag("Common", "Picker", "Fragment")
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/eu/darken/sdmse/common/picker/PickerItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package eu.darken.sdmse.common.picker

import eu.darken.sdmse.common.areas.DataArea
import eu.darken.sdmse.common.files.APathLookup

data class PickerItem(
val lookup: APathLookup<*>,
val parent: PickerItem?,
val dataArea: DataArea,
val selected: Boolean,
val selectable: Boolean,
)
71 changes: 71 additions & 0 deletions app/src/main/java/eu/darken/sdmse/common/picker/PickerItemVH.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package eu.darken.sdmse.common.picker

import android.view.ViewGroup
import androidx.core.view.isVisible
import coil.dispose
import eu.darken.sdmse.R
import eu.darken.sdmse.common.areas.label
import eu.darken.sdmse.common.coil.loadFilePreview
import eu.darken.sdmse.common.files.APath
import eu.darken.sdmse.common.files.FileType
import eu.darken.sdmse.common.lists.binding
import eu.darken.sdmse.databinding.CommonPickerItemBinding


class PickerItemVH(parent: ViewGroup) :
PickerAdapter.BaseVH<PickerItemVH.Item, CommonPickerItemBinding>(
R.layout.common_picker_item,
parent
) {

override val viewBinding = lazy { CommonPickerItemBinding.bind(itemView) }

override val onBindData: CommonPickerItemBinding.(
item: Item,
payloads: List<Any>
) -> Unit = binding { wrapper ->
val item = wrapper.item

contentIcon.apply {
if (item.parent == null) {
setImageResource(R.drawable.ic_folder_home_24)
} else {
dispose()
loadFilePreview(item.lookup)
}
}

primary.text = when {
item.parent == null -> item.lookup.path
else -> item.lookup.name
}
secondary.text = item.dataArea.type.label.get(context)
tertiary.text = when (item.lookup.fileType) {
FileType.DIRECTORY -> getString(eu.darken.sdmse.common.R.string.file_type_directory)
FileType.FILE -> getString(eu.darken.sdmse.common.R.string.file_type_file)
FileType.SYMBOLIC_LINK -> getString(eu.darken.sdmse.common.R.string.file_type_symbolic_link)
FileType.UNKNOWN -> getString(eu.darken.sdmse.common.R.string.file_type_unknown)
}

indicator.apply {
isChecked = item.selected
isVisible = item.selectable
setOnClickListener { wrapper.onSelect?.invoke() }
}

root.setOnClickListener { wrapper.onItemClicked() }
}

data class Item(
val item: PickerItem,
val onItemClicked: () -> Unit,
val onSelect: (() -> Unit)?,
) : PickerAdapter.Item {

val path: APath
get() = item.lookup.lookedUp

override val stableId: Long = path.hashCode().toLong()
}

}
17 changes: 17 additions & 0 deletions app/src/main/java/eu/darken/sdmse/common/picker/PickerRequest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package eu.darken.sdmse.common.picker

import android.os.Parcelable
import eu.darken.sdmse.common.areas.DataArea
import eu.darken.sdmse.common.files.APath
import kotlinx.parcelize.Parcelize

@Parcelize
data class PickerRequest(
val mode: PickMode,
val allowedAreas: Set<DataArea.Type> = emptySet(),
val selectedPaths: List<APath> = emptyList(),
) : Parcelable {
enum class PickMode {
MIXED, FILE, FILES, DIR, DIRS
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package eu.darken.sdmse.common.picker

import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.viewbinding.ViewBinding
import eu.darken.sdmse.common.lists.BindableVH
import eu.darken.sdmse.common.lists.differ.AsyncDiffer
import eu.darken.sdmse.common.lists.differ.DifferItem
import eu.darken.sdmse.common.lists.differ.HasAsyncDiffer
import eu.darken.sdmse.common.lists.differ.setupDiffer
import eu.darken.sdmse.common.lists.modular.ModularAdapter
import eu.darken.sdmse.common.lists.modular.mods.DataBinderMod
import eu.darken.sdmse.common.lists.modular.mods.TypedVHCreatorMod
import javax.inject.Inject


class PickerSelectedAdapter @Inject constructor() :
ModularAdapter<PickerSelectedAdapter.BaseVH<PickerSelectedAdapter.Item, ViewBinding>>(),
HasAsyncDiffer<PickerSelectedAdapter.Item> {

override val asyncDiffer: AsyncDiffer<*, Item> = setupDiffer()

override fun getItemCount(): Int = data.size

init {
addMod(DataBinderMod(data))
addMod(TypedVHCreatorMod({ data[it] is PickerSelectedVH.Item }) { PickerSelectedVH(it) })
}

abstract class BaseVH<D : Item, B : ViewBinding>(@LayoutRes layoutId: Int, parent: ViewGroup) :
VH(layoutId, parent), BindableVH<D, B>

interface Item : DifferItem {
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
get() = { old, new -> if (new::class.isInstance(old)) new else null }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package eu.darken.sdmse.common.picker

import android.view.ViewGroup
import eu.darken.sdmse.R
import eu.darken.sdmse.common.files.APathLookup
import eu.darken.sdmse.common.files.iconRes
import eu.darken.sdmse.common.lists.binding
import eu.darken.sdmse.databinding.CommonPickerSelectedItemBinding


class PickerSelectedVH(parent: ViewGroup) :
PickerSelectedAdapter.BaseVH<PickerSelectedVH.Item, CommonPickerSelectedItemBinding>(
R.layout.common_picker_selected_item,
parent
) {

override val viewBinding = lazy { CommonPickerSelectedItemBinding.bind(itemView) }

override val onBindData: CommonPickerSelectedItemBinding.(
item: Item,
payloads: List<Any>
) -> Unit = binding { item ->
icon.setImageResource(item.lookup.fileType.iconRes)
primary.text = item.lookup.userReadablePath.get(context)
removeAction.setOnClickListener { item.onRemove() }
}

data class Item(
val lookup: APathLookup<*>,
val onRemove: () -> Unit,
) : PickerSelectedAdapter.Item {
override val stableId: Long = lookup.path.hashCode().toLong()
}

}
Loading

0 comments on commit adf1f0a

Please sign in to comment.