Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8bfefb0
feat(ADFA-3760): Display watermark option
dara-abijo-adfa Sep 7, 2026
d820278
feat(ADFA-3760): Add commit watermark preference
dara-abijo-adfa Sep 7, 2026
f80459b
feat(ADFA-3760): Show watermark option based on preference
dara-abijo-adfa Sep 7, 2026
29cf74d
feat(ADFA-3760): Format commit message with watermark
dara-abijo-adfa Sep 7, 2026
2d9057b
fix(ADFA-3760): sync selected files state
dara-abijo-adfa Sep 7, 2026
a2c0970
fix(ADFA-3760): Address PR comments
dara-abijo-adfa Sep 7, 2026
fd3c14e
feat(ADFA-3760): Disable watermark per project
dara-abijo-adfa Sep 8, 2026
ac2d9ef
test(ADFA-3760): Test git watermark
dara-abijo-adfa Sep 8, 2026
d8a6ba0
fix(ADFA-3760): Sync switch checked state
dara-abijo-adfa Sep 9, 2026
6dd165e
feat(ADFA-3760): Save watermark preference in Git config
dara-abijo-adfa Sep 9, 2026
77bcfee
test(ADFA-3760): Write unit tests
dara-abijo-adfa Sep 9, 2026
8374f89
feat(ADFA-3760): Add watermark preference group
dara-abijo-adfa Sep 9, 2026
51e5592
Merge branch 'stage' into ADFA-3760-git-commit-watermark
dara-abijo-adfa Sep 9, 2026
b7c3905
format(ADFA-3760): Apply spotless formatting
dara-abijo-adfa Sep 9, 2026
78959f8
feat(ADFA-3760): Sync switch checked state
dara-abijo-adfa Sep 9, 2026
4232e54
fix(ADFA-3760): Prioritize global opt out
dara-abijo-adfa Sep 9, 2026
64afb27
feat(ADFA-3760): Handle watermark config errors
dara-abijo-adfa Sep 9, 2026
ca837df
test(ADFA-3670): Fix broken test
dara-abijo-adfa Sep 10, 2026
21c258f
fix(ADFA-3760): Propagare read failures
dara-abijo-adfa Sep 10, 2026
60b4ffc
Merge branch 'stage' into ADFA-3760-git-commit-watermark
dara-abijo-adfa Sep 10, 2026
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 @@ -9,6 +9,8 @@ import android.text.style.ClickableSpan
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
Expand All @@ -33,6 +35,7 @@ import com.itsaky.androidide.idetooltips.TooltipTag
import com.itsaky.androidide.idetooltips.attachTooltip
import com.itsaky.androidide.interfaces.IEditorHandler
import com.itsaky.androidide.preferences.internal.GitPreferences
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.utils.flashError
import com.itsaky.androidide.utils.flashSuccess
import com.itsaky.androidide.utils.onLongPress
Expand All @@ -55,6 +58,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
private lateinit var branchPopupWindow: GitBranchPopupWindow

private var _binding: FragmentGitBottomSheetBinding? = null
private var isUpdatingWatermarkUI = false
val binding: FragmentGitBottomSheetBinding
get() = checkNotNull(_binding) { "Fragment binding is null or view has been destroyed" }

Expand Down Expand Up @@ -276,6 +280,18 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
}
}

launch {
viewModel.isProjectWatermarkEnabled.collectLatest {
updateWatermarkUI()
}
}

launch {
viewModel.watermarkError.collectLatest {
flashError(getString(R.string.git_watermark_save_failed))
}
}

combine(
viewModel.isGitRepository,
viewModel.gitStatus,
Expand All @@ -290,6 +306,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
recyclerView.visibility = View.GONE
cbCheckAll.visibility = View.GONE
commitSection.visibility = View.GONE
layoutWatermark.visibility = View.GONE
authorWarning.visibility = View.GONE
commitHistoryButton.visibility = View.GONE
btnAbortMerge.visibility = View.GONE
Expand All @@ -306,6 +323,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
cbCheckAll.isChecked = false
cbCheckAll.text = getString(R.string.changed_files_count, 0)
commitSection.visibility = View.GONE
layoutWatermark.visibility = View.GONE
authorWarning.visibility = View.GONE
commitHistoryButton.visibility = View.VISIBLE
btnAbortMerge.visibility = View.GONE
Expand All @@ -321,6 +339,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
cbCheckAll.isEnabled = hasSelectable
cbCheckAll.text = getString(R.string.changed_files_count, allChanges.size)
commitSection.visibility = View.VISIBLE
updateWatermarkUI(isRepo = isRepo, hasChanges = allChanges.isNotEmpty())
authorWarning.visibility =
if (hasAuthorInfo()) View.GONE else View.VISIBLE
commitHistoryButton.visibility = View.VISIBLE
Expand Down Expand Up @@ -351,6 +370,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
override fun onResume() {
super.onResume()
updateAuthorUI()
updateWatermarkUI()
}

private fun updateAuthorUI() {
Expand All @@ -361,6 +381,53 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
validateCommitButton()
}

private fun updateWatermarkUI(
isRepo: Boolean = viewModel.isGitRepository.value,
hasChanges: Boolean =
viewModel.gitStatus.value
.allChanges()
.isNotEmpty(),
) {
if (!isRepo || !hasChanges) {
binding.layoutWatermark.isGone = true
return
}

binding.layoutWatermark.isVisible = true
isUpdatingWatermarkUI = true

try {
if (!GitPreferences.shouldAddGlobalCommitWatermark) {
showGlobalWatermarkDisabled()
} else {
showProjectWatermarkState()
}
} finally {
isUpdatingWatermarkUI = false
}
}

private fun showGlobalWatermarkDisabled() {
val projectEnabled = viewModel.isProjectWatermarkEnabled.value
binding.apply {
switchCommitWatermark.isEnabled = false
switchCommitWatermark.isChecked = projectEnabled
tvCommitWatermark.isGone = true
tvWatermarkGlobalDisabled.isVisible = true
}
}

private fun showProjectWatermarkState() {
val projectEnabled = viewModel.isProjectWatermarkEnabled.value

binding.apply {
switchCommitWatermark.isEnabled = true
switchCommitWatermark.isChecked = projectEnabled
tvCommitWatermark.isVisible = projectEnabled
tvWatermarkGlobalDisabled.isGone = true
}
}

private fun hasAuthorInfo(): Boolean = !GitPreferences.userName.isNullOrBlank() && !GitPreferences.userEmail.isNullOrBlank()

private fun setupCommitUI() {
Expand Down Expand Up @@ -408,30 +475,81 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
binding.commitSummary.text
?.toString()
?.trim() ?: ""

val description =
binding.commitDescription.text
?.toString()
?.trim()

if (summary.isNotEmpty() && fileChangeAdapter.selectedFiles.isNotEmpty() && hasAuthorInfo()) {
viewModel.commitChanges(
val watermark =
getString(R.string.made_with_code_on_the_go)
.takeIf {
GitPreferences.shouldAddGlobalCommitWatermark && binding.switchCommitWatermark.isChecked
}

val message =
formatCommitMessage(
summary = summary,
description = description,
watermark = watermark,
)

if (summary.isNotEmpty() && fileChangeAdapter.selectedFiles.isNotEmpty() && hasAuthorInfo()) {
viewModel.commitChanges(
message = message,
selectedPaths = fileChangeAdapter.selectedFiles.toList(),
) {
// Clear the inputs on successful commit
binding.commitSummary.text?.clear()
binding.commitDescription.text?.clear()
fileChangeAdapter.selectedFiles.clear()
fileChangeAdapter.clearSelection()
updateCheckAllButton()
}
}
}
}
setTooltipOnView(TooltipTag.PROJECT_GIT_COMMIT)
}
binding.switchCommitWatermark.setOnCheckedChangeListener { _, isChecked ->
if (isUpdatingWatermarkUI) {
return@setOnCheckedChangeListener
}
binding.tvCommitWatermark.isVisible = isChecked
if (viewModel.isProjectWatermarkEnabled.value != isChecked) {
viewModel.setProjectWatermarkEnabled(isChecked)
}
}
updateWatermarkUI()
}

fun formatCommitMessage(
summary: String,
description: String? = null,
watermark: String? = null,
): String {
val trimmedSummary = summary.trim()
val trimmedDescription = description?.trim()?.takeIf(String::isNotEmpty)
val trimmedWatermark = watermark?.trim()?.takeIf(String::isNotEmpty)

val effectiveWatermark =
trimmedWatermark
?.takeUnless { containsWatermark(trimmedSummary, trimmedDescription, it) }

return listOfNotNull(
trimmedSummary.takeIf(String::isNotEmpty),
trimmedDescription,
effectiveWatermark,
).joinToString("\n\n")
}

internal fun containsWatermark(
summary: String,
description: String?,
watermark: String,
): Boolean =
summary.contains(watermark, ignoreCase = true) ||
(description?.contains(watermark, ignoreCase = true) == true)

private fun showAuthorPopup() {
val name = GitPreferences.userName.orEmpty().ifBlank { getString(R.string.author_not_set) }
val email =
Expand Down
Loading
Loading