Gitnuro/src/main/kotlin/app/ui/dialogs/CloneDialog.kt
2022-06-06 14:58:53 +02:00

283 lines
9.1 KiB
Kotlin

package app.ui.dialogs
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.outlinedTextFieldColors
import app.theme.primaryTextColor
import app.theme.textButtonColors
import app.ui.components.PrimaryButton
import app.viewmodels.CloneViewModel
import openDirectoryDialog
import java.io.File
@Composable
fun CloneDialog(
cloneViewModel: CloneViewModel,
onClose: () -> Unit,
onOpenRepository: (File) -> Unit,
) {
val cloneStatus = cloneViewModel.cloneStatus.collectAsState()
val cloneStatusValue = cloneStatus.value
MaterialDialog(onCloseRequested = onClose) {
Box(
modifier = Modifier
.width(400.dp)
.animateContentSize()
) {
when (cloneStatusValue) {
is CloneStatus.Cloning -> {
Cloning(cloneViewModel, cloneStatusValue)
}
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) }
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 new repository",
color = MaterialTheme.colors.primaryTextColor,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp, horizontal = 8.dp)
)
OutlinedTextField(
modifier = Modifier
.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,
colors = outlinedTextFieldColors(),
onValueChange = {
cloneViewModel.resetStateIfError()
url = it
cloneViewModel.url = url
}
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp, horizontal = 8.dp),
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,
colors = outlinedTextFieldColors(),
onValueChange = {
cloneViewModel.resetStateIfError()
directory = it
cloneViewModel.directory = directory
}
)
IconButton(
onClick = {
cloneViewModel.resetStateIfError()
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) {
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
.padding(top = 16.dp)
.align(Alignment.End)
) {
TextButton(
modifier = Modifier
.padding(end = 8.dp)
.focusOrder(cancelButtonFocusRequester) {
previous = cloneButtonFocusRequester
next = urlFocusRequester
},
colors = textButtonColors(),
onClick = {
onClose()
}
) {
Text("Cancel")
}
PrimaryButton(
onClick = {
cloneViewModel.clone(directory, url)
},
modifier = Modifier
.focusOrder(cloneButtonFocusRequester) {
previous = directoryButtonFocusRequester
next = cancelButtonFocusRequester
},
text = "Clone"
)
}
}
}
@Composable
private fun Cloning(cloneViewModel: CloneViewModel, cloneStatusValue: CloneStatus.Cloning) {
Column (
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val progress = remember(cloneStatusValue) {
val total = cloneStatusValue.total
if(total == 0) // Prevent division by 0
-1f
else
cloneStatusValue.progress / total.toFloat()
}
if(progress >= 0f)
CircularProgressIndicator(
modifier = Modifier.padding(vertical = 16.dp),
progress = progress
)
else // Show indeterminate if we do not know the total (aka total == 0 or progress == -1)
CircularProgressIndicator(
modifier = Modifier.padding(vertical = 16.dp),
)
Text(cloneStatusValue.taskName, color = MaterialTheme.colors.primaryTextColor)
TextButton(
modifier = Modifier
.padding(
top = 36.dp,
end = 8.dp
)
.align(Alignment.End),
colors = textButtonColors(),
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),
)
}
}