Add support multiselect
This commit is contained in:
parent
c1d122b3b7
commit
bbc8132406
@ -24,8 +24,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||||
import com.jetpackduba.gitnuro.git.DiffEntryType
|
import com.jetpackduba.gitnuro.git.DiffEntryType
|
||||||
|
import com.jetpackduba.gitnuro.git.graph.GraphNode
|
||||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||||
|
import com.jetpackduba.gitnuro.ui.changes.CommitChanges
|
||||||
|
import com.jetpackduba.gitnuro.ui.changes.MultiCommitChanges
|
||||||
|
import com.jetpackduba.gitnuro.ui.changes.UncommitedChanges
|
||||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||||
import com.jetpackduba.gitnuro.ui.components.ScrollableColumn
|
import com.jetpackduba.gitnuro.ui.components.ScrollableColumn
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||||
@ -379,45 +383,60 @@ fun MainContentView(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
) {
|
) {
|
||||||
val safeSelectedItem = selectedItem
|
when (selectedItem) {
|
||||||
if (safeSelectedItem == SelectedItem.UncommitedChanges) {
|
SelectedItem.UncommitedChanges -> {
|
||||||
UncommitedChanges(
|
UncommitedChanges(
|
||||||
selectedEntryType = diffSelected,
|
selectedEntryType = diffSelected,
|
||||||
repositoryState = repositoryState,
|
repositoryState = repositoryState,
|
||||||
onStagedDiffEntrySelected = { diffEntry ->
|
onStagedDiffEntrySelected = { diffEntry ->
|
||||||
tabViewModel.minimizeBlame()
|
tabViewModel.minimizeBlame()
|
||||||
|
|
||||||
|
tabViewModel.newDiffSelected = if (diffEntry != null) {
|
||||||
|
if (repositoryState == RepositoryState.SAFE)
|
||||||
|
DiffEntryType.SafeStagedDiff(diffEntry)
|
||||||
|
else
|
||||||
|
DiffEntryType.UnsafeStagedDiff(diffEntry)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUnstagedDiffEntrySelected = { diffEntry ->
|
||||||
|
tabViewModel.minimizeBlame()
|
||||||
|
|
||||||
tabViewModel.newDiffSelected = if (diffEntry != null) {
|
|
||||||
if (repositoryState == RepositoryState.SAFE)
|
if (repositoryState == RepositoryState.SAFE)
|
||||||
DiffEntryType.SafeStagedDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
|
||||||
else
|
else
|
||||||
DiffEntryType.UnsafeStagedDiff(diffEntry)
|
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
|
||||||
} else {
|
},
|
||||||
null
|
onBlameFile = { tabViewModel.blameFile(it) },
|
||||||
}
|
onHistoryFile = { tabViewModel.fileHistory(it) }
|
||||||
},
|
)
|
||||||
onUnstagedDiffEntrySelected = { diffEntry ->
|
}
|
||||||
tabViewModel.minimizeBlame()
|
is SelectedItem.CommitBasedItem -> {
|
||||||
|
CommitChanges(
|
||||||
if (repositoryState == RepositoryState.SAFE)
|
selectedItem = selectedItem,
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.SafeUnstagedDiff(diffEntry)
|
diffSelected = diffSelected,
|
||||||
else
|
onDiffSelected = { diffEntry ->
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.UnsafeUnstagedDiff(diffEntry)
|
tabViewModel.minimizeBlame()
|
||||||
},
|
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
||||||
onBlameFile = { tabViewModel.blameFile(it) },
|
},
|
||||||
onHistoryFile = { tabViewModel.fileHistory(it) }
|
onBlame = { tabViewModel.blameFile(it) },
|
||||||
)
|
onHistory = { tabViewModel.fileHistory(it) },
|
||||||
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
|
)
|
||||||
CommitChanges(
|
}
|
||||||
selectedItem = safeSelectedItem,
|
is SelectedItem.MultiCommitBasedItem -> {
|
||||||
diffSelected = diffSelected,
|
MultiCommitChanges(
|
||||||
onDiffSelected = { diffEntry ->
|
selectedItem = selectedItem,
|
||||||
tabViewModel.minimizeBlame()
|
diffSelected = diffSelected,
|
||||||
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
onDiffSelected = { diffEntry ->
|
||||||
},
|
tabViewModel.minimizeBlame()
|
||||||
onBlame = { tabViewModel.blameFile(it) },
|
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
|
||||||
onHistory = { tabViewModel.fileHistory(it) },
|
},
|
||||||
)
|
onBlame = { tabViewModel.blameFile(it) },
|
||||||
|
onHistory = { tabViewModel.fileHistory(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,8 +469,21 @@ fun SplitterScope.repositorySplitter() {
|
|||||||
sealed class SelectedItem {
|
sealed class SelectedItem {
|
||||||
object None : SelectedItem()
|
object None : SelectedItem()
|
||||||
object UncommitedChanges : SelectedItem()
|
object UncommitedChanges : SelectedItem()
|
||||||
|
data class MultiCommitBasedItem(
|
||||||
|
val itemList: List<RevCommit>,
|
||||||
|
val targetCommit: RevCommit,
|
||||||
|
) : SelectedItem()
|
||||||
sealed class CommitBasedItem(val revCommit: RevCommit) : SelectedItem()
|
sealed class CommitBasedItem(val revCommit: RevCommit) : SelectedItem()
|
||||||
class Ref(revCommit: RevCommit) : CommitBasedItem(revCommit)
|
class Ref(revCommit: RevCommit) : CommitBasedItem(revCommit)
|
||||||
class Commit(revCommit: RevCommit) : CommitBasedItem(revCommit)
|
class Commit(revCommit: RevCommit) : CommitBasedItem(revCommit)
|
||||||
class Stash(revCommit: RevCommit) : CommitBasedItem(revCommit)
|
class Stash(revCommit: RevCommit) : CommitBasedItem(revCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelectedItem.containCommit(commit: RevCommit): Boolean {
|
||||||
|
return when (this) {
|
||||||
|
is SelectedItem.UncommitedChanges,
|
||||||
|
is SelectedItem.None -> false
|
||||||
|
is SelectedItem.MultiCommitBasedItem -> this.itemList.contains(commit)
|
||||||
|
is SelectedItem.CommitBasedItem -> this.revCommit == commit
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.jetpackduba.gitnuro.ui
|
package com.jetpackduba.gitnuro.ui.changes
|
||||||
|
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@ -19,12 +19,14 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.jetpackduba.gitnuro.extensions.*
|
import com.jetpackduba.gitnuro.extensions.*
|
||||||
import com.jetpackduba.gitnuro.git.DiffEntryType
|
import com.jetpackduba.gitnuro.git.DiffEntryType
|
||||||
import com.jetpackduba.gitnuro.theme.*
|
import com.jetpackduba.gitnuro.theme.*
|
||||||
|
import com.jetpackduba.gitnuro.ui.SelectedItem
|
||||||
import com.jetpackduba.gitnuro.ui.components.AvatarImage
|
import com.jetpackduba.gitnuro.ui.components.AvatarImage
|
||||||
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
|
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
|
||||||
import com.jetpackduba.gitnuro.ui.components.TooltipText
|
import com.jetpackduba.gitnuro.ui.components.TooltipText
|
||||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||||
import com.jetpackduba.gitnuro.ui.context_menu.commitedChangesEntriesContextMenuItems
|
import com.jetpackduba.gitnuro.ui.context_menu.commitedChangesEntriesContextMenuItems
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.CommitChanges
|
||||||
import com.jetpackduba.gitnuro.viewmodels.CommitChangesStatus
|
import com.jetpackduba.gitnuro.viewmodels.CommitChangesStatus
|
||||||
import com.jetpackduba.gitnuro.viewmodels.CommitChangesViewModel
|
import com.jetpackduba.gitnuro.viewmodels.CommitChangesViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -253,65 +255,77 @@ fun CommitLogChanges(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Column(
|
CommitLogChangesItem(
|
||||||
modifier = Modifier
|
diffEntry = diffEntry,
|
||||||
.height(40.dp)
|
diffSelected = diffSelected,
|
||||||
.fillMaxWidth()
|
onDiffSelected = { onDiffSelected(diffEntry) }
|
||||||
.handMouseClickable {
|
)
|
||||||
onDiffSelected(diffEntry)
|
|
||||||
}
|
|
||||||
.backgroundIf(
|
|
||||||
condition = diffSelected is DiffEntryType.CommitDiff && diffSelected.diffEntry == diffEntry,
|
|
||||||
color = MaterialTheme.colors.backgroundSelected,
|
|
||||||
),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.weight(2f))
|
|
||||||
|
|
||||||
Row {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
imageVector = diffEntry.icon,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = diffEntry.iconColor,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (diffEntry.parentDirectoryPath.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = diffEntry.parentDirectoryPath.removeSuffix("/"),
|
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
|
||||||
maxLines = 1,
|
|
||||||
softWrap = false,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.body2,
|
|
||||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "/",
|
|
||||||
maxLines = 1,
|
|
||||||
softWrap = false,
|
|
||||||
style = MaterialTheme.typography.body2,
|
|
||||||
overflow = TextOverflow.Visible,
|
|
||||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = diffEntry.fileName,
|
|
||||||
maxLines = 1,
|
|
||||||
softWrap = false,
|
|
||||||
modifier = Modifier.padding(end = 16.dp),
|
|
||||||
style = MaterialTheme.typography.body2,
|
|
||||||
color = MaterialTheme.colors.onBackground,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(2f))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CommitLogChangesItem(
|
||||||
|
diffEntry: DiffEntry,
|
||||||
|
diffSelected: DiffEntryType?,
|
||||||
|
onDiffSelected: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.handMouseClickable {
|
||||||
|
onDiffSelected()
|
||||||
|
}
|
||||||
|
.backgroundIf(
|
||||||
|
condition = diffSelected is DiffEntryType.CommitDiff && diffSelected.diffEntry == diffEntry,
|
||||||
|
color = MaterialTheme.colors.backgroundSelected,
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(2f))
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.size(16.dp),
|
||||||
|
imageVector = diffEntry.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = diffEntry.iconColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (diffEntry.parentDirectoryPath.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = diffEntry.parentDirectoryPath.removeSuffix("/"),
|
||||||
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "/",
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
|
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = diffEntry.fileName,
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(2f))
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.jetpackduba.gitnuro.ui.changes
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jetpackduba.gitnuro.git.DiffEntryType
|
||||||
|
import com.jetpackduba.gitnuro.theme.tertiarySurface
|
||||||
|
import com.jetpackduba.gitnuro.ui.SelectedItem
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.ScrollableColumn
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.CommitChanges
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.MultiCommitChangesStatus
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.MultiCommitChangesViewModel
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiCommitChanges(
|
||||||
|
multiCommitChangesViewModel: MultiCommitChangesViewModel = gitnuroViewModel(),
|
||||||
|
selectedItem: SelectedItem.MultiCommitBasedItem,
|
||||||
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
|
diffSelected: DiffEntryType?,
|
||||||
|
onBlame: (String) -> Unit,
|
||||||
|
onHistory: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
LaunchedEffect(selectedItem) {
|
||||||
|
multiCommitChangesViewModel.loadChanges(selectedItem.itemList)
|
||||||
|
}
|
||||||
|
|
||||||
|
val commitChangesStatusState = multiCommitChangesViewModel.commitsChangesStatus.collectAsState()
|
||||||
|
|
||||||
|
when (val commitChangesStatus = commitChangesStatusState.value) {
|
||||||
|
MultiCommitChangesStatus.Loading -> {
|
||||||
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.primaryVariant)
|
||||||
|
}
|
||||||
|
|
||||||
|
is MultiCommitChangesStatus.Loaded -> {
|
||||||
|
MultiCommitChangesView(
|
||||||
|
diffSelected = diffSelected,
|
||||||
|
changes = commitChangesStatus.changesList,
|
||||||
|
onDiffSelected = onDiffSelected,
|
||||||
|
onBlame = onBlame,
|
||||||
|
onHistory = onHistory,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiCommitChangesView(
|
||||||
|
changes: List<CommitChanges>,
|
||||||
|
onDiffSelected: (DiffEntry) -> Unit,
|
||||||
|
diffSelected: DiffEntryType?,
|
||||||
|
onBlame: (String) -> Unit,
|
||||||
|
onHistory: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp, bottom = 8.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 4.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f, fill = true)
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(34.dp)
|
||||||
|
.background(MaterialTheme.colors.tertiarySurface),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
text = "Files changed",
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
textAlign = TextAlign.Left,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
maxLines = 1,
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollableLazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
items(changes) {commitChanges ->
|
||||||
|
CommitLogChanges(
|
||||||
|
diffSelected = diffSelected,
|
||||||
|
diffEntries = commitChanges.changes,
|
||||||
|
onDiffSelected = onDiffSelected,
|
||||||
|
onBlame = onBlame,
|
||||||
|
onHistory = onHistory,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = commitChanges.commit.fullMessage,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Divider(
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
@file:OptIn(ExperimentalAnimationApi::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
|
@file:OptIn(ExperimentalAnimationApi::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
|
||||||
|
|
||||||
package com.jetpackduba.gitnuro.ui
|
package com.jetpackduba.gitnuro.ui.changes
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
@ -305,11 +305,91 @@ class LogViewModel @Inject constructor(
|
|||||||
NONE_MATCHING_INDEX
|
NONE_MATCHING_INDEX
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectLogLine(commit: GraphNode) = tabState.runOperation(
|
fun selectLogLine(
|
||||||
|
commit: GraphNode,
|
||||||
|
multiSelect: Boolean,
|
||||||
|
rangeSelect: Boolean
|
||||||
|
) = tabState.runOperation(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) {
|
) {
|
||||||
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
when {
|
||||||
|
multiSelect -> selectMultiLogLines(commit)
|
||||||
|
rangeSelect -> selectRangeLogLines(commit)
|
||||||
|
else -> selectSingleLogLine(commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLogSearchFilterByCommit(commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// like with ctrl pressed
|
||||||
|
private suspend fun selectMultiLogLines(commit: GraphNode) {
|
||||||
|
when (val selectedItem = tabState.selectedItem.value) {
|
||||||
|
is SelectedItem.None,
|
||||||
|
is SelectedItem.UncommitedChanges -> selectSingleLogLine(commit)
|
||||||
|
is SelectedItem.CommitBasedItem -> {
|
||||||
|
if (selectedItem.revCommit == commit) {
|
||||||
|
tabState.noneSelected()
|
||||||
|
} else {
|
||||||
|
val list = listOf(selectedItem.revCommit, commit)
|
||||||
|
tabState.newSelectedItem(SelectedItem.MultiCommitBasedItem(list, selectedItem.revCommit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SelectedItem.MultiCommitBasedItem -> {
|
||||||
|
val revList = selectedItem.itemList
|
||||||
|
val list = if (revList.contains(commit)) {
|
||||||
|
revList - commit
|
||||||
|
} else {
|
||||||
|
revList + commit
|
||||||
|
}
|
||||||
|
|
||||||
|
val item = if (list.size > 1) {
|
||||||
|
SelectedItem.MultiCommitBasedItem(list, list.maxBy { it.commitTime })
|
||||||
|
} else {
|
||||||
|
SelectedItem.Commit(list.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
tabState.newSelectedItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like with shift pressed
|
||||||
|
private suspend fun selectRangeLogLines(commit: GraphNode) {
|
||||||
|
when (val selectedItem = tabState.selectedItem.value) {
|
||||||
|
is SelectedItem.None,
|
||||||
|
is SelectedItem.UncommitedChanges -> selectSingleLogLine(commit)
|
||||||
|
is SelectedItem.CommitBasedItem -> {
|
||||||
|
val list = getRangeCommitsFromOneToOne(selectedItem.revCommit, commit)
|
||||||
|
tabState.newSelectedItem(SelectedItem.MultiCommitBasedItem(list, selectedItem.revCommit))
|
||||||
|
}
|
||||||
|
is SelectedItem.MultiCommitBasedItem -> {
|
||||||
|
val list = getRangeCommitsFromOneToOne(selectedItem.targetCommit, commit)
|
||||||
|
tabState.newSelectedItem(SelectedItem.MultiCommitBasedItem(list, selectedItem.targetCommit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRangeCommitsFromOneToOne(from: RevCommit, to: RevCommit): List<RevCommit> {
|
||||||
|
return if (from != to && logStatus.value is LogStatus.Loaded) {
|
||||||
|
val commitList = (logStatus.value as LogStatus.Loaded).plotCommitList
|
||||||
|
|
||||||
|
val first = commitList.indexOf(from)
|
||||||
|
val last = commitList.indexOf(to)
|
||||||
|
val range = if (first < last) first.rangeTo(last) else last.rangeTo(first)
|
||||||
|
|
||||||
|
println(range)
|
||||||
|
|
||||||
|
commitList.slice(range)
|
||||||
|
} else {
|
||||||
|
listOf(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun selectSingleLogLine(commit: GraphNode) {
|
||||||
|
tabState.newSelectedItem(SelectedItem.Commit(commit))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setLogSearchFilterByCommit(commit: GraphNode) {
|
||||||
val searchValue = _logSearchFilterResults.value
|
val searchValue = _logSearchFilterResults.value
|
||||||
if (searchValue is LogSearch.SearchResults) {
|
if (searchValue is LogSearch.SearchResults) {
|
||||||
var index = searchValue.commits.indexOf(commit)
|
var index = searchValue.commits.indexOf(commit)
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.jetpackduba.gitnuro.viewmodels
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.extensions.delayedStateChange
|
||||||
|
import com.jetpackduba.gitnuro.extensions.filePath
|
||||||
|
import com.jetpackduba.gitnuro.git.RefreshType
|
||||||
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
|
import com.jetpackduba.gitnuro.git.diff.GetCommitDiffEntriesUseCase
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import org.eclipse.jgit.revwalk.DepthWalk.Commit
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 300L
|
||||||
|
|
||||||
|
class MultiCommitChangesViewModel @Inject constructor(
|
||||||
|
private val tabState: TabState,
|
||||||
|
private val getCommitDiffEntriesUseCase: GetCommitDiffEntriesUseCase,
|
||||||
|
) {
|
||||||
|
private val _commitsChangesStatus = MutableStateFlow<MultiCommitChangesStatus>(MultiCommitChangesStatus.Loading)
|
||||||
|
val commitsChangesStatus: StateFlow<MultiCommitChangesStatus> = _commitsChangesStatus
|
||||||
|
|
||||||
|
fun loadChanges(commits: List<RevCommit>) = tabState.runOperation(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
delayedStateChange(
|
||||||
|
delayMs = MIN_TIME_IN_MS_TO_SHOW_LOAD,
|
||||||
|
onDelayTriggered = { _commitsChangesStatus.value = MultiCommitChangesStatus.Loading }
|
||||||
|
) {
|
||||||
|
val changes = commits
|
||||||
|
.map { commit ->
|
||||||
|
CommitChanges(
|
||||||
|
commit = commit,
|
||||||
|
changes = getCommitDiffEntriesUseCase(git, commit)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_commitsChangesStatus.value = MultiCommitChangesStatus.Loaded(changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class MultiCommitChangesStatus {
|
||||||
|
object Loading : MultiCommitChangesStatus()
|
||||||
|
data class Loaded(val changesList: List<CommitChanges>) : MultiCommitChangesStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CommitChanges(
|
||||||
|
val commit: RevCommit,
|
||||||
|
val changes: List<DiffEntry>,
|
||||||
|
)
|
@ -15,6 +15,7 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
stashesViewModel: StashesViewModel,
|
stashesViewModel: StashesViewModel,
|
||||||
submodulesViewModel: SubmodulesViewModel,
|
submodulesViewModel: SubmodulesViewModel,
|
||||||
commitChangesViewModel: CommitChangesViewModel,
|
commitChangesViewModel: CommitChangesViewModel,
|
||||||
|
multiCommitChangesViewModel: MultiCommitChangesViewModel,
|
||||||
cloneViewModel: CloneViewModel,
|
cloneViewModel: CloneViewModel,
|
||||||
settingsViewModel: SettingsViewModel,
|
settingsViewModel: SettingsViewModel,
|
||||||
// Dynamic VM
|
// Dynamic VM
|
||||||
@ -33,6 +34,7 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
stashesViewModel::class to stashesViewModel,
|
stashesViewModel::class to stashesViewModel,
|
||||||
submodulesViewModel::class to submodulesViewModel,
|
submodulesViewModel::class to submodulesViewModel,
|
||||||
commitChangesViewModel::class to commitChangesViewModel,
|
commitChangesViewModel::class to commitChangesViewModel,
|
||||||
|
multiCommitChangesViewModel::class to multiCommitChangesViewModel,
|
||||||
cloneViewModel::class to cloneViewModel,
|
cloneViewModel::class to cloneViewModel,
|
||||||
settingsViewModel::class to settingsViewModel,
|
settingsViewModel::class to settingsViewModel,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user