Reimplemented log view. Now graph can be horizontally scrolled
This commit is contained in:
parent
02313fe632
commit
7e86e3b2fd
@ -0,0 +1,9 @@
|
|||||||
|
package app.extensions
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
|
||||||
|
fun LazyListState.observeScrollChanges() {
|
||||||
|
// When accessing this property, this parent composable is recomposed when the scroll changes
|
||||||
|
// because LazyListState is marked with @Stable
|
||||||
|
this.firstVisibleItemScrollOffset
|
||||||
|
}
|
@ -7,6 +7,8 @@ import androidx.compose.foundation.gestures.Orientation
|
|||||||
import androidx.compose.foundation.gestures.draggable
|
import androidx.compose.foundation.gestures.draggable
|
||||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@ -37,6 +39,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import app.extensions.*
|
import app.extensions.*
|
||||||
import app.git.StatusSummary
|
import app.git.StatusSummary
|
||||||
|
import app.git.graph.GraphCommitList
|
||||||
import app.git.graph.GraphNode
|
import app.git.graph.GraphNode
|
||||||
import app.theme.*
|
import app.theme.*
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
@ -61,9 +64,9 @@ private val colors = listOf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private const val CANVAS_MIN_WIDTH = 100
|
private const val CANVAS_MIN_WIDTH = 100
|
||||||
|
private const val PADDING_BETWEEN_DIVIDER_AND_MESSAGE = 8
|
||||||
|
|
||||||
// TODO Min size for message column
|
// TODO Min size for message column
|
||||||
// TODO Horizontal scroll for the graph
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalFoundationApi::class,
|
ExperimentalFoundationApi::class,
|
||||||
ExperimentalComposeUiApi::class
|
ExperimentalComposeUiApi::class
|
||||||
@ -87,7 +90,9 @@ fun Log(
|
|||||||
if (logStatus is LogStatus.Loaded) {
|
if (logStatus is LogStatus.Loaded) {
|
||||||
val hasUncommitedChanges = logStatus.hasUncommitedChanges
|
val hasUncommitedChanges = logStatus.hasUncommitedChanges
|
||||||
val commitList = logStatus.plotCommitList
|
val commitList = logStatus.plotCommitList
|
||||||
val scrollState = rememberLazyListState()
|
val verticalScrollState = rememberLazyListState()
|
||||||
|
verticalScrollState.isScrollInProgress
|
||||||
|
verticalScrollState.observeScrollChanges()
|
||||||
|
|
||||||
LaunchedEffect(selectedCommit) {
|
LaunchedEffect(selectedCommit) {
|
||||||
// Scroll to commit if a Ref is selected
|
// Scroll to commit if a Ref is selected
|
||||||
@ -97,7 +102,7 @@ fun Log(
|
|||||||
// Index can be -1 if the ref points to a commit that is not shown in the graph due to the limited
|
// Index can be -1 if the ref points to a commit that is not shown in the graph due to the limited
|
||||||
// number of displayed commits.
|
// number of displayed commits.
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
scrollState.scrollToItem(index)
|
verticalScrollState.scrollToItem(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,46 +128,175 @@ fun Log(
|
|||||||
graphWidth = graphWidth,
|
graphWidth = graphWidth,
|
||||||
weightMod = weightMod,
|
weightMod = weightMod,
|
||||||
)
|
)
|
||||||
ScrollableLazyColumn(
|
|
||||||
|
val horizontalScrollState = rememberScrollState(0)
|
||||||
|
Box {
|
||||||
|
GraphList(
|
||||||
|
commitList = commitList,
|
||||||
|
stateHorizontal = horizontalScrollState,
|
||||||
|
graphWidth = graphWidth,
|
||||||
|
scrollState = verticalScrollState,
|
||||||
|
hasUncommitedChanges = hasUncommitedChanges,
|
||||||
|
)
|
||||||
|
|
||||||
|
MessagesList(
|
||||||
|
scrollState = verticalScrollState,
|
||||||
|
hasUncommitedChanges = hasUncommitedChanges,
|
||||||
|
selectedCommit = selectedCommit,
|
||||||
|
logStatus = logStatus,
|
||||||
|
repositoryState = repositoryState,
|
||||||
|
selectedItem = selectedItem,
|
||||||
|
commitList = commitList,
|
||||||
|
logViewModel = logViewModel,
|
||||||
|
graphWidth = graphWidth,
|
||||||
|
onShowLogDialog = { dialog ->
|
||||||
|
showLogDialog.value = dialog
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
DividerLog(
|
||||||
|
modifier = Modifier
|
||||||
|
.draggable(
|
||||||
|
rememberDraggableState {
|
||||||
|
weightMod.value += it
|
||||||
|
},
|
||||||
|
Orientation.Horizontal
|
||||||
|
),
|
||||||
|
graphWidth = graphWidth,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scrollbar used to scroll horizontally the graph nodes
|
||||||
|
// Added after every component to have the highest priority when clicking
|
||||||
|
HorizontalScrollbar(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.width(graphWidth)
|
||||||
|
.padding(start = 4.dp, bottom = 4.dp),
|
||||||
|
adapter = rememberScrollbarAdapter(horizontalScrollState)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessagesList(
|
||||||
|
scrollState: LazyListState,
|
||||||
|
hasUncommitedChanges: Boolean,
|
||||||
|
selectedCommit: RevCommit?,
|
||||||
|
logStatus: LogStatus.Loaded,
|
||||||
|
repositoryState: RepositoryState,
|
||||||
|
selectedItem: SelectedItem,
|
||||||
|
commitList: GraphCommitList,
|
||||||
|
logViewModel: LogViewModel,
|
||||||
|
onShowLogDialog: (LogDialog) -> Unit,
|
||||||
|
graphWidth: Dp,
|
||||||
|
) {
|
||||||
|
ScrollableLazyColumn(
|
||||||
|
state = scrollState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
if (hasUncommitedChanges)
|
||||||
|
item {
|
||||||
|
UncommitedChangesLine(
|
||||||
|
graphWidth = graphWidth,
|
||||||
|
selected = selectedItem == SelectedItem.UncommitedChanges,
|
||||||
|
statusSummary = logStatus.statusSummary,
|
||||||
|
repositoryState = repositoryState,
|
||||||
|
onUncommitedChangesSelected = {
|
||||||
|
logViewModel.selectLogLine(SelectedItem.UncommitedChanges)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
items(items = commitList) { graphNode ->
|
||||||
|
CommitLine(
|
||||||
|
graphWidth = graphWidth,
|
||||||
|
logViewModel = logViewModel,
|
||||||
|
graphNode = graphNode,
|
||||||
|
selected = selectedCommit?.name == graphNode.name,
|
||||||
|
currentBranch = logStatus.currentBranch,
|
||||||
|
showCreateNewBranch = { onShowLogDialog(LogDialog.NewBranch(graphNode)) },
|
||||||
|
showCreateNewTag = { onShowLogDialog(LogDialog.NewTag(graphNode)) },
|
||||||
|
resetBranch = { onShowLogDialog(LogDialog.ResetBranch(graphNode)) },
|
||||||
|
onMergeBranch = { ref -> onShowLogDialog(LogDialog.MergeBranch(ref)) },
|
||||||
|
onRebaseBranch = { ref -> onShowLogDialog(LogDialog.RebaseBranch(ref)) },
|
||||||
|
onRevCommitSelected = {
|
||||||
|
logViewModel.selectLogLine(SelectedItem.Commit(graphNode))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GraphList(
|
||||||
|
commitList: GraphCommitList,
|
||||||
|
stateHorizontal: ScrollState = rememberScrollState(0),
|
||||||
|
graphWidth: Dp,
|
||||||
|
scrollState: LazyListState,
|
||||||
|
hasUncommitedChanges: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
val graphRealWidth = remember(commitList, graphWidth) {
|
||||||
|
val maxLinePosition = commitList.maxOf { it.lane.position }
|
||||||
|
val calculatedGraphWidth = ((maxLinePosition + 2) * 30f).dp
|
||||||
|
|
||||||
|
if (calculatedGraphWidth < graphWidth)
|
||||||
|
graphWidth
|
||||||
|
else
|
||||||
|
calculatedGraphWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.width(graphWidth)
|
||||||
|
.fillMaxHeight()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.horizontalScroll(stateHorizontal)
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
state = scrollState,
|
state = scrollState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background)
|
.width(graphRealWidth)
|
||||||
.fillMaxSize(),
|
|
||||||
) {
|
) {
|
||||||
if (hasUncommitedChanges)
|
if (hasUncommitedChanges) {
|
||||||
item {
|
item {
|
||||||
UncommitedChangesLine(
|
Row(
|
||||||
selected = selectedItem == SelectedItem.UncommitedChanges,
|
modifier = Modifier
|
||||||
hasPreviousCommits = commitList.isNotEmpty(),
|
.height(40.dp)
|
||||||
statusSummary = logStatus.statusSummary,
|
.fillMaxWidth(),
|
||||||
graphWidth = graphWidth,
|
) {
|
||||||
weightMod = weightMod,
|
UncommitedChangesGraphNode(
|
||||||
repositoryState = repositoryState,
|
modifier = Modifier
|
||||||
onUncommitedChangesSelected = {
|
.width(graphWidth),
|
||||||
logViewModel.selectLogLine(SelectedItem.UncommitedChanges)
|
hasPreviousCommits = commitList.isNotEmpty(),
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(items = commitList) { graphNode ->
|
||||||
|
val nodeColor = colors[graphNode.lane.position % colors.size]
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
CommitsGraphLine(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight(),
|
||||||
|
plotCommit = graphNode,
|
||||||
|
nodeColor = nodeColor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(items = commitList) { graphNode ->
|
|
||||||
CommitLine(
|
|
||||||
logViewModel = logViewModel,
|
|
||||||
graphNode = graphNode,
|
|
||||||
selected = selectedCommit?.name == graphNode.name,
|
|
||||||
weightMod = weightMod,
|
|
||||||
graphWidth = graphWidth,
|
|
||||||
currentBranch = logStatus.currentBranch,
|
|
||||||
showCreateNewBranch = { showLogDialog.value = LogDialog.NewBranch(graphNode) },
|
|
||||||
showCreateNewTag = { showLogDialog.value = LogDialog.NewTag(graphNode) },
|
|
||||||
resetBranch = { showLogDialog.value = LogDialog.ResetBranch(graphNode) },
|
|
||||||
onMergeBranch = { ref -> showLogDialog.value = LogDialog.MergeBranch(ref) },
|
|
||||||
onRebaseBranch = { ref -> showLogDialog.value = LogDialog.RebaseBranch(ref) },
|
|
||||||
onRevCommitSelected = {
|
|
||||||
logViewModel.selectLogLine(SelectedItem.Commit(graphNode))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,10 +386,14 @@ fun GraphHeader(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
DividerLog(
|
SimpleDividerLog(
|
||||||
modifier = Modifier.draggable(rememberDraggableState {
|
modifier = Modifier
|
||||||
weightMod.value += it
|
.draggable(
|
||||||
}, Orientation.Horizontal)
|
rememberDraggableState {
|
||||||
|
weightMod.value += it
|
||||||
|
},
|
||||||
|
Orientation.Horizontal
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@ -272,13 +410,11 @@ fun GraphHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UncommitedChangesLine(
|
fun UncommitedChangesLine(
|
||||||
selected: Boolean,
|
|
||||||
hasPreviousCommits: Boolean,
|
|
||||||
graphWidth: Dp,
|
graphWidth: Dp,
|
||||||
weightMod: MutableState<Float>,
|
selected: Boolean,
|
||||||
onUncommitedChangesSelected: () -> Unit,
|
|
||||||
repositoryState: RepositoryState,
|
repositoryState: RepositoryState,
|
||||||
statusSummary: StatusSummary
|
statusSummary: StatusSummary,
|
||||||
|
onUncommitedChangesSelected: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val textColor = if (selected) {
|
val textColor = if (selected) {
|
||||||
MaterialTheme.colors.primary
|
MaterialTheme.colors.primary
|
||||||
@ -291,25 +427,10 @@ fun UncommitedChangesLine(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
onUncommitedChangesSelected()
|
onUncommitedChangesSelected()
|
||||||
},
|
}
|
||||||
|
.padding(start = graphWidth + PADDING_BETWEEN_DIVIDER_AND_MESSAGE.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
UncommitedChangesGraphNode(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(graphWidth),
|
|
||||||
hasPreviousCommits = hasPreviousCommits,
|
|
||||||
)
|
|
||||||
|
|
||||||
DividerLog(
|
|
||||||
modifier = Modifier
|
|
||||||
.draggable(
|
|
||||||
rememberDraggableState {
|
|
||||||
weightMod.value += it
|
|
||||||
},
|
|
||||||
Orientation.Horizontal
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val text = when {
|
val text = when {
|
||||||
repositoryState.isRebasing -> "Pending changes to rebase"
|
repositoryState.isRebasing -> "Pending changes to rebase"
|
||||||
repositoryState.isMerging -> "Pending changes to merge"
|
repositoryState.isMerging -> "Pending changes to merge"
|
||||||
@ -391,11 +512,10 @@ fun SummaryEntry(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommitLine(
|
fun CommitLine(
|
||||||
|
graphWidth: Dp,
|
||||||
logViewModel: LogViewModel,
|
logViewModel: LogViewModel,
|
||||||
graphNode: GraphNode,
|
graphNode: GraphNode,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
weightMod: MutableState<Float>,
|
|
||||||
graphWidth: Dp,
|
|
||||||
currentBranch: Ref?,
|
currentBranch: Ref?,
|
||||||
showCreateNewBranch: () -> Unit,
|
showCreateNewBranch: () -> Unit,
|
||||||
showCreateNewTag: () -> Unit,
|
showCreateNewTag: () -> Unit,
|
||||||
@ -410,6 +530,7 @@ fun CommitLine(
|
|||||||
.clickable {
|
.clickable {
|
||||||
onRevCommitSelected(graphNode)
|
onRevCommitSelected(graphNode)
|
||||||
}
|
}
|
||||||
|
.padding(start = graphWidth + PADDING_BETWEEN_DIVIDER_AND_MESSAGE.dp)
|
||||||
) {
|
) {
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
items = {
|
items = {
|
||||||
@ -447,25 +568,6 @@ fun CommitLine(
|
|||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
val nodeColor = colors[graphNode.lane.position % colors.size]
|
val nodeColor = colors[graphNode.lane.position % colors.size]
|
||||||
|
|
||||||
CommitsGraphLine(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(graphWidth)
|
|
||||||
.fillMaxHeight(),
|
|
||||||
plotCommit = graphNode,
|
|
||||||
nodeColor = nodeColor,
|
|
||||||
)
|
|
||||||
|
|
||||||
DividerLog(
|
|
||||||
modifier = Modifier
|
|
||||||
.draggable(
|
|
||||||
rememberDraggableState {
|
|
||||||
weightMod.value += it
|
|
||||||
},
|
|
||||||
Orientation.Horizontal
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
CommitMessage(
|
CommitMessage(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
commit = graphNode,
|
commit = graphNode,
|
||||||
@ -573,9 +675,10 @@ fun CommitMessage(
|
|||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DividerLog(modifier: Modifier) {
|
fun DividerLog(modifier: Modifier, graphWidth: Dp) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(start = graphWidth)
|
||||||
.width(8.dp)
|
.width(8.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.pointerHoverIcon(PointerIconDefaults.Hand)
|
.pointerHoverIcon(PointerIconDefaults.Hand)
|
||||||
@ -590,6 +693,12 @@ fun DividerLog(modifier: Modifier) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
fun SimpleDividerLog(modifier: Modifier) {
|
||||||
|
DividerLog(modifier, graphWidth = 0.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommitsGraphLine(
|
fun CommitsGraphLine(
|
||||||
|
Loading…
Reference in New Issue
Block a user