Skip to content

Commit 1061433

Browse files
authored
ADFA-3235 | Fix checkbox selection bug in project list (#1075)
* fix: replace OnCheckedChangeListener with OnClickListener Prevents unintended selection state changes during RecyclerView scrolling. * refactor: migrate DeleteProjectListAdapter to Checkable<ProjectFile> selection model improve UX by toggling selection from item click and removing internal selection state
1 parent 5e96d3e commit 1061433

File tree

2 files changed

+35
-19
lines changed

2 files changed

+35
-19
lines changed

app/src/main/java/com/itsaky/androidide/adapters/DeleteProjectListAdapter.kt

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,16 @@ import android.view.LayoutInflater
44
import android.view.View
55
import android.view.ViewGroup
66
import androidx.recyclerview.widget.RecyclerView
7-
import com.itsaky.androidide.R
87
import com.itsaky.androidide.databinding.DeleteProjectsItemBinding
9-
import com.itsaky.androidide.utils.formatDate
8+
import com.itsaky.androidide.models.Checkable
109
import org.appdevforall.codeonthego.layouteditor.ProjectFile
1110

1211
class DeleteProjectListAdapter(
13-
private var projects: List<ProjectFile>,
12+
private var projects: List<Checkable<ProjectFile>>,
1413
private val onSelectionChange: (Boolean) -> Unit,
1514
private val onCheckboxLongPress: () -> Boolean
1615
) : RecyclerView.Adapter<DeleteProjectListAdapter.ProjectViewHolder>() {
1716

18-
private val selectedProjects = mutableSetOf<ProjectFile>()
19-
2017
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProjectViewHolder {
2118
val binding =
2219
DeleteProjectsItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@@ -29,33 +26,40 @@ class DeleteProjectListAdapter(
2926

3027
override fun getItemCount(): Int = projects.size
3128

32-
fun getSelectedProjects(): List<ProjectFile> = selectedProjects.toList()
29+
fun getSelectedProjects(): List<ProjectFile> = projects.filter { it.isChecked }.map { it.data }
3330

34-
fun updateProjects(newProjects: List<ProjectFile>) {
31+
fun updateProjects(newProjects: List<Checkable<ProjectFile>>) {
3532
projects = newProjects
3633
notifyDataSetChanged()
3734
}
3835

39-
fun renderDate(binding: DeleteProjectsItemBinding, project: ProjectFile) {
36+
private fun hasSelection(): Boolean = projects.any { it.isChecked }
37+
38+
private fun renderDate(binding: DeleteProjectsItemBinding, project: ProjectFile) {
4039
binding.projectDate.text = project.renderDateText(binding.root.context)
4140
}
4241

4342
inner class ProjectViewHolder(private val binding: DeleteProjectsItemBinding) :
4443
RecyclerView.ViewHolder(binding.root) {
4544

46-
fun bind(project: ProjectFile) {
45+
fun bind(item: Checkable<ProjectFile>) {
46+
val project = item.data
47+
4748
binding.projectName.text = project.name
4849
renderDate(binding, project)
4950
binding.icon.text = project.name.take(2).uppercase()
5051

5152
binding.checkbox.visibility = View.VISIBLE
52-
binding.checkbox.isChecked = selectedProjects.contains(project)
53+
binding.checkbox.isChecked = item.isChecked
54+
55+
binding.root.setOnClickListener {
56+
item.isChecked = !item.isChecked
57+
binding.checkbox.isChecked = item.isChecked
58+
onSelectionChange(hasSelection())
59+
}
5360

54-
binding.checkbox.setOnCheckedChangeListener { _, isChecked ->
55-
if (isChecked) selectedProjects.add(project) else selectedProjects.remove(
56-
project
57-
)
58-
onSelectionChange(selectedProjects.isNotEmpty())
61+
binding.checkbox.setOnClickListener {
62+
binding.root.performClick()
5963
}
6064

6165
binding.checkbox.setOnLongClickListener {

app/src/main/java/com/itsaky/androidide/fragments/DeleteProjectFragment.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.itsaky.androidide.idetooltips.TooltipTag.DELETE_PROJECT_DIALOG
2020
import com.itsaky.androidide.idetooltips.TooltipTag.DELETE_PROJECT_SELECT
2121
import com.itsaky.androidide.idetooltips.TooltipTag.EXIT_TO_MAIN
2222
import com.itsaky.androidide.idetooltips.TooltipTag.PROJECT_NEW
23+
import com.itsaky.androidide.models.Checkable
2324
import com.itsaky.androidide.ui.CustomDividerItemDecoration
2425
import com.itsaky.androidide.utils.flashError
2526
import com.itsaky.androidide.utils.flashSuccess
@@ -61,23 +62,34 @@ class DeleteProjectFragment : BaseFragment() {
6162

6263
private fun observeProjects() {
6364
recentProjectsViewModel.projects.observe(viewLifecycleOwner) { projects ->
65+
val selectedPaths = adapter
66+
?.getSelectedProjects()
67+
?.map { it.path }
68+
?.toSet()
69+
.orEmpty()
70+
71+
val checkableProjects = projects.map { project ->
72+
Checkable(project.path in selectedPaths, project)
73+
}
74+
6475
if (adapter == null) {
6576
// Create adapter and pass a callback to update delete button state on selection change
6677
adapter = DeleteProjectListAdapter(
67-
projects,
78+
checkableProjects,
6879
{ enableBtn ->
6980
updateDeleteButtonState(enableBtn)
7081
},
7182
onCheckboxLongPress = {
72-
showToolTip(DELETE_PROJECT_SELECT)
83+
showToolTip(DELETE_PROJECT_SELECT, binding.listProjects)
7384
true
7485
}
7586

7687
)
7788
binding.listProjects.adapter = adapter
7889
} else {
79-
adapter?.updateProjects(projects)
90+
adapter?.updateProjects(checkableProjects)
8091
}
92+
8193
binding.recentProjectsTxt.isVisible = projects.isNotEmpty()
8294
binding.noProjectsView.isVisible = projects.isEmpty()
8395

@@ -87,7 +99,7 @@ class DeleteProjectFragment : BaseFragment() {
8799
binding.delete.isEnabled = true
88100
} else {
89101
binding.delete.text = getString(R.string.delete_project)
90-
updateDeleteButtonState(adapter?.getSelectedProjects()?.isNotEmpty() ?: false)
102+
updateDeleteButtonState(adapter?.getSelectedProjects()?.isNotEmpty() == true)
91103
}
92104
}
93105
}

0 commit comments

Comments
 (0)