Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7981945
feat(ADFA-2883): Display current branch name
dara-abijo-adfa Mar 24, 2026
48646ad
Merge branch 'stage' into ADFA-2883-git-pull-push
dara-abijo-adfa Mar 25, 2026
b9ee908
feat(ADFA-2883): Implement git push logic
dara-abijo-adfa Mar 25, 2026
1228126
feat(ADFA-2883): Show push action
dara-abijo-adfa Mar 25, 2026
9258555
feat(ADFA-2883): Push local commits
dara-abijo-adfa Mar 25, 2026
5c1e340
feat(ADFA-2883): Update commit history after push
dara-abijo-adfa Mar 26, 2026
b4add5d
feat(ADFA-2883): Implement git pull logic
dara-abijo-adfa Mar 26, 2026
aa4b85d
feat(ADFA-2883): Pull remote changes
dara-abijo-adfa Mar 26, 2026
c6358ba
Merge branch 'stage' into ADFA-2883-git-pull-push
dara-abijo-adfa Mar 26, 2026
ff20e49
feat(ADFA-2883): Display messages using existing flash bar
dara-abijo-adfa Mar 26, 2026
ca6181d
Merge branch 'stage' into ADFA-2883-git-pull-push
dara-abijo-adfa Mar 27, 2026
450d452
fix(ADFA-2883): Cancel delayed idle resets
dara-abijo-adfa Mar 27, 2026
3105c61
feat(ADFA-2883): Persist credentials after a successful pull
dara-abijo-adfa Mar 27, 2026
8911f61
fix(ADFA-2883): Use correct string resource
dara-abijo-adfa Mar 27, 2026
53d0a35
feat(ADFA-2883): Replace shared preferences with Android keystore
dara-abijo-adfa Mar 27, 2026
26fd63c
fix(ADFA-2883): Correct commits count error
dara-abijo-adfa Mar 27, 2026
45dd536
refactor(ADFA-2883): Remove duplicate method
dara-abijo-adfa Mar 27, 2026
f92bf24
fix(ADFA-2883): Handle keystore init errors
dara-abijo-adfa Mar 27, 2026
026670e
feat(ADFA-2883): Inject objects
dara-abijo-adfa Mar 31, 2026
01f32e1
refactor(ADFA-2883): Flatten nested ifs
dara-abijo-adfa Mar 31, 2026
2ad7da4
Merge branch 'stage' into ADFA-2883-git-pull-push
dara-abijo-adfa Mar 31, 2026
d00ac4d
feat(ADFA-2883): Check internet connection before push/pull
dara-abijo-adfa Mar 31, 2026
edb660c
feat(ADFA-2883): Handle exceptions
dara-abijo-adfa Mar 31, 2026
b88de50
refactor(ADFA-2883): Minor changes
dara-abijo-adfa Mar 31, 2026
3921b1d
refactor(ADFA-2883): Handle exceptions
dara-abijo-adfa Apr 1, 2026
32a0690
refactor(ADFA-2883): Optimise crypto operations
dara-abijo-adfa Apr 1, 2026
fd8d8e1
feat(ADFA-2883): Clear invalid credentials
dara-abijo-adfa Apr 1, 2026
fc52d50
feat(ADFA-2883): Handle authorisation errors
dara-abijo-adfa Apr 1, 2026
8b529b5
Merge branch 'stage' into ADFA-2883-git-pull-push
dara-abijo-adfa Apr 1, 2026
dbdee7b
Merge branch 'stage' into ADFA-2883-git-pull-push
dara-abijo-adfa Apr 2, 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
9 changes: 7 additions & 2 deletions app/src/main/java/com/itsaky/androidide/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import com.itsaky.androidide.agent.GeminiMacroProcessor
import com.itsaky.androidide.agent.viewmodel.ChatViewModel
import com.itsaky.androidide.analytics.AnalyticsManager
import com.itsaky.androidide.analytics.IAnalyticsManager
import com.itsaky.androidide.git.core.GitCredentialsManager
import com.itsaky.androidide.roomData.recentproject.RecentProjectRoomDatabase
import com.itsaky.androidide.viewmodel.GitBottomSheetViewModel
import com.itsaky.androidide.viewmodel.MainViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import org.koin.core.module.dsl.viewModel

Expand All @@ -28,8 +30,10 @@ val coreModule =
ChatViewModel()
}
viewModel {
GitBottomSheetViewModel()
GitBottomSheetViewModel(get())
}
viewModel { MainViewModel(get()) }


single<CoroutineScope> {
CoroutineScope(SupervisorJob() + Dispatchers.IO)
Expand All @@ -43,5 +47,6 @@ val coreModule =
get<RecentProjectRoomDatabase>().recentProjectDao()
}

viewModel { MainViewModel(get()) }
single { GitCredentialsManager(get()) }

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,36 @@ import android.view.View
import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.itsaky.androidide.R
import com.itsaky.androidide.activities.PreferencesActivity
import com.itsaky.androidide.databinding.DialogGitCredentialsBinding
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.preferences.internal.GitPreferences
import com.itsaky.androidide.utils.flashSuccess
import com.itsaky.androidide.viewmodel.GitBottomSheetViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.activityViewModel

class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {

private val viewModel: GitBottomSheetViewModel by activityViewModels()
private val viewModel: GitBottomSheetViewModel by activityViewModel()
private lateinit var fileChangeAdapter: GitFileChangeAdapter
private lateinit var credentialsManager: GitCredentialsManager

private var _binding: FragmentGitBottomSheetBinding? = null
private val binding get() = _binding!!

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentGitBottomSheetBinding.bind(view)
credentialsManager = GitCredentialsManager(requireContext())

fileChangeAdapter = GitFileChangeAdapter(
onFileClicked = { change ->
Expand All @@ -51,6 +56,17 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
binding.recyclerView.adapter = fileChangeAdapter

viewLifecycleOwner.lifecycleScope.launch {
launch {
viewModel.currentBranch.collectLatest { branchName ->
if (branchName != null) {
binding.tvBranchName.visibility = View.VISIBLE
binding.tvBranchName.text = getString(R.string.current_branch_name, branchName)
} else {
binding.tvBranchName.visibility = View.GONE
}
}
}

combine(
viewModel.isGitRepository,
viewModel.gitStatus
Expand Down Expand Up @@ -95,6 +111,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
dialog.show(childFragmentManager, "CommitHistoryDialog")
}

setupPullUI()
}

override fun onResume() {
Expand Down Expand Up @@ -187,6 +204,80 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
binding.commitButton.isEnabled = hasSummary && hasSelection && hasAuthor
}

private fun setupPullUI() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isGitRepository.collectLatest { isRepo ->
binding.btnPull.visibility = if (isRepo) View.VISIBLE else View.GONE
}
}

viewLifecycleOwner.lifecycleScope.launch {
viewModel.pullState.collectLatest { state ->
when (state) {
is GitBottomSheetViewModel.PullUiState.Idle -> {
binding.btnPull.isEnabled = true
binding.pullProgress.visibility = View.GONE
}
is GitBottomSheetViewModel.PullUiState.Pulling -> {
binding.btnPull.isEnabled = false
binding.pullProgress.visibility = View.VISIBLE
}
is GitBottomSheetViewModel.PullUiState.Success -> {
binding.btnPull.isEnabled = true
binding.pullProgress.visibility = View.GONE
flashSuccess(R.string.pull_successful)
viewModel.resetPullState()
}
is GitBottomSheetViewModel.PullUiState.Error -> {
binding.btnPull.isEnabled = true
binding.pullProgress.visibility = View.GONE
val message =
state.errorResId?.let { getString(it) } ?: state.message
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.pull_failed)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
}
}

binding.btnPull.setOnClickListener {
val username = credentialsManager.getUsername()
val token = credentialsManager.getToken()
if (!username.isNullOrBlank() && !token.isNullOrBlank()) {
viewModel.pull(username, token)
} else {
showCredentialsDialog()
}
}
}

private fun showCredentialsDialog() {
val context = requireContext()
val dialogBinding = DialogGitCredentialsBinding.inflate(layoutInflater)

dialogBinding.username.setText(credentialsManager.getUsername())
dialogBinding.token.setText(credentialsManager.getToken())

MaterialAlertDialogBuilder(context)
.setTitle(R.string.git_credentials_title)
.setView(dialogBinding.root)
.setPositiveButton(R.string.pull) { _, _ ->
val username = dialogBinding.username.text?.toString()?.trim()
val token = dialogBinding.token.text?.toString()?.trim()
if (!username.isNullOrBlank() && !token.isNullOrBlank()) {
viewModel.pull(username, token)
}
}
.setNeutralButton(R.string.git_credentials_clear) { _, _ ->
credentialsManager.clearCredentials()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,30 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.itsaky.androidide.R
import com.itsaky.androidide.databinding.DialogGitCommitHistoryBinding
import com.itsaky.androidide.databinding.DialogGitCredentialsBinding
import com.itsaky.androidide.fragments.git.adapter.GitCommitHistoryAdapter
import com.itsaky.androidide.git.core.GitCredentialsManager
import com.itsaky.androidide.git.core.models.CommitHistoryUiState
import com.itsaky.androidide.utils.flashSuccess
import com.itsaky.androidide.viewmodel.GitBottomSheetViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.activityViewModel

class GitCommitHistoryDialog : DialogFragment() {

private var _binding: DialogGitCommitHistoryBinding? = null
private val binding get() = _binding!!
private val viewModel: GitBottomSheetViewModel by activityViewModels()
private val viewModel: GitBottomSheetViewModel by activityViewModel()
private lateinit var commitHistoryAdapter: GitCommitHistoryAdapter
private val credentialsManager: GitCredentialsManager by inject()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -83,6 +89,84 @@ class GitCommitHistoryDialog : DialogFragment() {
}
}
}

setupPushUI()
}

private fun setupPushUI() {
binding.btnPush.setOnClickListener {
val username = credentialsManager.getUsername()
val token = credentialsManager.getToken()
if (!username.isNullOrBlank() && !token.isNullOrBlank()) {
viewModel.push(username, token)
} else {
showCredentialsDialog()
}
}

viewLifecycleOwner.lifecycleScope.launch {
viewModel.localCommitsCount.collectLatest { count ->
binding.btnPush.visibility = if (count > 0) View.VISIBLE else View.GONE
}
}

viewLifecycleOwner.lifecycleScope.launch {
viewModel.pushState.collectLatest { state ->
when (state) {
is GitBottomSheetViewModel.PushUiState.Idle -> {
binding.btnPush.isEnabled = true
binding.btnPush.text = getString(R.string.push)
binding.pushProgress.visibility = View.GONE
}
is GitBottomSheetViewModel.PushUiState.Pushing -> {
binding.btnPush.isEnabled = false
binding.pushProgress.visibility = View.VISIBLE
}
is GitBottomSheetViewModel.PushUiState.Success -> {
binding.btnPush.isEnabled = true
binding.pushProgress.visibility = View.GONE
flashSuccess(R.string.push_successful)
viewModel.resetPushState()
dismiss()
}
is GitBottomSheetViewModel.PushUiState.Error -> {
binding.btnPush.isEnabled = true
binding.pushProgress.visibility = View.GONE
val message =
state.errorResId?.let { getString(it) } ?: state.message
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.push_failed)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
}
}

}

private fun showCredentialsDialog() {
val dialogBinding = DialogGitCredentialsBinding.inflate(layoutInflater)

dialogBinding.username.setText(credentialsManager.getUsername())
dialogBinding.token.setText(credentialsManager.getToken())

MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.git_credentials_title)
.setView(dialogBinding.root)
.setPositiveButton(R.string.push) { _, _ ->
val username = dialogBinding.username.text?.toString()?.trim()
val token = dialogBinding.token.text?.toString()?.trim()
if (!username.isNullOrBlank() && !token.isNullOrBlank()) {
viewModel.push(username, token)
}
}
.setNeutralButton(R.string.git_credentials_clear) { _, _ ->
credentialsManager.clearCredentials()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}

override fun onDestroyView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat.getColor
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.itsaky.androidide.R
import com.itsaky.androidide.databinding.DialogGitDiffBinding
import com.itsaky.androidide.viewmodel.GitBottomSheetViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.androidx.viewmodel.ext.android.activityViewModel
import java.io.File

class GitDiffViewerDialog : DialogFragment() {

private val viewModel: GitBottomSheetViewModel by activityViewModels()
private val viewModel: GitBottomSheetViewModel by activityViewModel()

private var filePath: String = ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class GitCommitHistoryAdapter :
tvCommitMessage.text = commit.message
tvCommitAuthor.text = commit.authorName
tvCommitTime.text = dateFormat.format(Date(commit.timestamp))
imgNotPushedCommit.setImageResource(if (commit.hasBeenPushed) R.drawable.ic_cloud_done else R.drawable.ic_upload)
imgNotPushedCommit.contentDescription = if (commit.hasBeenPushed) {
imgCommitStatus.setImageResource(if (commit.hasBeenPushed) R.drawable.ic_cloud_done else R.drawable.ic_upload)
imgCommitStatus.contentDescription = if (commit.hasBeenPushed) {
binding.root.context.getString(R.string.commit_pushed)
} else {
binding.root.context.getString(R.string.commit_not_pushed)
Expand Down
Loading
Loading