Gitnuro/src/main/kotlin/app/ui/UncommitedChanges.kt
2021-11-05 04:33:07 +01:00

314 lines
9.7 KiB
Kotlin

@file:Suppress("UNUSED_PARAMETER")
package app.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.ui.components.ScrollableLazyColumn
import app.extensions.filePath
import app.extensions.icon
import app.extensions.iconColor
import app.git.GitManager
import app.git.StageStatus
import org.eclipse.jgit.diff.DiffEntry
import app.theme.headerBackground
import app.theme.headerText
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun UncommitedChanges(
gitManager: GitManager,
onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
) {
val stageStatusState = gitManager.stageStatus.collectAsState()
val stageStatus = stageStatusState.value
val lastCheck by gitManager.lastTimeChecked.collectAsState()
LaunchedEffect(lastCheck) {
gitManager.loadStatus()
}
val (staged, unstaged) = if (stageStatus is StageStatus.Loaded) {
stageStatus.staged to stageStatus.unstaged
} else {
listOf<DiffEntry>() to listOf<DiffEntry>() // return 2 empty lists if still loading
}
var commitMessage by remember { mutableStateOf("") }
Column {
AnimatedVisibility(
visible = stageStatus is StageStatus.Loading,
enter = fadeIn(),
exit = fadeOut(),
) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
EntriesList(
modifier = Modifier
.padding(8.dp)
.weight(5f)
.fillMaxWidth(),
title = "Staged",
allActionTitle = "Unstage all",
actionTitle = "Unstage",
actionColor = MaterialTheme.colors.error,
diffEntries = staged,
onDiffEntrySelected = onStagedDiffEntrySelected,
onDiffEntryOptionSelected = {
gitManager.unstage(it)
},
onReset = { diffEntry ->
gitManager.resetStaged(diffEntry)
},
onAllAction = {
gitManager.unstageAll()
}
)
EntriesList(
modifier = Modifier
.padding(8.dp)
.weight(5f)
.fillMaxWidth(),
title = "Unstaged",
actionTitle = "Stage",
actionColor = MaterialTheme.colors.primary,
diffEntries = unstaged,
onDiffEntrySelected = onUnstagedDiffEntrySelected,
onDiffEntryOptionSelected = {
gitManager.stage(it)
},
onReset = { diffEntry ->
gitManager.resetUnstaged(diffEntry)
},
{
gitManager.stageAll()
},
allActionTitle = "Stage all"
)
Card(
modifier = Modifier
.padding(8.dp)
.height(192.dp)
.fillMaxWidth()
) {
Column(
modifier = Modifier
.fillMaxSize()
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.weight(weight = 1f, fill = true),
value = commitMessage,
onValueChange = { commitMessage = it },
label = { Text("Write your commit message here", fontSize = 14.sp) },
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.surface),
textStyle = TextStyle.Default.copy(fontSize = 14.sp)
)
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
gitManager.commit(commitMessage)
commitMessage = ""
onStagedDiffEntrySelected(null)
},
enabled = commitMessage.isNotEmpty() && staged.isNotEmpty(),
shape = RectangleShape,
) {
Text(
text = "Commit",
fontSize = 14.sp,
)
}
}
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun EntriesList(
modifier: Modifier,
title: String,
actionTitle: String,
actionColor: Color,
diffEntries: List<DiffEntry>,
onDiffEntrySelected: (DiffEntry) -> Unit,
onDiffEntryOptionSelected: (DiffEntry) -> Unit,
onReset: (DiffEntry) -> Unit,
onAllAction: () -> Unit,
allActionTitle: String,
) {
Card(
modifier = modifier
) {
Column {
Box {
Text(
modifier = Modifier
.background(color = MaterialTheme.colors.headerBackground)
.padding(vertical = 8.dp)
.fillMaxWidth(),
text = title,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
color = MaterialTheme.colors.headerText,
fontSize = 14.sp,
maxLines = 1,
)
Button(
onClick = onAllAction,
modifier = Modifier
.padding(horizontal = 16.dp)
.align(Alignment.CenterEnd),
elevation = ButtonDefaults.elevation(0.dp, 0.dp, 0.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = actionColor)
) {
Text(allActionTitle, fontSize = 10.sp)
}
}
ScrollableLazyColumn(
modifier = Modifier.fillMaxSize(),
) {
itemsIndexed(diffEntries) { index, diffEntry ->
FileEntry(
diffEntry = diffEntry,
actionTitle = actionTitle,
actionColor = actionColor,
onClick = {
onDiffEntrySelected(diffEntry)
},
onButtonClick = {
onDiffEntryOptionSelected(diffEntry)
},
onReset = {
onReset(diffEntry)
}
)
if (index < diffEntries.size - 1) {
Divider(modifier = Modifier.fillMaxWidth())
}
}
}
}
}
}
@OptIn(
ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class,
ExperimentalAnimationApi::class
)
@Composable
private fun FileEntry(
diffEntry: DiffEntry,
actionTitle: String,
actionColor: Color,
onClick: () -> Unit,
onButtonClick: () -> Unit,
onReset: () -> Unit,
) {
var active by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.clickable { onClick() }
.fillMaxWidth()
.pointerMoveFilter(
onEnter = {
active = true
false
},
onExit = {
active = false
false
}
)
) {
ContextMenuArea(
items = {
listOf(
ContextMenuItem(
label = "Reset",
onClick = onReset
)
)
},
) {
Row(
modifier = Modifier
.height(48.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = diffEntry.icon,
contentDescription = null,
modifier = Modifier
.padding(horizontal = 8.dp)
.size(16.dp),
tint = diffEntry.iconColor,
)
Text(
text = diffEntry.filePath,
modifier = Modifier.weight(1f, fill = true),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 14.sp,
)
}
}
AnimatedVisibility(
modifier = Modifier
.align(Alignment.CenterEnd),
visible = active,
enter = fadeIn(),
exit = fadeOut(),
) {
Button(
onClick = onButtonClick,
modifier = Modifier
.padding(horizontal = 16.dp),
elevation = ButtonDefaults.elevation(0.dp, 0.dp, 0.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = actionColor)
) {
Text(actionTitle, fontSize = 12.sp)
}
}
}
}