367 lines
12 KiB
Kotlin
367 lines
12 KiB
Kotlin
package com.jetpackduba.gitnuro.ui.dialogs
|
|
|
|
import androidx.compose.animation.AnimatedVisibility
|
|
import androidx.compose.animation.animateContentSize
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
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.FocusProperties
|
|
import androidx.compose.ui.focus.FocusRequester
|
|
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
|
|
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
|
|
|
import com.jetpackduba.gitnuro.theme.textButtonColors
|
|
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
|
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
|
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
|
import com.jetpackduba.gitnuro.viewmodels.CloneViewModel
|
|
import java.io.File
|
|
|
|
@Composable
|
|
fun CloneDialog(
|
|
cloneViewModel: CloneViewModel = gitnuroViewModel(),
|
|
onClose: () -> Unit,
|
|
onOpenRepository: (File) -> Unit,
|
|
) {
|
|
val cloneStatus = cloneViewModel.cloneState.collectAsState()
|
|
val cloneStatusValue = cloneStatus.value
|
|
|
|
MaterialDialog(
|
|
onCloseRequested = onClose,
|
|
background = MaterialTheme.colors.surface,
|
|
) {
|
|
Box(
|
|
modifier = Modifier
|
|
.width(720.dp)
|
|
.animateContentSize()
|
|
) {
|
|
when (cloneStatusValue) {
|
|
is CloneState.Cloning -> {
|
|
Cloning(cloneViewModel, cloneStatusValue)
|
|
}
|
|
|
|
is CloneState.Cancelling -> {
|
|
Cancelling()
|
|
}
|
|
|
|
is CloneState.Completed -> {
|
|
onOpenRepository(cloneStatusValue.repoDir)
|
|
onClose()
|
|
}
|
|
|
|
is CloneState.Fail, CloneState.None -> CloneDialogView(
|
|
cloneViewModel = cloneViewModel,
|
|
onClose = onClose,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun CloneDialogView(
|
|
cloneViewModel: CloneViewModel,
|
|
onClose: () -> Unit,
|
|
) {
|
|
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() }
|
|
val cloneButtonFocusRequester = remember { FocusRequester() }
|
|
val cancelButtonFocusRequester = remember { FocusRequester() }
|
|
|
|
Column(
|
|
modifier = Modifier.fillMaxWidth()
|
|
.padding(horizontal = 8.dp)
|
|
) {
|
|
Text(
|
|
"Clone a new repository",
|
|
style = MaterialTheme.typography.h3,
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(vertical = 4.dp)
|
|
)
|
|
|
|
TextInput(
|
|
modifier = Modifier.padding(top = 8.dp),
|
|
title = "URL",
|
|
value = url,
|
|
focusRequester = urlFocusRequester,
|
|
focusProperties = {
|
|
previous = cancelButtonFocusRequester
|
|
next = directoryFocusRequester
|
|
},
|
|
onValueChange = { repositoryUrl ->
|
|
url = repositoryUrl
|
|
cloneViewModel.onRepositoryUrlChanged(repositoryUrl)
|
|
cloneViewModel.resetStateIfError()
|
|
}
|
|
)
|
|
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
) {
|
|
|
|
TextInput(
|
|
modifier = Modifier.padding(top = 16.dp),
|
|
title = "Directory",
|
|
value = directory,
|
|
focusRequester = directoryFocusRequester,
|
|
focusProperties = {
|
|
previous = urlFocusRequester
|
|
next = directoryButtonFocusRequester
|
|
},
|
|
onValueChange = {
|
|
directory = it
|
|
cloneViewModel.onDirectoryPathChanged(directory)
|
|
cloneViewModel.resetStateIfError()
|
|
},
|
|
)
|
|
|
|
IconButton(
|
|
onClick = {
|
|
cloneViewModel.resetStateIfError()
|
|
val newDirectory = cloneViewModel.openDirectoryPicker()
|
|
if (newDirectory != null) {
|
|
directory = TextFieldValue(newDirectory, selection = TextRange(newDirectory.count()))
|
|
cloneViewModel.onDirectoryPathChanged(directory)
|
|
cloneViewModel.resetStateIfError()
|
|
}
|
|
},
|
|
modifier = Modifier
|
|
.focusRequester(directoryButtonFocusRequester)
|
|
.focusProperties {
|
|
previous = directoryFocusRequester
|
|
next = cloneButtonFocusRequester
|
|
}
|
|
.padding(start = 8.dp)
|
|
.clip(RoundedCornerShape(4.dp))
|
|
.background(MaterialTheme.colors.primary)
|
|
.height(40.dp),
|
|
) {
|
|
Icon(
|
|
Icons.Default.Search,
|
|
contentDescription = null,
|
|
tint = MaterialTheme.colors.onPrimary,
|
|
)
|
|
}
|
|
}
|
|
|
|
Row(
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
modifier = Modifier.handMouseClickable(
|
|
interactionSource = remember { MutableInteractionSource() },
|
|
indication = null,
|
|
) {
|
|
cloneSubmodules = !cloneSubmodules
|
|
}
|
|
.fillMaxWidth()
|
|
.padding(top = 16.dp)
|
|
) {
|
|
Checkbox(
|
|
checked = cloneSubmodules,
|
|
onCheckedChange = {
|
|
cloneSubmodules = it
|
|
},
|
|
modifier = Modifier
|
|
.padding(all = 8.dp)
|
|
.size(12.dp)
|
|
)
|
|
|
|
Text(
|
|
"Clone submodules recursively",
|
|
style = MaterialTheme.typography.body2,
|
|
color = MaterialTheme.colors.onBackground,
|
|
)
|
|
}
|
|
|
|
AnimatedVisibility (error.isNotBlank()) {
|
|
Box(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(vertical = 4.dp)
|
|
.clip(RoundedCornerShape(4.dp))
|
|
.background(MaterialTheme.colors.error)
|
|
) {
|
|
Text(
|
|
error,
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(vertical = 4.dp, horizontal = 8.dp),
|
|
color = MaterialTheme.colors.onError,
|
|
)
|
|
}
|
|
}
|
|
|
|
Row(
|
|
modifier = Modifier
|
|
.padding(top = 16.dp)
|
|
.align(Alignment.End)
|
|
) {
|
|
PrimaryButton(
|
|
text = "Cancel",
|
|
modifier = Modifier.padding(end = 8.dp)
|
|
.focusRequester(cancelButtonFocusRequester)
|
|
.focusProperties {
|
|
previous = cloneButtonFocusRequester
|
|
next = urlFocusRequester
|
|
},
|
|
onClick = onClose,
|
|
backgroundColor = Color.Transparent,
|
|
textColor = MaterialTheme.colors.onBackground,
|
|
)
|
|
PrimaryButton(
|
|
onClick = {
|
|
cloneViewModel.clone(directory.text, url.text, cloneSubmodules)
|
|
},
|
|
modifier = Modifier
|
|
.focusRequester(cloneButtonFocusRequester)
|
|
.focusProperties {
|
|
previous = directoryButtonFocusRequester
|
|
next = cancelButtonFocusRequester
|
|
},
|
|
text = "Clone"
|
|
)
|
|
}
|
|
|
|
LaunchedEffect(Unit) {
|
|
urlFocusRequester.requestFocus()
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun Cloning(cloneViewModel: CloneViewModel, cloneStateValue: CloneState.Cloning) {
|
|
Box(
|
|
modifier = Modifier
|
|
.fillMaxWidth(),
|
|
) {
|
|
val progress = remember(cloneStateValue) {
|
|
val total = cloneStateValue.total
|
|
|
|
if (total == 0) // Prevent division by 0
|
|
-1f
|
|
else
|
|
cloneStateValue.progress / total.toFloat()
|
|
}
|
|
|
|
Column(
|
|
modifier = Modifier
|
|
.align(Alignment.Center),
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
) {
|
|
|
|
Text(cloneStateValue.taskName, color = MaterialTheme.colors.onBackground)
|
|
|
|
if (progress >= 0f)
|
|
CircularProgressIndicator(
|
|
modifier = Modifier
|
|
.padding(vertical = 16.dp),
|
|
progress = progress,
|
|
color = MaterialTheme.colors.primaryVariant,
|
|
)
|
|
else // Show indeterminate if we do not know the total (aka total == 0 or progress == -1)
|
|
CircularProgressIndicator(
|
|
modifier = Modifier
|
|
.padding(vertical = 16.dp),
|
|
color = MaterialTheme.colors.primaryVariant,
|
|
)
|
|
|
|
}
|
|
|
|
TextButton(
|
|
modifier = Modifier
|
|
.padding(end = 8.dp)
|
|
.align(Alignment.BottomEnd),
|
|
colors = textButtonColors(),
|
|
onClick = {
|
|
cloneViewModel.cancelClone()
|
|
}
|
|
) {
|
|
Text(
|
|
text = "Cancel",
|
|
color = MaterialTheme.colors.onBackground,
|
|
style = MaterialTheme.typography.body1,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun Cancelling() {
|
|
Column(
|
|
modifier = Modifier
|
|
.fillMaxWidth(),
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
verticalArrangement = Arrangement.Center,
|
|
) {
|
|
CircularProgressIndicator(
|
|
modifier = Modifier.padding(horizontal = 16.dp),
|
|
color = MaterialTheme.colors.primaryVariant,
|
|
)
|
|
|
|
Text(
|
|
text = "Cancelling clone operation...",
|
|
color = MaterialTheme.colors.onBackground,
|
|
modifier = Modifier.padding(vertical = 16.dp),
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TextInput(
|
|
modifier: Modifier = Modifier,
|
|
title: String,
|
|
value: TextFieldValue,
|
|
enabled: Boolean = true,
|
|
focusRequester: FocusRequester,
|
|
focusProperties: FocusProperties.() -> Unit,
|
|
onValueChange: (TextFieldValue) -> Unit,
|
|
textFieldShape: Shape = RoundedCornerShape(4.dp),
|
|
) {
|
|
Column(
|
|
modifier = modifier,
|
|
) {
|
|
Text(
|
|
text = title,
|
|
style = MaterialTheme.typography.body1,
|
|
modifier = Modifier
|
|
.padding(bottom = 8.dp),
|
|
)
|
|
|
|
AdjustableOutlinedTextField(
|
|
value = value,
|
|
modifier = Modifier
|
|
.focusRequester(focusRequester)
|
|
.focusProperties(focusProperties),
|
|
enabled = enabled,
|
|
onValueChange = onValueChange,
|
|
colors = outlinedTextFieldColors(),
|
|
singleLine = true,
|
|
shape = textFieldShape,
|
|
)
|
|
}
|
|
} |