Improved clone dialog features by adding more error management & directory picker
This commit is contained in:
parent
8a9c2d5fc3
commit
d662edba9d
@ -0,0 +1,3 @@
|
||||
package app.exceptions
|
||||
|
||||
class InvalidDirectoryException(msg: String) : GitnuroException(msg)
|
@ -4,11 +4,10 @@ import app.credentials.GSessionManager
|
||||
import app.credentials.HttpCredentialsProvider
|
||||
import app.extensions.remoteName
|
||||
import app.extensions.simpleName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.api.RebaseResult
|
||||
import org.eclipse.jgit.lib.ProgressMonitor
|
||||
@ -16,15 +15,12 @@ import org.eclipse.jgit.lib.Ref
|
||||
import org.eclipse.jgit.transport.*
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
|
||||
class RemoteOperationsManager @Inject constructor(
|
||||
private val sessionManager: GSessionManager
|
||||
) {
|
||||
private val _cloneStatus = MutableStateFlow<CloneStatus>(CloneStatus.None)
|
||||
val cloneStatus: StateFlow<CloneStatus>
|
||||
get() = _cloneStatus
|
||||
|
||||
suspend fun pull(git: Git, rebase: Boolean) = withContext(Dispatchers.IO) {
|
||||
val pullResult = git
|
||||
.pull()
|
||||
@ -226,59 +222,69 @@ class RemoteOperationsManager @Inject constructor(
|
||||
|
||||
}
|
||||
|
||||
suspend fun clone(directory: File, url: String) = withContext(Dispatchers.IO) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun clone(directory: File, url: String): Flow<CloneStatus> = callbackFlow {
|
||||
try {
|
||||
_cloneStatus.value = CloneStatus.Cloning(0)
|
||||
ensureActive()
|
||||
this.trySend(CloneStatus.Cloning(0))
|
||||
|
||||
Git.cloneRepository()
|
||||
.setDirectory(directory)
|
||||
.setURI(url)
|
||||
.setProgressMonitor(object : ProgressMonitor {
|
||||
override fun start(totalTasks: Int) {
|
||||
println("ProgressMonitor Start")
|
||||
}
|
||||
.setProgressMonitor(
|
||||
object : ProgressMonitor {
|
||||
override fun start(totalTasks: Int) {
|
||||
println("ProgressMonitor Start with total tasks of: $totalTasks")
|
||||
}
|
||||
|
||||
override fun beginTask(title: String?, totalWork: Int) {
|
||||
println("ProgressMonitor Begin task")
|
||||
}
|
||||
override fun beginTask(title: String?, totalWork: Int) {
|
||||
println("ProgressMonitor Begin task with title: $title")
|
||||
}
|
||||
|
||||
override fun update(completed: Int) {
|
||||
println("ProgressMonitor Update $completed")
|
||||
_cloneStatus.value = CloneStatus.Cloning(completed)
|
||||
}
|
||||
override fun update(completed: Int) {
|
||||
println("ProgressMonitor Update $completed")
|
||||
ensureActive()
|
||||
trySend(CloneStatus.Cloning(completed))
|
||||
}
|
||||
|
||||
override fun endTask() {
|
||||
println("ProgressMonitor End task")
|
||||
_cloneStatus.value = CloneStatus.CheckingOut
|
||||
}
|
||||
override fun endTask() {
|
||||
println("ProgressMonitor End task")
|
||||
ensureActive()
|
||||
trySend(CloneStatus.CheckingOut)
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return !isActive
|
||||
override fun isCancelled(): Boolean {
|
||||
return !isActive
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
)
|
||||
.setTransportConfigCallback {
|
||||
handleTransportCredentials(it)
|
||||
}
|
||||
.call()
|
||||
|
||||
_cloneStatus.value = CloneStatus.Completed
|
||||
ensureActive()
|
||||
trySend(CloneStatus.Completed(directory))
|
||||
channel.close()
|
||||
} catch (ex: Exception) {
|
||||
_cloneStatus.value = CloneStatus.Fail(ex.localizedMessage)
|
||||
if(ex.cause?.cause is CancellationException) {
|
||||
println("Clone cancelled")
|
||||
} else {
|
||||
trySend(CloneStatus.Fail(ex.localizedMessage))
|
||||
}
|
||||
|
||||
channel.close()
|
||||
}
|
||||
|
||||
awaitClose()
|
||||
}
|
||||
|
||||
fun resetCloneStatus() {
|
||||
_cloneStatus.value = CloneStatus.None
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
sealed class CloneStatus {
|
||||
object None : CloneStatus()
|
||||
data class Cloning(val progress: Int) : CloneStatus()
|
||||
object Cancelling : CloneStatus()
|
||||
object CheckingOut : CloneStatus()
|
||||
data class Fail(val reason: String) : CloneStatus()
|
||||
object Completed : CloneStatus()
|
||||
data class Completed(val repoDir: File) : CloneStatus()
|
||||
}
|
@ -85,14 +85,14 @@ class TabState @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun safeProcessingWihoutGit(showError: Boolean = true, callback: suspend () -> Unit) =
|
||||
fun safeProcessingWihoutGit(showError: Boolean = true, callback: suspend CoroutineScope.() -> Unit) =
|
||||
managerScope.launch(Dispatchers.IO) {
|
||||
mutex.withLock {
|
||||
_processing.value = true
|
||||
operationRunning = true
|
||||
|
||||
try {
|
||||
callback()
|
||||
this.callback()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
|
||||
|
@ -13,6 +13,7 @@ val secondaryTextDark = Color(0xFFCCCBCB)
|
||||
val borderColorLight = Color(0xFF989898)
|
||||
val borderColorDark = Color(0xFF989898)
|
||||
val errorColor = Color(0xFFc93838)
|
||||
val onErrorColor = Color(0xFFFFFFFF)
|
||||
|
||||
val backgroundColorLight = Color(0xFFFFFFFF)
|
||||
val backgroundColorDark = Color(0xFF0E1621)
|
||||
|
@ -14,7 +14,8 @@ private val DarkColorPalette = darkColors(
|
||||
secondary = secondary,
|
||||
surface = surfaceColorDark,
|
||||
background = backgroundColorDark,
|
||||
error = errorColor
|
||||
error = errorColor,
|
||||
onError = onErrorColor,
|
||||
)
|
||||
|
||||
private val LightColorPalette = lightColors(
|
||||
@ -23,10 +24,8 @@ private val LightColorPalette = lightColors(
|
||||
secondary = secondary,
|
||||
background = backgroundColorLight,
|
||||
surface = surfaceColorLight,
|
||||
error = errorColor
|
||||
/* Other default colors to override
|
||||
|
||||
*/
|
||||
error = errorColor,
|
||||
onError = onErrorColor,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.DefaultAlpha
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -121,12 +120,12 @@ fun AppTab(
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp),
|
||||
color = Color.White,
|
||||
color = MaterialTheme.colors.onError,
|
||||
) // TODO Add more descriptive title
|
||||
|
||||
Text(
|
||||
text = lastError?.message ?: "",
|
||||
color = Color.White,
|
||||
color = MaterialTheme.colors.onError,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp, bottom = 16.dp)
|
||||
.widthIn(max = 600.dp)
|
||||
|
@ -23,7 +23,6 @@ import app.extensions.dirPath
|
||||
import app.theme.primaryTextColor
|
||||
import app.theme.secondaryTextColor
|
||||
import app.ui.dialogs.CloneDialog
|
||||
import app.ui.dialogs.MaterialDialog
|
||||
import app.viewmodels.TabViewModel
|
||||
import openDirectoryDialog
|
||||
import openRepositoryDialog
|
||||
@ -90,7 +89,7 @@ fun WelcomePage(
|
||||
painter = painterResource("open.svg"),
|
||||
onClick = {
|
||||
val dir = openDirectoryDialog()
|
||||
if(dir != null)
|
||||
if (dir != null)
|
||||
tabViewModel.initLocalRepository(dir)
|
||||
}
|
||||
)
|
||||
@ -163,23 +162,23 @@ fun WelcomePage(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(showCloneView) {
|
||||
if (showCloneView)
|
||||
tabViewModel.cloneViewModel.reset() // Reset dialog before showing it
|
||||
}
|
||||
|
||||
|
||||
if (showCloneView)
|
||||
MaterialDialog {
|
||||
CloneDialog(tabViewModel, onClose = { showCloneView = false })
|
||||
}
|
||||
// Popup(focusable = true, onDismissRequest = { showCloneView = false }, alignment = Alignment.Center) {
|
||||
//
|
||||
// }
|
||||
|
||||
// PopupAlertDialogProvider.AlertDialog(onDismissRequest = {}) {
|
||||
//
|
||||
// CloneDialog(gitManager, onClose = { showCloneView = false })
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
CloneDialog(
|
||||
tabViewModel.cloneViewModel,
|
||||
onClose = {
|
||||
showCloneView = false
|
||||
tabViewModel.cloneViewModel.reset()
|
||||
},
|
||||
onOpenRepository = { dir ->
|
||||
tabViewModel.openRepository(dir)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -1,68 +1,183 @@
|
||||
package app.ui.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusOrder
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.git.CloneStatus
|
||||
import app.theme.primaryTextColor
|
||||
import app.viewmodels.TabViewModel
|
||||
import app.viewmodels.CloneViewModel
|
||||
import openDirectoryDialog
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
fun CloneDialog(
|
||||
gitManager: TabViewModel,
|
||||
onClose: () -> Unit
|
||||
cloneViewModel: CloneViewModel,
|
||||
onClose: () -> Unit,
|
||||
onOpenRepository: (File) -> Unit,
|
||||
) {
|
||||
val cloneStatus = gitManager.cloneStatus.collectAsState()
|
||||
val cloneStatus = cloneViewModel.cloneStatus.collectAsState()
|
||||
val cloneStatusValue = cloneStatus.value
|
||||
var directory by remember { mutableStateOf("") }
|
||||
var url by remember { mutableStateOf("") }
|
||||
Column {
|
||||
if (cloneStatusValue is CloneStatus.Cloning || cloneStatusValue == CloneStatus.CheckingOut)
|
||||
LinearProgressIndicator(modifier = Modifier.width(500.dp))
|
||||
else if (cloneStatusValue == CloneStatus.Completed) {
|
||||
gitManager.openRepository(directory)
|
||||
onClose()
|
||||
}
|
||||
|
||||
MaterialDialog {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(400.dp)
|
||||
.animateContentSize()
|
||||
) {
|
||||
when (cloneStatusValue) {
|
||||
CloneStatus.CheckingOut -> {
|
||||
Cloning(cloneViewModel)
|
||||
}
|
||||
is CloneStatus.Cloning -> {
|
||||
Cloning(cloneViewModel)
|
||||
}
|
||||
is CloneStatus.Cancelling -> {
|
||||
onClose()
|
||||
}
|
||||
is CloneStatus.Completed -> {
|
||||
onOpenRepository(cloneStatusValue.repoDir)
|
||||
onClose()
|
||||
}
|
||||
is CloneStatus.Fail -> CloneInput(
|
||||
cloneViewModel = cloneViewModel,
|
||||
onClose = onClose,
|
||||
errorMessage = cloneStatusValue.reason
|
||||
)
|
||||
CloneStatus.None -> CloneInput(
|
||||
cloneViewModel = cloneViewModel,
|
||||
onClose = onClose,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloneInput(
|
||||
cloneViewModel: CloneViewModel,
|
||||
onClose: () -> Unit,
|
||||
errorMessage: String? = null,
|
||||
) {
|
||||
var url by remember { mutableStateOf(cloneViewModel.url) }
|
||||
var directory by remember { mutableStateOf(cloneViewModel.directory) }
|
||||
var errorHasBeenNoticed by remember { mutableStateOf(false) }
|
||||
|
||||
val urlFocusRequester = remember { FocusRequester() }
|
||||
val directoryFocusRequester = remember { FocusRequester() }
|
||||
val directoryButtonFocusRequester = remember { FocusRequester() }
|
||||
val cloneButtonFocusRequester = remember { FocusRequester() }
|
||||
val cancelButtonFocusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
urlFocusRequester.requestFocus()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
"Clone a repository",
|
||||
"Clone a new repository",
|
||||
color = MaterialTheme.colors.primaryTextColor,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.width(400.dp)
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp),
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||
.focusOrder(urlFocusRequester) {
|
||||
previous = cancelButtonFocusRequester
|
||||
next = directoryFocusRequester
|
||||
},
|
||||
label = { Text("URL") },
|
||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||
maxLines = 1,
|
||||
value = url,
|
||||
onValueChange = {
|
||||
errorHasBeenNoticed = true
|
||||
url = it
|
||||
}
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.width(400.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp),
|
||||
label = { Text("Directory") },
|
||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||
maxLines = 1,
|
||||
value = directory,
|
||||
onValueChange = {
|
||||
directory = it
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 4.dp)
|
||||
.focusOrder(directoryFocusRequester) {
|
||||
previous = urlFocusRequester
|
||||
next = directoryButtonFocusRequester
|
||||
},
|
||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||
maxLines = 1,
|
||||
label = { Text("Directory") },
|
||||
value = directory,
|
||||
onValueChange = {
|
||||
errorHasBeenNoticed = true
|
||||
directory = it
|
||||
}
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
errorHasBeenNoticed = true
|
||||
val newDirectory = openDirectoryDialog()
|
||||
if (newDirectory != null)
|
||||
directory = newDirectory
|
||||
},
|
||||
modifier = Modifier
|
||||
.focusOrder(directoryButtonFocusRequester) {
|
||||
previous = directoryFocusRequester
|
||||
next = cloneButtonFocusRequester
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Search,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.primaryTextColor,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(errorMessage != null && !errorHasBeenNoticed) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(MaterialTheme.colors.error)
|
||||
) {
|
||||
Text(
|
||||
errorMessage.orEmpty(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp),
|
||||
color = MaterialTheme.colors.onError,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -70,7 +185,12 @@ fun CloneDialog(
|
||||
.align(Alignment.End)
|
||||
) {
|
||||
TextButton(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.focusOrder(cancelButtonFocusRequester) {
|
||||
previous = cloneButtonFocusRequester
|
||||
next = urlFocusRequester
|
||||
},
|
||||
onClick = {
|
||||
onClose()
|
||||
}
|
||||
@ -79,11 +199,61 @@ fun CloneDialog(
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
gitManager.clone(File(directory), url)
|
||||
}
|
||||
cloneViewModel.clone(directory, url)
|
||||
},
|
||||
modifier = Modifier
|
||||
.focusOrder(cloneButtonFocusRequester) {
|
||||
previous = directoryButtonFocusRequester
|
||||
next = cancelButtonFocusRequester
|
||||
}
|
||||
) {
|
||||
Text("Clone")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Cloning(cloneViewModel: CloneViewModel) {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
|
||||
CircularProgressIndicator(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 36.dp,
|
||||
end = 8.dp
|
||||
)
|
||||
.align(Alignment.End),
|
||||
onClick = {
|
||||
cloneViewModel.cancelClone()
|
||||
}
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Cancelling() {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Cancelling clone operation...",
|
||||
color = MaterialTheme.colors.primaryTextColor,
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
86
src/main/kotlin/app/viewmodels/CloneViewModel.kt
Normal file
86
src/main/kotlin/app/viewmodels/CloneViewModel.kt
Normal file
@ -0,0 +1,86 @@
|
||||
package app.viewmodels
|
||||
|
||||
import app.git.CloneStatus
|
||||
import app.git.RemoteOperationsManager
|
||||
import app.git.TabState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
class CloneViewModel @Inject constructor(
|
||||
private val tabState: TabState,
|
||||
private val remoteOperationsManager: RemoteOperationsManager,
|
||||
) {
|
||||
|
||||
private val _cloneStatus = MutableStateFlow<CloneStatus>(CloneStatus.None)
|
||||
val cloneStatus: StateFlow<CloneStatus>
|
||||
get() = _cloneStatus
|
||||
|
||||
var url: String = ""
|
||||
var directory: String = ""
|
||||
|
||||
private var cloneJob: Job? = null
|
||||
|
||||
fun clone(directoryPath: String, url: String) {
|
||||
cloneJob = tabState.safeProcessingWihoutGit {
|
||||
if (directoryPath.isBlank()) {
|
||||
_cloneStatus.value = CloneStatus.Fail("Check your URL and try again")
|
||||
return@safeProcessingWihoutGit
|
||||
}
|
||||
|
||||
if (url.isBlank()) {
|
||||
_cloneStatus.value = CloneStatus.Fail("Check your URL and try again")
|
||||
return@safeProcessingWihoutGit
|
||||
}
|
||||
|
||||
|
||||
val urlSplit = url.split("/", "\\").toMutableList()
|
||||
|
||||
// Removes the last element for URLs that end with "/" or "\" instead of the repo name like https://github.com/JetpackDuba/Gitnuro/
|
||||
if(urlSplit.isNotEmpty() && urlSplit.last().isBlank()) {
|
||||
urlSplit.removeLast()
|
||||
}
|
||||
|
||||
// Take the last element of the path/URL to generate obtain the repo name
|
||||
val repoName = urlSplit.lastOrNull()?.replace(".git", "")
|
||||
|
||||
if (repoName.isNullOrBlank()) {
|
||||
_cloneStatus.value = CloneStatus.Fail("Check your URL and try again")
|
||||
return@safeProcessingWihoutGit
|
||||
}
|
||||
|
||||
val directory = File(directoryPath)
|
||||
|
||||
if (!directory.exists()) {
|
||||
directory.mkdirs()
|
||||
}
|
||||
|
||||
val repoDir = File(directory, repoName)
|
||||
if (!repoDir.exists()) {
|
||||
repoDir.mkdir()
|
||||
}
|
||||
|
||||
remoteOperationsManager.clone(repoDir, url)
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collect { newCloneStatus ->
|
||||
_cloneStatus.value = newCloneStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
_cloneStatus.value = CloneStatus.None
|
||||
url = ""
|
||||
directory = ""
|
||||
}
|
||||
|
||||
fun cancelClone() {
|
||||
cloneJob?.cancel()
|
||||
_cloneStatus.value = CloneStatus.Cancelling
|
||||
}
|
||||
}
|
@ -19,6 +19,11 @@ import javax.inject.Inject
|
||||
|
||||
private const val MIN_TIME_IN_MS_BETWEEN_REFRESHES = 500L
|
||||
|
||||
/**
|
||||
* Contains all the information related to a tab and its subcomponents (smaller composables like the log, branches,
|
||||
* commit changes, etc.). It holds a reference to every view model because this class lives as long as the tab is open (survives
|
||||
* across full app recompositions), therefore, tab's content can be recreated with these view models.
|
||||
*/
|
||||
class TabViewModel @Inject constructor(
|
||||
val logViewModel: LogViewModel,
|
||||
val branchesViewModel: BranchesViewModel,
|
||||
@ -29,8 +34,8 @@ class TabViewModel @Inject constructor(
|
||||
val menuViewModel: MenuViewModel,
|
||||
val stashesViewModel: StashesViewModel,
|
||||
val commitChangesViewModel: CommitChangesViewModel,
|
||||
val cloneViewModel: CloneViewModel,
|
||||
private val repositoryManager: RepositoryManager,
|
||||
private val remoteOperationsManager: RemoteOperationsManager,
|
||||
private val tabState: TabState,
|
||||
val appStateManager: AppStateManager,
|
||||
private val fileChangesWatcher: FileChangesWatcher,
|
||||
@ -47,7 +52,6 @@ class TabViewModel @Inject constructor(
|
||||
val processing: StateFlow<Boolean> = tabState.processing
|
||||
|
||||
val credentialsState: StateFlow<CredentialsState> = credentialsStateManager.credentialsState
|
||||
val cloneStatus: StateFlow<CloneStatus> = remoteOperationsManager.cloneStatus
|
||||
|
||||
private val _diffSelected = MutableStateFlow<DiffEntryType?>(null)
|
||||
val diffSelected: StateFlow<DiffEntryType?> = _diffSelected
|
||||
@ -243,10 +247,6 @@ class TabViewModel @Inject constructor(
|
||||
tabState.managerScope.cancel()
|
||||
}
|
||||
|
||||
fun clone(directory: File, url: String) = tabState.safeProcessingWihoutGit {
|
||||
remoteOperationsManager.clone(directory, url)
|
||||
}
|
||||
|
||||
private fun updateDiffEntry() {
|
||||
val diffSelected = diffSelected.value
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user