Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
},
onSelectionChanged = {
validateCommitButton()
updateCheckAllButton()
},
onResolveConflict = { change ->
viewModel.resolveConflict(change.path)
Expand Down Expand Up @@ -118,6 +119,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
emptyView.visibility = View.VISIBLE
emptyView.text = getString(R.string.not_a_git_repo)
recyclerView.visibility = View.GONE
btnCheckAll.visibility = View.GONE
commitSection.visibility = View.GONE
authorWarning.visibility = View.GONE
commitHistoryButton.visibility = View.GONE
Expand All @@ -128,24 +130,32 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
emptyView.visibility = View.VISIBLE
emptyView.text = getString(R.string.no_uncommitted_changes)
recyclerView.visibility = View.GONE
btnCheckAll.visibility = View.GONE
commitSection.visibility = View.GONE
authorWarning.visibility = View.GONE
commitHistoryButton.visibility = View.VISIBLE
btnAbortMerge.visibility = View.GONE
}

else -> {
// Only offer "Check All" when there is at least one
// non-conflicted file; conflicted files can't be staged.
val hasSelectable = allChanges.any { it.type != ChangeType.CONFLICTED }
binding.apply {
emptyView.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
btnCheckAll.visibility =
if (hasSelectable) View.VISIBLE else View.GONE
commitSection.visibility = View.VISIBLE
authorWarning.visibility =
if (hasAuthorInfo()) View.GONE else View.VISIBLE
commitHistoryButton.visibility = View.VISIBLE
btnAbortMerge.visibility =
if (status.isMerging) View.VISIBLE else View.GONE
}
fileChangeAdapter.submitList(allChanges)
fileChangeAdapter.submitList(allChanges) {
updateCheckAllButton()
}
}
}
}.collectLatest { }
Expand Down Expand Up @@ -186,6 +196,14 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
binding.commitSummary.doAfterTextChanged { validateCommitButton() }
binding.commitDescription.doAfterTextChanged { validateCommitButton() }

binding.btnCheckAll.setOnClickListener {
if (fileChangeAdapter.areAllSelected()) {
fileChangeAdapter.clearSelection()
} else {
fileChangeAdapter.selectAll()
}
}

binding.btnAbortMerge.apply {
setOnClickListener {
val dialog = MaterialAlertDialogBuilder(requireContext())
Expand Down Expand Up @@ -228,6 +246,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
binding.commitSummary.text?.clear()
binding.commitDescription.text?.clear()
fileChangeAdapter.selectedFiles.clear()
updateCheckAllButton()
}
}
}
Expand Down Expand Up @@ -279,12 +298,22 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
}

private fun validateCommitButton() {
// May be invoked from async adapter callbacks; bail if the view is gone.
val binding = _binding ?: return
val hasSummary = !binding.commitSummary.text.isNullOrBlank()
val hasSelection = fileChangeAdapter.selectedFiles.isNotEmpty()
val hasAuthor = hasAuthorInfo()
binding.commitButton.isEnabled = hasSummary && hasSelection && hasAuthor
}

private fun updateCheckAllButton() {
// May be invoked from the async submitList commit callback; bail if the view is gone.
val binding = _binding ?: return
binding.btnCheckAll.setText(
if (fileChangeAdapter.areAllSelected()) R.string.uncheck_all else R.string.check_all
)
}

private fun setupPullUI() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isGitRepository.collectLatest { isRepo ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,41 @@ class GitFileChangeAdapter(
// Keep track of which files are selected to be committed
val selectedFiles = mutableSetOf<String>()

// Conflicted files can't be staged, so they are excluded from "select all".
private val selectablePaths: List<String>
get() = currentList.filter { it.type != ChangeType.CONFLICTED }.map { it.path }

/** True when every selectable (non-conflicted) file is currently selected. */
fun areAllSelected(): Boolean =
selectablePaths.isNotEmpty() && selectedFiles.containsAll(selectablePaths)

/** Select every non-conflicted file. */
fun selectAll() {
selectedFiles.addAll(selectablePaths)
notifyItemRangeChanged(0, itemCount)
onSelectionChanged(selectedFiles.size)
}

/** Clear the entire selection. */
fun clearSelection() {
selectedFiles.clear()
notifyItemRangeChanged(0, itemCount)
onSelectionChanged(selectedFiles.size)
}

override fun onCurrentListChanged(
previousList: List<FileChange>,
currentList: List<FileChange>
) {
super.onCurrentListChanged(previousList, currentList)
// Drop selections for files that are no longer in the change set so they
// aren't committed and don't skew areAllSelected()/the commit button.
val currentPaths = currentList.mapTo(HashSet()) { it.path }
if (selectedFiles.retainAll(currentPaths)) {
onSelectionChanged(selectedFiles.size)
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemGitFileChangeBinding.inflate(
LayoutInflater.from(parent.context), parent, false
Expand Down
19 changes: 18 additions & 1 deletion app/src/main/res/layout/fragment_git_bottom_sheet.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/btnPull"
app:layout_constraintEnd_toStartOf="@id/btnCheckAll"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

Expand Down Expand Up @@ -68,12 +68,29 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_branch_name" />

<com.google.android.material.button.MaterialButton
android:id="@+id/btnCheckAll"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:paddingHorizontal="8dp"
android:text="@string/check_all"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/tv_branch_name"
app:layout_constraintEnd_toStartOf="@id/btnPull"
app:layout_constraintTop_toTopOf="@id/tv_branch_name" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:paddingEnd="8dp"
android:paddingVertical="8dp"
android:fadeScrollbars="false"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layout_constraintBottom_toTopOf="@id/guideline_limit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/res/layout/item_git_file_change.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingVertical="8dp">
android:paddingVertical="4dp">

<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:checked="true"
android:minWidth="0dp"
android:minHeight="0dp" />

Expand Down
2 changes: 2 additions & 0 deletions resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,8 @@
<string name="no_save_before_git_action">Proceed without saving</string>
<string name="save_before_git_action">Save before proceeding</string>
<string name="mark_as_resolved">Mark resolved</string>
<string name="check_all">Check All</string>
<string name="uncheck_all">Uncheck All</string>

<!-- Templates -->
<string name="template_exec_info_basepath">Starting project creation for %1$s</string>
Expand Down
Loading