From a9f7fae94d4e962f743e3a9ba83adb03b4d96480 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Wed, 15 Apr 2026 19:12:16 +0100 Subject: [PATCH 1/7] feat(ADFA-3723): Show save prompt for unsaved changes before performing Git operations --- .../fragments/git/GitBottomSheetFragment.kt | 62 +++++++++++++------ resources/src/main/res/values/strings.xml | 5 ++ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt index 4341add028..4e73f6888e 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt @@ -22,6 +22,7 @@ import com.itsaky.androidide.databinding.FragmentGitBottomSheetBinding import com.itsaky.androidide.fragments.git.adapter.GitFileChangeAdapter import com.itsaky.androidide.git.core.GitCredentialsManager import com.itsaky.androidide.git.core.models.ChangeType +import com.itsaky.androidide.interfaces.IEditorHandler import com.itsaky.androidide.preferences.internal.GitPreferences import com.itsaky.androidide.utils.flashSuccess import com.itsaky.androidide.viewmodel.BottomSheetViewModel @@ -183,19 +184,21 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) { } binding.commitButton.setOnClickListener { - val summary = binding.commitSummary.text?.toString()?.trim() ?: "" - val description = binding.commitDescription.text?.toString()?.trim() - - if (summary.isNotEmpty() && fileChangeAdapter.selectedFiles.isNotEmpty() && hasAuthorInfo()) { - viewModel.commitChanges( - summary = summary, - description = description, - selectedPaths = fileChangeAdapter.selectedFiles.toList() - ) { - // Clear the inputs on successful commit - binding.commitSummary.text?.clear() - binding.commitDescription.text?.clear() - fileChangeAdapter.selectedFiles.clear() + checkUnsavedChangesAndProceed { + val summary = binding.commitSummary.text?.toString()?.trim() ?: "" + val description = binding.commitDescription.text?.toString()?.trim() + + if (summary.isNotEmpty() && fileChangeAdapter.selectedFiles.isNotEmpty() && hasAuthorInfo()) { + viewModel.commitChanges( + summary = summary, + description = description, + selectedPaths = fileChangeAdapter.selectedFiles.toList() + ) { + // Clear the inputs on successful commit + binding.commitSummary.text?.clear() + binding.commitDescription.text?.clear() + fileChangeAdapter.selectedFiles.clear() + } } } } @@ -303,12 +306,14 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) { } binding.btnPull.setOnClickListener { - val username = credentialsManager.getUsername() - val token = credentialsManager.getToken() - if (!username.isNullOrBlank() && !token.isNullOrBlank()) { - viewModel.pull(username, token) - } else { - showCredentialsDialog() + checkUnsavedChangesAndProceed { + val username = credentialsManager.getUsername() + val token = credentialsManager.getToken() + if (!username.isNullOrBlank() && !token.isNullOrBlank()) { + viewModel.pull(username, token) + } else { + showCredentialsDialog() + } } } } @@ -344,6 +349,25 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) { } } + private fun checkUnsavedChangesAndProceed(action: () -> Unit) { + val handler = requireActivity() as? IEditorHandler + if (handler?.areFilesModified() == true) { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.title_files_unsaved) + .setMessage(R.string.msg_save_before_git_action) + .setPositiveButton(R.string.save_before_git_action) { _, _ -> + handler.saveAllAsync { action() } + } + .setNegativeButton(R.string.no_save_before_git_action) { _, _ -> + action() + } + .setNeutralButton(android.R.string.cancel, null) + .show() + } else { + action() + } + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index acf731e49b..a868e5658c 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -1282,4 +1282,9 @@ Merge Conflicts Abort Merge Are you sure you want to abort the current merge? All conflict resolutions will be discarded. + You have unsaved changes. Would you like to save them before proceeding? + Proceed without saving + Save before proceeding + + From 5c7ca2e4808f9c45bb6a6f70410058a3537f5787 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Wed, 15 Apr 2026 20:01:56 +0100 Subject: [PATCH 2/7] feat(ADFA-3723): Prevent conflicted files from getting staged --- .../fragments/git/GitBottomSheetFragment.kt | 24 ++++--------------- .../git/adapter/GitFileChangeAdapter.kt | 17 +++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt index 4e73f6888e..d6a8aeda7d 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt @@ -51,26 +51,10 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) { fileChangeAdapter = GitFileChangeAdapter( onFileClicked = { change -> - when (change.type) { - ChangeType.CONFLICTED -> { - // Open conflicted file in editor - val activity = requireActivity() - if (activity is EditorHandlerActivity) { - viewLifecycleOwner.lifecycleScope.launch { - val repo = viewModel.currentRepository - repo?.let { - activity.checkForExternalFileChanges(force = true) - activity.openFile(File(repo.rootDir, change.path)) - bottomSheetViewModel.setSheetState(BottomSheetBehavior.STATE_COLLAPSED) - } - } - } - } - else -> { - // Show diff in a dialog when changed file is clicked - val dialog = GitDiffViewerDialog.newInstance(change.path) - dialog.show(childFragmentManager, "GitDiffViewerDialog") - } + if (change.type != ChangeType.CONFLICTED) { + // Show diff in a dialog when changed file is clicked + val dialog = GitDiffViewerDialog.newInstance(change.path) + dialog.show(childFragmentManager, "GitDiffViewerDialog") } }, onSelectionChanged = { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt index 5b6649e50e..c8dacacf05 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt @@ -44,6 +44,12 @@ class GitFileChangeAdapter( val pos = bindingAdapterPosition if (pos != RecyclerView.NO_POSITION) { val change = getItem(pos) + // Conflicted files should not be selectable + if (change.type == ChangeType.CONFLICTED) { + binding.checkbox.isChecked = false + return@setOnCheckedChangeListener + } + if (isChecked) { selectedFiles.add(change.path) } else { @@ -57,7 +63,18 @@ class GitFileChangeAdapter( fun bind(change: FileChange) { binding.filePath.text = change.path + val isConflicted = change.type == ChangeType.CONFLICTED + binding.checkbox.isEnabled = !isConflicted + + // Ensure conflicted files are never in selectedFiles + if (isConflicted) { + selectedFiles.remove(change.path) + } + binding.checkbox.isChecked = selectedFiles.contains(change.path) + + // Dim the entry if it's conflicted to show it's disabled for staging + binding.root.alpha = if (isConflicted) 0.5f else 1.0f val (imageRes, descRes) = when (change.type) { ChangeType.ADDED -> R.drawable.ic_file_added to R.string.desc_file_added From 5a65cac630f2f813f68a183a1abfb9b14ef93d68 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Thu, 16 Apr 2026 12:41:22 +0100 Subject: [PATCH 3/7] feat(ADFA-3723): Mark conflicted files as resolved --- .../fragments/git/GitBottomSheetFragment.kt | 25 ++++++++++++++++--- .../git/adapter/GitFileChangeAdapter.kt | 23 ++++++++++++++--- .../viewmodel/GitBottomSheetViewModel.kt | 14 ++++++++++- .../main/res/layout/item_git_file_change.xml | 14 +++++++++++ resources/src/main/res/values/strings.xml | 1 + 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt index d6a8aeda7d..f77c4411df 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt @@ -51,14 +51,31 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) { fileChangeAdapter = GitFileChangeAdapter( onFileClicked = { change -> - if (change.type != ChangeType.CONFLICTED) { - // Show diff in a dialog when changed file is clicked - val dialog = GitDiffViewerDialog.newInstance(change.path) - dialog.show(childFragmentManager, "GitDiffViewerDialog") + when (change.type) { + ChangeType.CONFLICTED -> { + val activity = requireActivity() + if (activity is EditorHandlerActivity) { + viewLifecycleOwner.lifecycleScope.launch { + val repo = viewModel.currentRepository + repo?.let { + activity.checkForExternalFileChanges(force = true) + activity.openFile(File(repo.rootDir, change.path)) + bottomSheetViewModel.setSheetState(BottomSheetBehavior.STATE_COLLAPSED) + } + } + } + } + else -> { + val dialog = GitDiffViewerDialog.newInstance(change.path) + dialog.show(childFragmentManager, "GitDiffViewerDialog") + } } }, onSelectionChanged = { validateCommitButton() + }, + onResolveConflict = { change -> + viewModel.resolveConflict(change.path) } ) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt index c8dacacf05..0a559919c2 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt @@ -1,6 +1,7 @@ package com.itsaky.androidide.fragments.git.adapter import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -12,7 +13,8 @@ import com.itsaky.androidide.git.core.models.FileChange class GitFileChangeAdapter( private val onFileClicked: (FileChange) -> Unit, - private val onSelectionChanged: (Int) -> Unit = {} + private val onSelectionChanged: (Int) -> Unit = {}, + private val onResolveConflict: (FileChange) -> Unit = {} ) : ListAdapter(DiffCallback()) { // Keep track of which files are selected to be committed @@ -58,13 +60,24 @@ class GitFileChangeAdapter( onSelectionChanged(selectedFiles.size) } } + + binding.btnMarkResolved.setOnClickListener { + val pos = bindingAdapterPosition + if (pos != RecyclerView.NO_POSITION) { + onResolveConflict(getItem(pos)) + } + } } fun bind(change: FileChange) { binding.filePath.text = change.path val isConflicted = change.type == ChangeType.CONFLICTED - binding.checkbox.isEnabled = !isConflicted + binding.checkbox.apply { + isEnabled = !isConflicted + visibility = if (isConflicted) View.INVISIBLE else View.VISIBLE + } + binding.btnMarkResolved.visibility = if (isConflicted) View.VISIBLE else View.GONE // Ensure conflicted files are never in selectedFiles if (isConflicted) { @@ -73,8 +86,10 @@ class GitFileChangeAdapter( binding.checkbox.isChecked = selectedFiles.contains(change.path) - // Dim the entry if it's conflicted to show it's disabled for staging - binding.root.alpha = if (isConflicted) 0.5f else 1.0f + val contentAlpha = if (isConflicted) 0.5f else 1.0f + binding.filePath.alpha = contentAlpha + binding.statusIcon.alpha = contentAlpha + binding.root.alpha = 1.0f val (imageRes, descRes) = when (change.type) { ChangeType.ADDED -> R.drawable.ic_file_added to R.string.desc_file_added diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/GitBottomSheetViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/GitBottomSheetViewModel.kt index dac3257c0f..4e04772785 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/GitBottomSheetViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/GitBottomSheetViewModel.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.eclipse.jgit.api.MergeResult.MergeStatus import org.eclipse.jgit.api.PullResult -import org.eclipse.jgit.errors.NoRemoteRepositoryException import org.eclipse.jgit.api.errors.CheckoutConflictException import org.eclipse.jgit.transport.RemoteRefUpdate import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider @@ -369,4 +368,17 @@ class GitBottomSheetViewModel(private val credentialsManager: GitCredentialsMana } } + fun resolveConflict(path: String) { + viewModelScope.launch { + try { + val repository = currentRepository ?: return@launch + val projectDir = File(IProjectManager.getInstance().projectDirPath) + repository.stageFiles(listOf(File(projectDir, path))) + refreshStatus() + } catch (e: Exception) { + log.error("Failed to resolve conflict for $path", e) + } + } + } + } diff --git a/app/src/main/res/layout/item_git_file_change.xml b/app/src/main/res/layout/item_git_file_change.xml index a9b49083f2..8f917b5b53 100644 --- a/app/src/main/res/layout/item_git_file_change.xml +++ b/app/src/main/res/layout/item_git_file_change.xml @@ -1,5 +1,6 @@ + + You have unsaved changes. Would you like to save them before proceeding? Proceed without saving Save before proceeding + Mark Resolved From 27b682e0e78f8b69856728c38459e300d68c3c43 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Thu, 16 Apr 2026 13:28:48 +0100 Subject: [PATCH 4/7] fix(ADFA-3723): Resolve adapter issues --- .../git/adapter/GitFileChangeAdapter.kt | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt index 0a559919c2..ede61a759e 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt @@ -42,25 +42,6 @@ class GitFileChangeAdapter( } } - binding.checkbox.setOnCheckedChangeListener { _, isChecked -> - val pos = bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - val change = getItem(pos) - // Conflicted files should not be selectable - if (change.type == ChangeType.CONFLICTED) { - binding.checkbox.isChecked = false - return@setOnCheckedChangeListener - } - - if (isChecked) { - selectedFiles.add(change.path) - } else { - selectedFiles.remove(change.path) - } - onSelectionChanged(selectedFiles.size) - } - } - binding.btnMarkResolved.setOnClickListener { val pos = bindingAdapterPosition if (pos != RecyclerView.NO_POSITION) { @@ -73,6 +54,10 @@ class GitFileChangeAdapter( binding.filePath.text = change.path val isConflicted = change.type == ChangeType.CONFLICTED + + // Clear listener before setting state to avoid recursive/accidental calls during binding + binding.checkbox.setOnCheckedChangeListener(null) + binding.checkbox.apply { isEnabled = !isConflicted visibility = if (isConflicted) View.INVISIBLE else View.VISIBLE @@ -86,6 +71,26 @@ class GitFileChangeAdapter( binding.checkbox.isChecked = selectedFiles.contains(change.path) + // Re-set the listener after the state is initialized + binding.checkbox.setOnCheckedChangeListener { _, isChecked -> + val pos = bindingAdapterPosition + if (pos != RecyclerView.NO_POSITION) { + val changeAtPos = getItem(pos) + // Conflicted files should not be selectable + if (changeAtPos.type == ChangeType.CONFLICTED) { + binding.checkbox.isChecked = false + return@setOnCheckedChangeListener + } + + if (isChecked) { + selectedFiles.add(changeAtPos.path) + } else { + selectedFiles.remove(changeAtPos.path) + } + onSelectionChanged(selectedFiles.size) + } + } + val contentAlpha = if (isConflicted) 0.5f else 1.0f binding.filePath.alpha = contentAlpha binding.statusIcon.alpha = contentAlpha From 0297a127ac29197091b45797878c7d78a7533a62 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Thu, 16 Apr 2026 13:55:34 +0100 Subject: [PATCH 5/7] fix(ADFA-3723): Fix duplicate file listing --- .../androidide/git/core/JGitRepository.kt | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/git-core/src/main/java/com/itsaky/androidide/git/core/JGitRepository.kt b/git-core/src/main/java/com/itsaky/androidide/git/core/JGitRepository.kt index f91c264cfa..acfafaf30b 100644 --- a/git-core/src/main/java/com/itsaky/androidide/git/core/JGitRepository.kt +++ b/git-core/src/main/java/com/itsaky/androidide/git/core/JGitRepository.kt @@ -67,23 +67,36 @@ class JGitRepository(override val rootDir: File) : GitRepository { val unstaged = mutableListOf() val untracked = mutableListOf() val conflicted = mutableListOf() + + // Track unique paths to avoid duplicates across categories + // Priority: Conflicted > Staged > Unstaged > Untracked + val processedPaths = mutableSetOf() - jgitStatus.added.forEach { staged.add(FileChange(it, ChangeType.ADDED)) } - jgitStatus.changed.forEach { staged.add(FileChange(it, ChangeType.MODIFIED)) } - jgitStatus.removed.forEach { staged.add(FileChange(it, ChangeType.DELETED)) } + // 1. Conflicted (Highest Priority) + jgitStatus.conflicting.forEach { + if (processedPaths.add(it)) { + conflicted.add(FileChange(it, ChangeType.CONFLICTED)) + } + } + + // 2. Staged files (Added, Changed, Removed) + jgitStatus.added.forEach { if (processedPaths.add(it)) staged.add(FileChange(it, ChangeType.ADDED)) } + jgitStatus.changed.forEach { if (processedPaths.add(it)) staged.add(FileChange(it, ChangeType.MODIFIED)) } + jgitStatus.removed.forEach { if (processedPaths.add(it)) staged.add(FileChange(it, ChangeType.DELETED)) } - jgitStatus.modified.forEach { unstaged.add(FileChange(it, ChangeType.MODIFIED)) } - jgitStatus.missing.forEach { unstaged.add(FileChange(it, ChangeType.DELETED)) } + // 3. Unstaged files (Modified, Missing) + jgitStatus.modified.forEach { if (processedPaths.add(it)) unstaged.add(FileChange(it, ChangeType.MODIFIED)) } + jgitStatus.missing.forEach { if (processedPaths.add(it)) unstaged.add(FileChange(it, ChangeType.DELETED)) } - jgitStatus.untracked.forEach { untracked.add(FileChange(it, ChangeType.UNTRACKED)) } + // 4. Untracked files + jgitStatus.untracked.forEach { if (processedPaths.add(it)) untracked.add(FileChange(it, ChangeType.UNTRACKED)) } - jgitStatus.conflicting.forEach { conflicted.add(FileChange(it, ChangeType.CONFLICTED)) } val isMerging = repository.repositoryState == RepositoryState.MERGING GitStatus( isClean = jgitStatus.isClean, - hasConflicts = jgitStatus.conflicting.isNotEmpty(), + hasConflicts = conflicted.isNotEmpty(), isMerging = isMerging, staged = staged, unstaged = unstaged, From 71002836c191226771bc8262e0dd82ed1766d237 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Fri, 17 Apr 2026 12:47:47 +0100 Subject: [PATCH 6/7] fix(ADFA-3723): Ensure adapter updates --- .../androidide/fragments/git/adapter/GitFileChangeAdapter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt index ede61a759e..0cc6f7a251 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt @@ -65,8 +65,8 @@ class GitFileChangeAdapter( binding.btnMarkResolved.visibility = if (isConflicted) View.VISIBLE else View.GONE // Ensure conflicted files are never in selectedFiles - if (isConflicted) { - selectedFiles.remove(change.path) + if (isConflicted && selectedFiles.remove(change.path)) { + onSelectionChanged(selectedFiles.size) } binding.checkbox.isChecked = selectedFiles.contains(change.path) From 6ccc614e0226e31110d13859589df0629ac91b68 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Mon, 20 Apr 2026 17:48:36 +0100 Subject: [PATCH 7/7] refactor(ADFA-3273): Flatten nested ifs --- .../git/adapter/GitFileChangeAdapter.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt index 0cc6f7a251..6642e807fa 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/git/adapter/GitFileChangeAdapter.kt @@ -74,21 +74,21 @@ class GitFileChangeAdapter( // Re-set the listener after the state is initialized binding.checkbox.setOnCheckedChangeListener { _, isChecked -> val pos = bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - val changeAtPos = getItem(pos) - // Conflicted files should not be selectable - if (changeAtPos.type == ChangeType.CONFLICTED) { - binding.checkbox.isChecked = false - return@setOnCheckedChangeListener - } - - if (isChecked) { - selectedFiles.add(changeAtPos.path) - } else { - selectedFiles.remove(changeAtPos.path) - } - onSelectionChanged(selectedFiles.size) + if (pos == RecyclerView.NO_POSITION) return@setOnCheckedChangeListener + + val changeAtPos = getItem(pos) + // Conflicted files should not be selectable + if (changeAtPos.type == ChangeType.CONFLICTED) { + binding.checkbox.isChecked = false + return@setOnCheckedChangeListener } + + if (isChecked) { + selectedFiles.add(changeAtPos.path) + } else { + selectedFiles.remove(changeAtPos.path) + } + onSelectionChanged(selectedFiles.size) } val contentAlpha = if (isConflicted) 0.5f else 1.0f