Added "File history" feature
This commit is contained in:
parent
9c1133a292
commit
969233ec99
@ -32,6 +32,7 @@ fun CommitChanges(
|
|||||||
onDiffSelected: (DiffEntry) -> Unit,
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
diffSelected: DiffEntryType?,
|
diffSelected: DiffEntryType?,
|
||||||
onBlame: (String) -> Unit,
|
onBlame: (String) -> Unit,
|
||||||
|
onHistory: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(selectedItem) {
|
LaunchedEffect(selectedItem) {
|
||||||
commitChangesViewModel.loadChanges(selectedItem.revCommit)
|
commitChangesViewModel.loadChanges(selectedItem.revCommit)
|
||||||
@ -49,7 +50,8 @@ fun CommitChanges(
|
|||||||
commit = commitChangesStatus.commit,
|
commit = commitChangesStatus.commit,
|
||||||
changes = commitChangesStatus.changes,
|
changes = commitChangesStatus.changes,
|
||||||
onDiffSelected = onDiffSelected,
|
onDiffSelected = onDiffSelected,
|
||||||
onBlame = onBlame
|
onBlame = onBlame,
|
||||||
|
onHistory = onHistory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,6 +64,7 @@ fun CommitChangesView(
|
|||||||
onDiffSelected: (DiffEntry) -> Unit,
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
diffSelected: DiffEntryType?,
|
diffSelected: DiffEntryType?,
|
||||||
onBlame: (String) -> Unit,
|
onBlame: (String) -> Unit,
|
||||||
|
onHistory: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -119,6 +122,7 @@ fun CommitChangesView(
|
|||||||
diffEntries = changes,
|
diffEntries = changes,
|
||||||
onDiffSelected = onDiffSelected,
|
onDiffSelected = onDiffSelected,
|
||||||
onBlame = onBlame,
|
onBlame = onBlame,
|
||||||
|
onHistory = onHistory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +174,6 @@ fun Author(commit: RevCommit) {
|
|||||||
fontSize = 13.sp,
|
fontSize = 13.sp,
|
||||||
tooltipTitle = authorIdent.`when`.toSystemDateTimeString()
|
tooltipTitle = authorIdent.`when`.toSystemDateTimeString()
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@ -190,6 +193,7 @@ fun CommitLogChanges(
|
|||||||
onDiffSelected: (DiffEntry) -> Unit,
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
diffSelected: DiffEntryType?,
|
diffSelected: DiffEntryType?,
|
||||||
onBlame: (String) -> Unit,
|
onBlame: (String) -> Unit,
|
||||||
|
onHistory: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollableLazyColumn(
|
ScrollableLazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -200,7 +204,8 @@ fun CommitLogChanges(
|
|||||||
items = {
|
items = {
|
||||||
commitedChangesEntriesContextMenuItems(
|
commitedChangesEntriesContextMenuItems(
|
||||||
diffEntry,
|
diffEntry,
|
||||||
onBlame = { onBlame(diffEntry.filePath) }
|
onBlame = { onBlame(diffEntry.filePath) },
|
||||||
|
onHistory = { onHistory(diffEntry.filePath) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@ -219,7 +224,6 @@ fun CommitLogChanges(
|
|||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.weight(2f))
|
Spacer(modifier = Modifier.weight(2f))
|
||||||
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -5,6 +5,7 @@ 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.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.selection.DisableSelection
|
import androidx.compose.foundation.text.selection.DisableSelection
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
@ -79,12 +80,23 @@ fun Diff(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (diffResult is DiffResult.Text) {
|
if (diffResult is DiffResult.Text) {
|
||||||
TextDiff(diffEntryType, diffViewModel, diffResult)
|
val scrollState by diffViewModel.lazyListState.collectAsState()
|
||||||
|
|
||||||
|
TextDiff(
|
||||||
|
diffEntryType = diffEntryType,
|
||||||
|
scrollState = scrollState,
|
||||||
|
diffResult = diffResult,
|
||||||
|
onUnstageHunk = { entry, hunk ->
|
||||||
|
diffViewModel.unstageHunk(entry, hunk)
|
||||||
|
},
|
||||||
|
) { entry, hunk ->
|
||||||
|
diffViewModel.stageHunk(entry, hunk)
|
||||||
|
}
|
||||||
} else if (diffResult is DiffResult.NonText) {
|
} else if (diffResult is DiffResult.NonText) {
|
||||||
NonTextDiff(diffResult)
|
NonTextDiff(diffResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewDiffResult.Loading -> {
|
ViewDiffResult.Loading, ViewDiffResult.None -> {
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,25 +198,29 @@ fun BinaryDiff() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextDiff(diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, diffResult: DiffResult.Text) {
|
fun TextDiff(
|
||||||
|
diffEntryType: DiffEntryType,
|
||||||
|
scrollState: LazyListState,
|
||||||
|
diffResult: DiffResult.Text,
|
||||||
|
onUnstageHunk: (DiffEntry, Hunk) -> Unit,
|
||||||
|
onStageHunk: (DiffEntry, Hunk) -> Unit,
|
||||||
|
) {
|
||||||
val hunks = diffResult.hunks
|
val hunks = diffResult.hunks
|
||||||
|
|
||||||
val scrollState by diffViewModel.lazyListState.collectAsState()
|
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
ScrollableLazyColumn(
|
ScrollableLazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
state = scrollState
|
state = scrollState
|
||||||
) {
|
) {
|
||||||
|
|
||||||
for (hunk in hunks) {
|
for (hunk in hunks) {
|
||||||
item {
|
item {
|
||||||
DisableSelection {
|
DisableSelection {
|
||||||
HunkHeader(
|
HunkHeader(
|
||||||
hunk = hunk,
|
hunk = hunk,
|
||||||
diffViewModel = diffViewModel,
|
|
||||||
diffEntryType = diffEntryType,
|
diffEntryType = diffEntryType,
|
||||||
diffEntry =diffResult.diffEntry,
|
onUnstageHunk = { onUnstageHunk(diffResult.diffEntry, hunk) },
|
||||||
|
onStageHunk = { onStageHunk(diffResult.diffEntry, hunk) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,8 +243,8 @@ fun TextDiff(diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, diffRes
|
|||||||
fun HunkHeader(
|
fun HunkHeader(
|
||||||
hunk: Hunk,
|
hunk: Hunk,
|
||||||
diffEntryType: DiffEntryType,
|
diffEntryType: DiffEntryType,
|
||||||
diffViewModel: DiffViewModel,
|
onUnstageHunk: () -> Unit,
|
||||||
diffEntry: DiffEntry,
|
onStageHunk: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -266,9 +282,9 @@ fun HunkHeader(
|
|||||||
backgroundButton = color,
|
backgroundButton = color,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (diffEntryType is DiffEntryType.StagedDiff) {
|
if (diffEntryType is DiffEntryType.StagedDiff) {
|
||||||
diffViewModel.unstageHunk(diffEntry, hunk)
|
onUnstageHunk()
|
||||||
} else {
|
} else {
|
||||||
diffViewModel.stageHunk(diffEntry, hunk)
|
onStageHunk()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -346,7 +362,10 @@ fun DiffHeader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DiffLine(highestLineNumberLength: Int, line: Line) {
|
fun DiffLine(
|
||||||
|
highestLineNumberLength: Int,
|
||||||
|
line: Line,
|
||||||
|
) {
|
||||||
val backgroundColor = when (line.lineType) {
|
val backgroundColor = when (line.lineType) {
|
||||||
LineType.ADDED -> {
|
LineType.ADDED -> {
|
||||||
Color(0x77a9d49b)
|
Color(0x77a9d49b)
|
||||||
|
222
src/main/kotlin/app/ui/FileHistory.kt
Normal file
222
src/main/kotlin/app/ui/FileHistory.kt
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
@file:OptIn(ExperimentalComposeUiApi::class, ExperimentalSplitPaneApi::class)
|
||||||
|
|
||||||
|
package app.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||||
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.extensions.handMouseClickable
|
||||||
|
import app.extensions.toSmartSystemString
|
||||||
|
import app.extensions.toSystemDateTimeString
|
||||||
|
import app.git.diff.DiffResult
|
||||||
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.secondaryTextColor
|
||||||
|
import app.ui.components.AvatarImage
|
||||||
|
import app.ui.components.ScrollableLazyColumn
|
||||||
|
import app.ui.components.TooltipText
|
||||||
|
import app.viewmodels.HistoryState
|
||||||
|
import app.viewmodels.HistoryViewModel
|
||||||
|
import app.viewmodels.ViewDiffResult
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FileHistory(
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
|
onClose: () -> Unit
|
||||||
|
) {
|
||||||
|
val historyState by historyViewModel.historyState.collectAsState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Header(filePath = historyState.filePath, onClose = onClose)
|
||||||
|
|
||||||
|
HistoryContent(
|
||||||
|
historyViewModel,
|
||||||
|
historyState,
|
||||||
|
onCommitSelected = { historyViewModel.selectCommit(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Header(
|
||||||
|
filePath: String,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(50.dp)
|
||||||
|
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
|
||||||
|
.background(MaterialTheme.colors.surface),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = filePath,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onClose,
|
||||||
|
modifier = Modifier
|
||||||
|
.pointerHoverIcon(PointerIconDefaults.Hand)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource("close.svg"),
|
||||||
|
contentDescription = "Close history",
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryTextColor),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun HistoryContent(
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
|
historyState: HistoryState,
|
||||||
|
onCommitSelected: (RevCommit) -> Unit,
|
||||||
|
) {
|
||||||
|
val textScrollState by historyViewModel.lazyListState.collectAsState()
|
||||||
|
val viewDiffResult by historyViewModel.viewDiffResult.collectAsState()
|
||||||
|
|
||||||
|
when (historyState) {
|
||||||
|
is HistoryState.Loaded -> HistoryContentLoaded(
|
||||||
|
historyState = historyState,
|
||||||
|
viewDiffResult = viewDiffResult,
|
||||||
|
scrollState = textScrollState,
|
||||||
|
onCommitSelected = onCommitSelected,
|
||||||
|
)
|
||||||
|
is HistoryState.Loading -> Box { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HistoryContentLoaded(
|
||||||
|
historyState: HistoryState.Loaded,
|
||||||
|
viewDiffResult: ViewDiffResult?,
|
||||||
|
scrollState: LazyListState,
|
||||||
|
onCommitSelected: (RevCommit) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
ScrollableLazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.width(300.dp)
|
||||||
|
.background(MaterialTheme.colors.surface)
|
||||||
|
) {
|
||||||
|
items(historyState.commits) { commit ->
|
||||||
|
HistoryCommit(commit, onCommitSelected = { onCommitSelected(commit) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
viewDiffResult != null &&
|
||||||
|
viewDiffResult is ViewDiffResult.Loaded
|
||||||
|
) {
|
||||||
|
val diffResult = viewDiffResult.diffResult
|
||||||
|
if (diffResult is DiffResult.Text) {
|
||||||
|
TextDiff(
|
||||||
|
diffEntryType = viewDiffResult.diffEntryType,
|
||||||
|
scrollState = scrollState,
|
||||||
|
diffResult = diffResult,
|
||||||
|
onUnstageHunk = { _, _ -> },
|
||||||
|
onStageHunk = { _, _ -> }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HistoryCommit(commit: RevCommit, onCommitSelected: () -> Unit) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.handMouseClickable { onCommitSelected() }
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
AvatarImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.size(40.dp),
|
||||||
|
personIdent = commit.authorIdent,
|
||||||
|
)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = commit.shortMessage,
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = commit.name.take(7),
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colors.secondaryTextColor,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
val date = remember(commit.authorIdent) {
|
||||||
|
commit.authorIdent.`when`.toSmartSystemString()
|
||||||
|
}
|
||||||
|
|
||||||
|
TooltipText(
|
||||||
|
text = date,
|
||||||
|
color = MaterialTheme.colors.secondaryTextColor,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
tooltipTitle = date
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:OptIn(ExperimentalSplitPaneApi::class)
|
||||||
|
|
||||||
package app.ui
|
package app.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
@ -31,6 +33,7 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
val diffSelected by tabViewModel.diffSelected.collectAsState()
|
val diffSelected by tabViewModel.diffSelected.collectAsState()
|
||||||
val selectedItem by tabViewModel.selectedItem.collectAsState()
|
val selectedItem by tabViewModel.selectedItem.collectAsState()
|
||||||
val blameState by tabViewModel.blameState.collectAsState()
|
val blameState by tabViewModel.blameState.collectAsState()
|
||||||
|
val showHistory by tabViewModel.showHistory.collectAsState()
|
||||||
|
|
||||||
var showNewBranchDialog by remember { mutableStateOf(false) }
|
var showNewBranchDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@ -65,13 +68,12 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
onCreateBranch = { showNewBranchDialog = true }
|
onCreateBranch = { showNewBranchDialog = true }
|
||||||
)
|
)
|
||||||
|
|
||||||
RepoContent(tabViewModel, diffSelected, selectedItem, repositoryState, blameState)
|
RepoContent(tabViewModel, diffSelected, selectedItem, repositoryState, blameState, showHistory)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalSplitPaneApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RepoContent(
|
fun RepoContent(
|
||||||
tabViewModel: TabViewModel,
|
tabViewModel: TabViewModel,
|
||||||
@ -79,6 +81,39 @@ fun RepoContent(
|
|||||||
selectedItem: SelectedItem,
|
selectedItem: SelectedItem,
|
||||||
repositoryState: RepositoryState,
|
repositoryState: RepositoryState,
|
||||||
blameState: BlameState,
|
blameState: BlameState,
|
||||||
|
showHistory: Boolean,
|
||||||
|
) {
|
||||||
|
if(showHistory) {
|
||||||
|
val historyViewModel = tabViewModel.historyViewModel
|
||||||
|
|
||||||
|
if(historyViewModel != null) {
|
||||||
|
FileHistory(
|
||||||
|
historyViewModel = historyViewModel,
|
||||||
|
onClose = {
|
||||||
|
tabViewModel.closeHistory()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MainContentView(
|
||||||
|
tabViewModel,
|
||||||
|
diffSelected,
|
||||||
|
selectedItem,
|
||||||
|
repositoryState,
|
||||||
|
blameState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainContentView(
|
||||||
|
tabViewModel: TabViewModel,
|
||||||
|
diffSelected: DiffEntryType?,
|
||||||
|
selectedItem: SelectedItem,
|
||||||
|
repositoryState: RepositoryState,
|
||||||
|
blameState: BlameState
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
HorizontalSplitPane {
|
HorizontalSplitPane {
|
||||||
@ -187,7 +222,8 @@ fun RepoContent(
|
|||||||
else
|
else
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
|
||||||
},
|
},
|
||||||
onBlameFile = { tabViewModel.blameFile(it) }
|
onBlameFile = { tabViewModel.blameFile(it) },
|
||||||
|
onHistoryFile = { tabViewModel.fileHistory(it) }
|
||||||
)
|
)
|
||||||
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
|
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
|
||||||
CommitChanges(
|
CommitChanges(
|
||||||
@ -198,7 +234,8 @@ fun RepoContent(
|
|||||||
tabViewModel.minimizeBlame()
|
tabViewModel.minimizeBlame()
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
||||||
},
|
},
|
||||||
onBlame = { tabViewModel.blameFile(it) }
|
onBlame = { tabViewModel.blameFile(it) },
|
||||||
|
onHistory = { tabViewModel.fileHistory(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ fun UncommitedChanges(
|
|||||||
onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
|
onStagedDiffEntrySelected: (StatusEntry?) -> Unit,
|
||||||
onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
|
onUnstagedDiffEntrySelected: (StatusEntry) -> Unit,
|
||||||
onBlameFile: (String) -> Unit,
|
onBlameFile: (String) -> Unit,
|
||||||
|
onHistoryFile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
||||||
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
||||||
@ -106,6 +107,7 @@ fun UncommitedChanges(
|
|||||||
entryType = EntryType.STAGED,
|
entryType = EntryType.STAGED,
|
||||||
onBlame = { onBlameFile(statusEntry.filePath) },
|
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||||
onReset = { statusViewModel.resetStaged(statusEntry) },
|
onReset = { statusViewModel.resetStaged(statusEntry) },
|
||||||
|
onHistory = { onHistoryFile(statusEntry.filePath) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAllAction = {
|
onAllAction = {
|
||||||
@ -132,10 +134,11 @@ fun UncommitedChanges(
|
|||||||
statusEntry = statusEntry,
|
statusEntry = statusEntry,
|
||||||
entryType = EntryType.UNSTAGED,
|
entryType = EntryType.UNSTAGED,
|
||||||
onBlame = { onBlameFile(statusEntry.filePath) },
|
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||||
|
onHistory = { onHistoryFile(statusEntry.filePath) },
|
||||||
onReset = { statusViewModel.resetUnstaged(statusEntry) },
|
onReset = { statusViewModel.resetUnstaged(statusEntry) },
|
||||||
onDelete = {
|
onDelete = {
|
||||||
statusViewModel.deleteFile(statusEntry)
|
statusViewModel.deleteFile(statusEntry)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAllAction = {
|
onAllAction = {
|
||||||
|
@ -9,6 +9,7 @@ import org.eclipse.jgit.diff.DiffEntry
|
|||||||
fun commitedChangesEntriesContextMenuItems(
|
fun commitedChangesEntriesContextMenuItems(
|
||||||
diffEntry: DiffEntry,
|
diffEntry: DiffEntry,
|
||||||
onBlame: () -> Unit,
|
onBlame: () -> Unit,
|
||||||
|
onHistory: () -> Unit,
|
||||||
): List<ContextMenuItem> {
|
): List<ContextMenuItem> {
|
||||||
return mutableListOf<ContextMenuItem>().apply {
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
if (diffEntry.changeType != DiffEntry.ChangeType.ADD ||
|
if (diffEntry.changeType != DiffEntry.ChangeType.ADD ||
|
||||||
@ -19,6 +20,12 @@ fun commitedChangesEntriesContextMenuItems(
|
|||||||
onClick = onBlame,
|
onClick = onBlame,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "File history",
|
||||||
|
onClick = onHistory,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ fun statusEntriesContextMenuItems(
|
|||||||
onReset: () -> Unit,
|
onReset: () -> Unit,
|
||||||
onDelete: () -> Unit = {},
|
onDelete: () -> Unit = {},
|
||||||
onBlame: () -> Unit,
|
onBlame: () -> Unit,
|
||||||
|
onHistory: () -> Unit,
|
||||||
): List<ContextMenuItem> {
|
): List<ContextMenuItem> {
|
||||||
return mutableListOf<ContextMenuItem>().apply {
|
return mutableListOf<ContextMenuItem>().apply {
|
||||||
if (statusEntry.statusType != StatusType.ADDED) {
|
if (statusEntry.statusType != StatusType.ADDED) {
|
||||||
@ -29,6 +30,13 @@ fun statusEntriesContextMenuItems(
|
|||||||
onClick = onBlame,
|
onClick = onBlame,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add(
|
||||||
|
ContextMenuItem(
|
||||||
|
label = "File history",
|
||||||
|
onClick = onHistory,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,7 +779,7 @@ fun CommitMessage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = commit.committerIdent.`when`.toSmartSystemString(),
|
text = commit.authorIdent.`when`.toSmartSystemString(),
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = MaterialTheme.colors.secondaryTextColor,
|
color = MaterialTheme.colors.secondaryTextColor,
|
||||||
|
@ -3,7 +3,6 @@ package app.viewmodels
|
|||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import app.exceptions.MissingDiffEntryException
|
import app.exceptions.MissingDiffEntryException
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.git.diff.DiffResult
|
|
||||||
import app.git.diff.Hunk
|
import app.git.diff.Hunk
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@ -87,8 +86,3 @@ class DiffViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed interface ViewDiffResult {
|
|
||||||
object Loading: ViewDiffResult
|
|
||||||
object DiffNotFound: ViewDiffResult
|
|
||||||
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult): ViewDiffResult
|
|
||||||
}
|
|
||||||
|
82
src/main/kotlin/app/viewmodels/HistoryViewModel.kt
Normal file
82
src/main/kotlin/app/viewmodels/HistoryViewModel.kt
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package app.viewmodels
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import app.exceptions.MissingDiffEntryException
|
||||||
|
import app.extensions.filePath
|
||||||
|
import app.git.DiffEntryType
|
||||||
|
import app.git.DiffManager
|
||||||
|
import app.git.RefreshType
|
||||||
|
import app.git.TabState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class HistoryViewModel @Inject constructor(
|
||||||
|
private val tabState: TabState,
|
||||||
|
private val diffManager: DiffManager,
|
||||||
|
) {
|
||||||
|
private val _historyState = MutableStateFlow<HistoryState>(HistoryState.Loading(""))
|
||||||
|
val historyState: StateFlow<HistoryState> = _historyState
|
||||||
|
|
||||||
|
private val _viewDiffResult = MutableStateFlow<ViewDiffResult>(ViewDiffResult.None)
|
||||||
|
val viewDiffResult: StateFlow<ViewDiffResult> = _viewDiffResult
|
||||||
|
var filePath: String = ""
|
||||||
|
|
||||||
|
val lazyListState = MutableStateFlow(
|
||||||
|
LazyListState(
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun fileHistory(filePath: String) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
this.filePath = filePath
|
||||||
|
_historyState.value = HistoryState.Loading(filePath)
|
||||||
|
|
||||||
|
val log = git.log()
|
||||||
|
.addPath(filePath)
|
||||||
|
.call()
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
_historyState.value = HistoryState.Loaded(filePath, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectCommit(commit: RevCommit) = tabState.runOperation(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
showError = true,
|
||||||
|
) { git ->
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
val diffEntries = diffManager.commitDiffEntries(git, commit)
|
||||||
|
val diffEntry = diffEntries.firstOrNull {entry ->
|
||||||
|
entry.filePath == this.filePath
|
||||||
|
}
|
||||||
|
|
||||||
|
if(diffEntry == null) {
|
||||||
|
_viewDiffResult.value = ViewDiffResult.DiffNotFound
|
||||||
|
return@runOperation
|
||||||
|
}
|
||||||
|
val diffEntryType = DiffEntryType.CommitDiff(diffEntry)
|
||||||
|
|
||||||
|
val diffFormat = diffManager.diffFormat(git, diffEntryType)
|
||||||
|
|
||||||
|
_viewDiffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if(ex is MissingDiffEntryException) {
|
||||||
|
tabState.refreshData(refreshType = RefreshType.UNCOMMITED_CHANGES)
|
||||||
|
_viewDiffResult.value = ViewDiffResult.DiffNotFound
|
||||||
|
} else
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class HistoryState(val filePath: String) {
|
||||||
|
class Loading(filePath: String) : HistoryState(filePath)
|
||||||
|
class Loaded(filePath: String, val commits: List<RevCommit>) : HistoryState(filePath)
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ class TabViewModel @Inject constructor(
|
|||||||
val commitChangesViewModel: CommitChangesViewModel,
|
val commitChangesViewModel: CommitChangesViewModel,
|
||||||
val cloneViewModel: CloneViewModel,
|
val cloneViewModel: CloneViewModel,
|
||||||
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
||||||
|
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
||||||
private val repositoryManager: RepositoryManager,
|
private val repositoryManager: RepositoryManager,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
val appStateManager: AppStateManager,
|
val appStateManager: AppStateManager,
|
||||||
@ -79,6 +80,12 @@ class TabViewModel @Inject constructor(
|
|||||||
private val _blameState = MutableStateFlow<BlameState>(BlameState.None)
|
private val _blameState = MutableStateFlow<BlameState>(BlameState.None)
|
||||||
val blameState: StateFlow<BlameState> = _blameState
|
val blameState: StateFlow<BlameState> = _blameState
|
||||||
|
|
||||||
|
private val _showHistory = MutableStateFlow(false)
|
||||||
|
val showHistory: StateFlow<Boolean> = _showHistory
|
||||||
|
|
||||||
|
var historyViewModel: HistoryViewModel? = null
|
||||||
|
private set
|
||||||
|
|
||||||
val showError = MutableStateFlow(false)
|
val showError = MutableStateFlow(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -361,6 +368,17 @@ class TabViewModel @Inject constructor(
|
|||||||
fun selectCommit(commit: RevCommit) {
|
fun selectCommit(commit: RevCommit) {
|
||||||
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fileHistory(filePath: String) {
|
||||||
|
historyViewModel = historyViewModelProvider.get()
|
||||||
|
historyViewModel?.fileHistory(filePath)
|
||||||
|
_showHistory.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeHistory() {
|
||||||
|
_showHistory.value = false
|
||||||
|
historyViewModel = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -370,7 +388,6 @@ sealed class RepositorySelectionStatus {
|
|||||||
data class Open(val repository: Repository) : RepositorySelectionStatus()
|
data class Open(val repository: Repository) : RepositorySelectionStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed interface BlameState {
|
sealed interface BlameState {
|
||||||
data class Loading(val filePath: String) : BlameState
|
data class Loading(val filePath: String) : BlameState
|
||||||
|
|
||||||
|
11
src/main/kotlin/app/viewmodels/ViewDiffResult.kt
Normal file
11
src/main/kotlin/app/viewmodels/ViewDiffResult.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package app.viewmodels
|
||||||
|
|
||||||
|
import app.git.DiffEntryType
|
||||||
|
import app.git.diff.DiffResult
|
||||||
|
|
||||||
|
sealed interface ViewDiffResult {
|
||||||
|
object None: ViewDiffResult
|
||||||
|
object Loading: ViewDiffResult
|
||||||
|
object DiffNotFound: ViewDiffResult
|
||||||
|
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult): ViewDiffResult
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user