@@ -77,6 +77,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
7777 },
7878 onSelectionChanged = {
7979 validateCommitButton()
80+ updateCheckAllButton()
8081 },
8182 onResolveConflict = { change ->
8283 viewModel.resolveConflict(change.path)
@@ -118,6 +119,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
118119 emptyView.visibility = View .VISIBLE
119120 emptyView.text = getString(R .string.not_a_git_repo)
120121 recyclerView.visibility = View .GONE
122+ btnCheckAll.visibility = View .GONE
121123 commitSection.visibility = View .GONE
122124 authorWarning.visibility = View .GONE
123125 commitHistoryButton.visibility = View .GONE
@@ -128,24 +130,32 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
128130 emptyView.visibility = View .VISIBLE
129131 emptyView.text = getString(R .string.no_uncommitted_changes)
130132 recyclerView.visibility = View .GONE
133+ btnCheckAll.visibility = View .GONE
131134 commitSection.visibility = View .GONE
132135 authorWarning.visibility = View .GONE
133136 commitHistoryButton.visibility = View .VISIBLE
134137 btnAbortMerge.visibility = View .GONE
135138 }
136139
137140 else -> {
141+ // Only offer "Check All" when there is at least one
142+ // non-conflicted file; conflicted files can't be staged.
143+ val hasSelectable = allChanges.any { it.type != ChangeType .CONFLICTED }
138144 binding.apply {
139145 emptyView.visibility = View .GONE
140146 recyclerView.visibility = View .VISIBLE
147+ btnCheckAll.visibility =
148+ if (hasSelectable) View .VISIBLE else View .GONE
141149 commitSection.visibility = View .VISIBLE
142150 authorWarning.visibility =
143151 if (hasAuthorInfo()) View .GONE else View .VISIBLE
144152 commitHistoryButton.visibility = View .VISIBLE
145153 btnAbortMerge.visibility =
146154 if (status.isMerging) View .VISIBLE else View .GONE
147155 }
148- fileChangeAdapter.submitList(allChanges)
156+ fileChangeAdapter.submitList(allChanges) {
157+ updateCheckAllButton()
158+ }
149159 }
150160 }
151161 }.collectLatest { }
@@ -186,6 +196,14 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
186196 binding.commitSummary.doAfterTextChanged { validateCommitButton() }
187197 binding.commitDescription.doAfterTextChanged { validateCommitButton() }
188198
199+ binding.btnCheckAll.setOnClickListener {
200+ if (fileChangeAdapter.areAllSelected()) {
201+ fileChangeAdapter.clearSelection()
202+ } else {
203+ fileChangeAdapter.selectAll()
204+ }
205+ }
206+
189207 binding.btnAbortMerge.apply {
190208 setOnClickListener {
191209 val dialog = MaterialAlertDialogBuilder (requireContext())
@@ -228,6 +246,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
228246 binding.commitSummary.text?.clear()
229247 binding.commitDescription.text?.clear()
230248 fileChangeAdapter.selectedFiles.clear()
249+ updateCheckAllButton()
231250 }
232251 }
233252 }
@@ -279,12 +298,22 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
279298 }
280299
281300 private fun validateCommitButton () {
301+ // May be invoked from async adapter callbacks; bail if the view is gone.
302+ val binding = _binding ? : return
282303 val hasSummary = ! binding.commitSummary.text.isNullOrBlank()
283304 val hasSelection = fileChangeAdapter.selectedFiles.isNotEmpty()
284305 val hasAuthor = hasAuthorInfo()
285306 binding.commitButton.isEnabled = hasSummary && hasSelection && hasAuthor
286307 }
287308
309+ private fun updateCheckAllButton () {
310+ // May be invoked from the async submitList commit callback; bail if the view is gone.
311+ val binding = _binding ? : return
312+ binding.btnCheckAll.setText(
313+ if (fileChangeAdapter.areAllSelected()) R .string.uncheck_all else R .string.check_all
314+ )
315+ }
316+
288317 private fun setupPullUI () {
289318 viewLifecycleOwner.lifecycleScope.launch {
290319 viewModel.isGitRepository.collectLatest { isRepo ->
0 commit comments