From f618dfdf6af2876a1100638d5142b7d492a8ae26 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Wed, 11 Mar 2026 17:41:41 +0100 Subject: [PATCH 1/2] feat(ADFA-3074): Add retry button --- .../fragments/CloneRepositoryFragment.kt | 23 +- .../viewmodel/CloneRepositoryViewModel.kt | 23 +- .../res/layout/fragment_clone_repository.xml | 279 ++++++++++-------- app/src/main/res/values/strings.xml | 6 - resources/src/main/res/values/strings.xml | 7 + 5 files changed, 194 insertions(+), 144 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt index 6cdf18215f..b462f08995 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt @@ -82,12 +82,11 @@ class CloneRepositoryFragment : BaseFragment() { } cloneButton.setOnClickListener { - val url = repoUrl.text.toString() - val path = localPath.text.toString() - val username = if (authCheckbox.isChecked) username.text.toString() else null - val password = if (authCheckbox.isChecked) password.text.toString() else null - - viewModel.cloneRepository(url, path, username, password) + cloneRepo() + } + + retryButton.setOnClickListener { + cloneRepo() } exitButton.setOnClickListener { @@ -110,6 +109,15 @@ class CloneRepositoryFragment : BaseFragment() { } } + private fun FragmentCloneRepositoryBinding.cloneRepo() { + val url = repoUrl.text.toString() + val path = localPath.text.toString() + val mUsername = if (authCheckbox.isChecked) username.text.toString() else null + val mPassword = if (authCheckbox.isChecked) password.text.toString() else null + + viewModel.cloneRepository(url, path, mUsername, mPassword) + } + private fun observeViewModel() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -140,14 +148,17 @@ class CloneRepositoryFragment : BaseFragment() { when (state) { is CloneRepoUiState.Idle -> { cloneButton.isEnabled = state.isCloneButtonEnabled + retryButton.visibility = View.GONE statusText.text = "" } is CloneRepoUiState.Cloning -> { cloneButton.isEnabled = false + retryButton.visibility = View.GONE statusText.text = getString(R.string.cloning_repo) } is CloneRepoUiState.Error -> { cloneButton.isEnabled = true + retryButton.visibility = View.VISIBLE val statusMessage = state.errorResId?.let { getString(it) } ?: state.errorMessage statusText.text = statusMessage } diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt index f6379ddcb7..169543b131 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt @@ -16,7 +16,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.eclipse.jgit.lib.ProgressMonitor import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider +import org.eclipse.jgit.api.errors.TransportException +import java.net.UnknownHostException import java.io.File +import com.blankj.utilcode.util.NetworkUtils class CloneRepositoryViewModel(application: Application) : AndroidViewModel(application) { @@ -67,6 +70,17 @@ class CloneRepositoryViewModel(application: Application) : AndroidViewModel(appl return } + if (!NetworkUtils.isConnected()) { + _uiState.update { + CloneRepoUiState.Error( + url = url, + localPath = localPath, + errorResId = R.string.no_internet_connection + ) + } + return + } + viewModelScope.launch { var hasCloned = false _uiState.update { @@ -143,12 +157,17 @@ class CloneRepositoryViewModel(application: Application) : AndroidViewModel(appl CloneRepoUiState.Success(localPath = localPath) } } catch (e: Exception) { - val errorMessage = e.message ?: application.getString(R.string.unknown_error) + // Error handling + val isNetworkError = e is TransportException && e.cause is UnknownHostException + val errorResId = if (isNetworkError) R.string.no_internet_connection else null + val errorMessage = if (isNetworkError) null else (e.message ?: application.getString(R.string.unknown_error)) + _uiState.update { CloneRepoUiState.Error( url = url, localPath = localPath, - errorMessage = application.getString(R.string.clone_failed, errorMessage) + errorResId = errorResId, + errorMessage = errorMessage?.let { application.getString(R.string.clone_failed, it) } ) } } finally { diff --git a/app/src/main/res/layout/fragment_clone_repository.xml b/app/src/main/res/layout/fragment_clone_repository.xml index 8b6b90b732..7b2e9e863a 100644 --- a/app/src/main/res/layout/fragment_clone_repository.xml +++ b/app/src/main/res/layout/fragment_clone_repository.xml @@ -1,156 +1,175 @@ - - - - + android:layout_margin="16dp"> - - - - - - - + + + android:layout_marginTop="12dp" + android:hint="@string/repository_url" + app:layout_constraintTop_toBottomOf="@id/tv_download_project"> - + - - - + - + android:layout_marginTop="16dp" + android:hint="@string/local_path" + app:endIconContentDescription="@string/pick_folder" + app:endIconDrawable="@drawable/ic_folder" + app:endIconMode="custom" + app:layout_constraintTop_toBottomOf="@id/repoUrlLayout"> + + + + + + + + - + - + - - - - - + android:layout_marginTop="16dp" + android:hint="@string/personal_access_token" + app:endIconMode="password_toggle" + app:layout_constraintTop_toBottomOf="@id/usernameLayout"> + + + + + + - - - - - + + + + + + + - + - - + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e728ab5e68..563068d8d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,10 +19,4 @@ Code on the Go Use simplified prompt - File added - File modified - File deleted - File untracked - File renamed - File conflicted diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index 5c286cf69e..14222a649f 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -1167,4 +1167,11 @@ Unable to load diff Diff Viewer Loading Git Diff… + No internet connection. Please check your network and try again. + File added + File modified + File deleted + File untracked + File renamed + Conflicted File From 99aa4928902faaed802b23c40c0e32e797e334d9 Mon Sep 17 00:00:00 2001 From: Oluwadara Abijo Date: Wed, 11 Mar 2026 18:25:16 +0100 Subject: [PATCH 2/2] feat(ADFA-3074): Handle connection errors --- .../viewmodel/CloneRepositoryViewModel.kt | 18 +++++++++++++++--- resources/src/main/res/values/strings.xml | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt index 169543b131..650652921a 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt @@ -4,7 +4,6 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.application import androidx.lifecycle.viewModelScope -import com.itsaky.androidide.R import com.itsaky.androidide.git.core.GitRepositoryManager import com.itsaky.androidide.git.core.models.CloneRepoUiState import kotlinx.coroutines.Dispatchers @@ -18,8 +17,10 @@ import org.eclipse.jgit.lib.ProgressMonitor import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider import org.eclipse.jgit.api.errors.TransportException import java.net.UnknownHostException +import java.io.EOFException import java.io.File import com.blankj.utilcode.util.NetworkUtils +import com.itsaky.androidide.resources.R class CloneRepositoryViewModel(application: Application) : AndroidViewModel(application) { @@ -159,8 +160,19 @@ class CloneRepositoryViewModel(application: Application) : AndroidViewModel(appl } catch (e: Exception) { // Error handling val isNetworkError = e is TransportException && e.cause is UnknownHostException - val errorResId = if (isNetworkError) R.string.no_internet_connection else null - val errorMessage = if (isNetworkError) null else (e.message ?: application.getString(R.string.unknown_error)) + val isConnectionDrop = e.cause is EOFException || + e.message?.contains("Unexpected end of stream") == true || + e.message?.contains("Software caused connection abort") == true + + val errorResId = when { + isNetworkError -> R.string.no_internet_connection + isConnectionDrop -> R.string.connection_lost + else -> null + } + + val errorMessage = if (errorResId == null) { + e.message ?: application.getString(R.string.unknown_error) + } else null _uiState.update { CloneRepoUiState.Error( diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index 14222a649f..ce66925024 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -1168,6 +1168,7 @@ Diff Viewer Loading Git Diff… No internet connection. Please check your network and try again. + Connection lost during download. Please check your internet connection and try again. File added File modified File deleted