Fixed UX issues with clone dialog

This commit is contained in:
Abdelilah El Aissaoui 2023-04-30 15:45:41 +02:00
parent 73816089a6
commit 2e825be44b
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
2 changed files with 53 additions and 39 deletions

View File

@ -19,6 +19,8 @@ import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.extensions.handMouseClickable
import com.jetpackduba.gitnuro.git.CloneState
@ -63,13 +65,7 @@ fun CloneDialog(
onClose()
}
is CloneState.Fail -> CloneInput(
cloneViewModel = cloneViewModel,
onClose = onClose,
errorMessage = cloneStatusValue.reason
)
CloneState.None -> CloneInput(
is CloneState.Fail, CloneState.None -> CloneDialogView(
cloneViewModel = cloneViewModel,
onClose = onClose,
)
@ -79,15 +75,16 @@ fun CloneDialog(
}
@Composable
private fun CloneInput(
private fun CloneDialogView(
cloneViewModel: CloneViewModel,
onClose: () -> Unit,
errorMessage: String? = null,
) {
var url by remember { mutableStateOf(cloneViewModel.url) }
var directory by remember { mutableStateOf(cloneViewModel.directory) }
var url by remember(cloneViewModel) { mutableStateOf(cloneViewModel.repositoryUrl.value) }
var directory by remember(cloneViewModel) { mutableStateOf(cloneViewModel.directoryPath.value) }
var cloneSubmodules by remember { mutableStateOf(true) }
val error by cloneViewModel.error.collectAsState()
val urlFocusRequester = remember { FocusRequester() }
val directoryFocusRequester = remember { FocusRequester() }
val directoryButtonFocusRequester = remember { FocusRequester() }
@ -115,10 +112,10 @@ private fun CloneInput(
previous = cancelButtonFocusRequester
next = directoryFocusRequester
},
onValueChange = {
onValueChange = { repositoryUrl ->
url = repositoryUrl
cloneViewModel.onRepositoryUrlChanged(repositoryUrl)
cloneViewModel.resetStateIfError()
url = it
cloneViewModel.url = url
}
)
@ -138,9 +135,9 @@ private fun CloneInput(
next = directoryButtonFocusRequester
},
onValueChange = {
cloneViewModel.resetStateIfError()
directory = it
cloneViewModel.directory = directory
cloneViewModel.onDirectoryPathChanged(directory)
cloneViewModel.resetStateIfError()
},
)
@ -149,8 +146,9 @@ private fun CloneInput(
cloneViewModel.resetStateIfError()
val newDirectory = cloneViewModel.openDirectoryPicker()
if (newDirectory != null) {
directory = newDirectory
cloneViewModel.directory = directory
directory = TextFieldValue(newDirectory, selection = TextRange(newDirectory.count()))
cloneViewModel.onDirectoryPathChanged(directory)
cloneViewModel.resetStateIfError()
}
},
modifier = Modifier
@ -200,7 +198,7 @@ private fun CloneInput(
)
}
AnimatedVisibility (errorMessage != null) {
AnimatedVisibility (error.isNotBlank()) {
Box(
modifier = Modifier
.fillMaxWidth()
@ -209,7 +207,7 @@ private fun CloneInput(
.background(MaterialTheme.colors.error)
) {
Text(
errorMessage.orEmpty(),
error,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp, horizontal = 8.dp),
@ -237,7 +235,7 @@ private fun CloneInput(
)
PrimaryButton(
onClick = {
cloneViewModel.clone(directory, url, cloneSubmodules)
cloneViewModel.clone(directory.text, url.text, cloneSubmodules)
},
modifier = Modifier
.focusRequester(cloneButtonFocusRequester)
@ -248,6 +246,10 @@ private fun CloneInput(
text = "Clone"
)
}
LaunchedEffect(Unit) {
urlFocusRequester.requestFocus()
}
}
}
@ -333,11 +335,11 @@ private fun Cancelling() {
private fun TextInput(
modifier: Modifier = Modifier,
title: String,
value: String,
value: TextFieldValue,
enabled: Boolean = true,
focusRequester: FocusRequester,
focusProperties: FocusProperties.() -> Unit,
onValueChange: (String) -> Unit,
onValueChange: (TextFieldValue) -> Unit,
textFieldShape: Shape = RoundedCornerShape(4.dp),
) {
Column(

View File

@ -1,5 +1,6 @@
package com.jetpackduba.gitnuro.viewmodels
import androidx.compose.ui.text.input.TextFieldValue
import com.jetpackduba.gitnuro.git.CloneState
import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.remote_operations.CloneRepositoryUseCase
@ -9,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOn
import java.io.File
import javax.inject.Inject
@ -19,25 +20,29 @@ class CloneViewModel @Inject constructor(
private val cloneRepositoryUseCase: CloneRepositoryUseCase,
private val openFilePickerUseCase: OpenFilePickerUseCase,
) {
private val _repositoryUrl = MutableStateFlow(TextFieldValue(""))
val repositoryUrl = _repositoryUrl.asStateFlow()
private val _directoryPath = MutableStateFlow(TextFieldValue(""))
val directoryPath = _directoryPath.asStateFlow()
private val _cloneState = MutableStateFlow<CloneState>(CloneState.None)
val cloneState: StateFlow<CloneState>
get() = _cloneState
val cloneState = _cloneState.asStateFlow()
var url: String = ""
var directory: String = ""
private val _error = MutableStateFlow("")
val error = _error.asStateFlow()
private var cloneJob: Job? = null
fun clone(directoryPath: String, url: String, cloneSubmodules: Boolean) {
cloneJob = tabState.safeProcessingWithoutGit {
if (directoryPath.isBlank()) {
_cloneState.value = CloneState.Fail("Invalid empty directory")
_error.value = "Invalid empty directory"
return@safeProcessingWithoutGit
}
if (url.isBlank()) {
_cloneState.value = CloneState.Fail("Invalid empty URL")
_error.value = "Invalid empty URL"
return@safeProcessingWithoutGit
}
@ -57,7 +62,7 @@ class CloneViewModel @Inject constructor(
}
if (repoName.isNullOrBlank()) {
_cloneState.value = CloneState.Fail("Check your URL and try again")
_error.value = "Check your URL and try again"
return@safeProcessingWithoutGit
}
@ -75,17 +80,16 @@ class CloneViewModel @Inject constructor(
cloneRepositoryUseCase(repoDir, url, cloneSubmodules)
.flowOn(Dispatchers.IO)
.collect { newCloneStatus ->
_cloneState.value = newCloneStatus
if (newCloneStatus is CloneState.Fail) {
_error.value = newCloneStatus.reason
_cloneState.value = CloneState.None
} else {
_cloneState.value = newCloneStatus
}
}
}
}
fun reset() {
_cloneState.value = CloneState.None
url = ""
directory = ""
}
fun cancelClone() = tabState.safeProcessingWithoutGit {
_cloneState.value = CloneState.Cancelling
cloneJob?.cancelAndJoin()
@ -93,10 +97,18 @@ class CloneViewModel @Inject constructor(
}
fun resetStateIfError() {
_cloneState.value = CloneState.None
_error.value = ""
}
fun openDirectoryPicker(): String? {
return openFilePickerUseCase(PickerType.DIRECTORIES, null)
}
fun onDirectoryPathChanged(directory: TextFieldValue) {
_directoryPath.value = directory
}
fun onRepositoryUrlChanged(repositoryUrl: TextFieldValue) {
_repositoryUrl.value = repositoryUrl
}
}