Multiple blame improvements
- Clicking on a diff now minimizes the blame - Clicking on a commit of the blame will select this commit in the log and show the commit changes. - Unified design of Diff and Blame
This commit is contained in:
parent
cb3fe17fee
commit
eca68aaf07
@ -1,10 +1,10 @@
|
|||||||
@file:Suppress("UNUSED_PARAMETER")
|
|
||||||
|
|
||||||
package app.ui
|
package app.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
@ -18,13 +18,16 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import app.extensions.lineAt
|
import app.extensions.lineAt
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.ui.components.PrimaryButton
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import org.eclipse.jgit.blame.BlameResult
|
import org.eclipse.jgit.blame.BlameResult
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Blame(
|
fun Blame(
|
||||||
filePath: String,
|
filePath: String,
|
||||||
blameResult: BlameResult,
|
blameResult: BlameResult,
|
||||||
|
onSelectCommit: (RevCommit) -> Unit,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
@ -45,7 +48,11 @@ fun Blame(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.width(200.dp).fillMaxHeight().background(MaterialTheme.colors.surface),
|
modifier = Modifier
|
||||||
|
.width(200.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(MaterialTheme.colors.surface)
|
||||||
|
.clickable { onSelectCommit(commit) },
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -77,13 +84,68 @@ fun Blame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MinimizedBlame(
|
||||||
|
filePath: String,
|
||||||
|
onExpand: () -> Unit,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(50.dp)
|
||||||
|
.background(MaterialTheme.colors.surface),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Minimized file blame",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = filePath,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
onClick = onExpand,
|
||||||
|
text = "Show",
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onClose,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource("close.svg"),
|
||||||
|
contentDescription = "Close blame",
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryTextColor),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Header(
|
private fun Header(
|
||||||
filePath: String,
|
filePath: String,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().background(MaterialTheme.colors.surface),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(50.dp)
|
||||||
|
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
|
||||||
|
.background(MaterialTheme.colors.surface),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -94,12 +156,13 @@ private fun Header(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClose
|
onClick = onClose
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource("close.svg"),
|
painter = painterResource("close.svg"),
|
||||||
contentDescription = "Close diff",
|
contentDescription = "Close blame",
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryTextColor),
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryTextColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,6 @@ fun Diff(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@ -228,7 +227,7 @@ fun HunkHeader(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.surface)
|
.background(MaterialTheme.colors.surface)
|
||||||
.padding(vertical = 4.dp)
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@ -282,6 +281,8 @@ fun DiffHeader(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.height(50.dp)
|
||||||
|
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
|
||||||
.background(MaterialTheme.colors.surface),
|
.background(MaterialTheme.colors.surface),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@ -351,6 +352,7 @@ fun DiffLine(highestLineNumberLength: Int, line: Line) {
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.height(IntrinsicSize.Min)
|
.height(IntrinsicSize.Min)
|
||||||
) {
|
) {
|
||||||
|
@ -118,13 +118,16 @@ fun RepoContent(
|
|||||||
shape = RoundedCornerShape(4.dp)
|
shape = RoundedCornerShape(4.dp)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (blameState is BlameState.Loaded) {
|
if (blameState is BlameState.Loaded && !blameState.isMinimized) {
|
||||||
Blame(
|
Blame(
|
||||||
filePath = blameState.filePath,
|
filePath = blameState.filePath,
|
||||||
blameResult = blameState.blameResult,
|
blameResult = blameState.blameResult,
|
||||||
onClose = { tabViewModel.resetBlameState() }
|
onClose = { tabViewModel.resetBlameState() },
|
||||||
|
onSelectCommit = { tabViewModel.selectCommit(it) }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
Column {
|
||||||
|
Box(modifier = Modifier.weight(1f, true)) {
|
||||||
when (diffSelected) {
|
when (diffSelected) {
|
||||||
null -> {
|
null -> {
|
||||||
Log(
|
Log(
|
||||||
@ -140,6 +143,16 @@ fun RepoContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (blameState is BlameState.Loaded) { // BlameState.isMinimized is true here
|
||||||
|
MinimizedBlame(
|
||||||
|
filePath = blameState.filePath,
|
||||||
|
onExpand = { tabViewModel.expandBlame() },
|
||||||
|
onClose = { tabViewModel.resetBlameState() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,10 +168,7 @@ fun RepoContent(
|
|||||||
selectedEntryType = diffSelected,
|
selectedEntryType = diffSelected,
|
||||||
repositoryState = repositoryState,
|
repositoryState = repositoryState,
|
||||||
onStagedDiffEntrySelected = { diffEntry ->
|
onStagedDiffEntrySelected = { diffEntry ->
|
||||||
// TODO: Instead of resetting the state, create a new one where the blame
|
tabViewModel.minimizeBlame()
|
||||||
// is "on hold". In this state we can show a bar at the bottom so the user
|
|
||||||
// can click on it and return to the blame
|
|
||||||
tabViewModel.resetBlameState()
|
|
||||||
|
|
||||||
tabViewModel.newDiffSelected = if (diffEntry != null) {
|
tabViewModel.newDiffSelected = if (diffEntry != null) {
|
||||||
if (repositoryState == RepositoryState.SAFE)
|
if (repositoryState == RepositoryState.SAFE)
|
||||||
@ -170,7 +180,7 @@ fun RepoContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUnstagedDiffEntrySelected = { diffEntry ->
|
onUnstagedDiffEntrySelected = { diffEntry ->
|
||||||
tabViewModel.resetBlameState()
|
tabViewModel.minimizeBlame()
|
||||||
|
|
||||||
if (repositoryState == RepositoryState.SAFE)
|
if (repositoryState == RepositoryState.SAFE)
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
|
||||||
@ -185,7 +195,7 @@ fun RepoContent(
|
|||||||
selectedItem = safeSelectedItem,
|
selectedItem = safeSelectedItem,
|
||||||
diffSelected = diffSelected,
|
diffSelected = diffSelected,
|
||||||
onDiffSelected = { diffEntry ->
|
onDiffSelected = { diffEntry ->
|
||||||
tabViewModel.resetBlameState()
|
tabViewModel.minimizeBlame()
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
||||||
},
|
},
|
||||||
onBlame = { tabViewModel.blameFile(it) }
|
onBlame = { tabViewModel.blameFile(it) }
|
||||||
|
@ -17,6 +17,7 @@ import org.eclipse.jgit.api.Git
|
|||||||
import org.eclipse.jgit.blame.BlameResult
|
import org.eclipse.jgit.blame.BlameResult
|
||||||
import org.eclipse.jgit.lib.Repository
|
import org.eclipse.jgit.lib.Repository
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@ -340,6 +341,26 @@ class TabViewModel @Inject constructor(
|
|||||||
fun resetBlameState() {
|
fun resetBlameState() {
|
||||||
_blameState.value = BlameState.None
|
_blameState.value = BlameState.None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun expandBlame() {
|
||||||
|
val blameState = _blameState.value
|
||||||
|
|
||||||
|
if(blameState is BlameState.Loaded && blameState.isMinimized) {
|
||||||
|
_blameState.value = blameState.copy(isMinimized = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun minimizeBlame() {
|
||||||
|
val blameState = _blameState.value
|
||||||
|
|
||||||
|
if(blameState is BlameState.Loaded && !blameState.isMinimized) {
|
||||||
|
_blameState.value = blameState.copy(isMinimized = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectCommit(commit: RevCommit) {
|
||||||
|
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -352,6 +373,8 @@ sealed class RepositorySelectionStatus {
|
|||||||
|
|
||||||
sealed interface BlameState {
|
sealed interface BlameState {
|
||||||
data class Loading(val filePath: String) : BlameState
|
data class Loading(val filePath: String) : BlameState
|
||||||
data class Loaded(val filePath: String, val blameResult: BlameResult) : BlameState
|
|
||||||
|
data class Loaded(val filePath: String, val blameResult: BlameResult, val isMinimized: Boolean = false) : BlameState
|
||||||
|
|
||||||
object None : BlameState
|
object None : BlameState
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user