Implemented search in side panel
Refactored composables to use a lazy column instead of a normal column, as it had performance issues with large repositories. Fixes #43
This commit is contained in:
parent
7de332be87
commit
9d07ac59b7
@ -0,0 +1,28 @@
|
||||
package com.jetpackduba.gitnuro.di.factories
|
||||
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.BranchesViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.RemotesViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.StashesViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.TagsViewModel
|
||||
import dagger.assisted.AssistedFactory
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@AssistedFactory
|
||||
interface BranchesViewModelFactory {
|
||||
fun create(filter: StateFlow<String>): BranchesViewModel
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface RemotesViewModelFactory {
|
||||
fun create(filter: StateFlow<String>): RemotesViewModel
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface TagsViewModelFactory {
|
||||
fun create(filter: StateFlow<String>): TagsViewModel
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface StashesViewModelFactory {
|
||||
fun create(filter: StateFlow<String>): StashesViewModel
|
||||
}
|
@ -47,3 +47,7 @@ val String.lineDelimiter: String?
|
||||
|
||||
val String.nullIfEmpty: String?
|
||||
get() = this.ifBlank { null }
|
||||
|
||||
fun String.lowercaseContains(other: String) : Boolean {
|
||||
return this.lowercase().contains(other.lowercase().trim())
|
||||
}
|
@ -27,6 +27,7 @@ fun textFieldColors(
|
||||
fun outlinedTextFieldColors() = TextFieldDefaults.outlinedTextFieldColors(
|
||||
cursorColor = MaterialTheme.colors.primaryVariant,
|
||||
focusedBorderColor = MaterialTheme.colors.primaryVariant,
|
||||
unfocusedBorderColor = MaterialTheme.colors.onBackgroundSecondary.copy(alpha = 0.2f),
|
||||
focusedLabelColor = MaterialTheme.colors.primaryVariant,
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
textColor = MaterialTheme.colors.onBackground,
|
||||
|
@ -1,104 +0,0 @@
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.extensions.isLocal
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.branchContextMenuItems
|
||||
import com.jetpackduba.gitnuro.viewmodels.BranchesViewModel
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
|
||||
@Composable
|
||||
fun Branches(
|
||||
branchesViewModel: BranchesViewModel = gitnuroViewModel(),
|
||||
) {
|
||||
val branches by branchesViewModel.branches.collectAsState()
|
||||
val currentBranchState = branchesViewModel.currentBranch.collectAsState()
|
||||
val isExpanded by branchesViewModel.isExpanded.collectAsState()
|
||||
val currentBranch = currentBranchState.value
|
||||
|
||||
SideMenuPanel(
|
||||
title = "Local branches",
|
||||
icon = painterResource("branch.svg"),
|
||||
items = branches,
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { branchesViewModel.onExpand() },
|
||||
itemContent = { branch ->
|
||||
BranchLineEntry(
|
||||
branch = branch,
|
||||
currentBranch = currentBranch,
|
||||
isCurrentBranch = currentBranch?.name == branch.name,
|
||||
onBranchClicked = { branchesViewModel.selectBranch(branch) },
|
||||
onBranchDoubleClicked = { branchesViewModel.checkoutRef(branch) },
|
||||
onCheckoutBranch = { branchesViewModel.checkoutRef(branch) },
|
||||
onMergeBranch = { branchesViewModel.mergeBranch(branch) },
|
||||
onDeleteBranch = { branchesViewModel.deleteBranch(branch) },
|
||||
onRebaseBranch = { branchesViewModel.rebaseBranch(branch) },
|
||||
onPushToRemoteBranch = { branchesViewModel.pushToRemoteBranch(branch) },
|
||||
onPullFromRemoteBranch = { branchesViewModel.pullFromRemoteBranch(branch) },
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun BranchLineEntry(
|
||||
branch: Ref,
|
||||
currentBranch: Ref?,
|
||||
isCurrentBranch: Boolean,
|
||||
onBranchClicked: () -> Unit,
|
||||
onBranchDoubleClicked: () -> Unit,
|
||||
onCheckoutBranch: () -> Unit,
|
||||
onMergeBranch: () -> Unit,
|
||||
onRebaseBranch: () -> Unit,
|
||||
onDeleteBranch: () -> Unit,
|
||||
onPushToRemoteBranch: () -> Unit,
|
||||
onPullFromRemoteBranch: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
branchContextMenuItems(
|
||||
branch = branch,
|
||||
currentBranch = currentBranch,
|
||||
isCurrentBranch = isCurrentBranch,
|
||||
isLocal = branch.isLocal,
|
||||
onCheckoutBranch = onCheckoutBranch,
|
||||
onMergeBranch = onMergeBranch,
|
||||
onDeleteBranch = onDeleteBranch,
|
||||
onRebaseBranch = onRebaseBranch,
|
||||
onPushToRemoteBranch = onPushToRemoteBranch,
|
||||
onPullFromRemoteBranch = onPullFromRemoteBranch,
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = branch.simpleName,
|
||||
iconResourcePath = "branch.svg",
|
||||
onClick = onBranchClicked,
|
||||
onDoubleClick = onBranchDoubleClicked,
|
||||
) {
|
||||
if (isCurrentBranch) {
|
||||
Text(
|
||||
text = "HEAD",
|
||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
@file:OptIn(ExperimentalComposeUiApi::class)
|
||||
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
|
||||
import com.jetpackduba.gitnuro.ui.components.VerticalExpandable
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.remoteBranchesContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.remoteContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.RemoteView
|
||||
import com.jetpackduba.gitnuro.viewmodels.RemotesViewModel
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
|
||||
@Composable
|
||||
fun Remotes(
|
||||
remotesViewModel: RemotesViewModel = gitnuroViewModel(),
|
||||
) {
|
||||
val remotes by remotesViewModel.remotes.collectAsState()
|
||||
var showEditRemotesDialog by remember { mutableStateOf(false) }
|
||||
val isExpanded by remotesViewModel.isExpanded.collectAsState()
|
||||
|
||||
if (showEditRemotesDialog) {
|
||||
EditRemotesDialog(
|
||||
remotesViewModel = remotesViewModel,
|
||||
onDismiss = {
|
||||
showEditRemotesDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
SideMenuPanel(
|
||||
title = "Remotes",
|
||||
icon = painterResource("cloud.svg"),
|
||||
items = remotes,
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { remotesViewModel.onExpand() },
|
||||
contextItems = {
|
||||
remoteContextMenu { showEditRemotesDialog = true }
|
||||
},
|
||||
headerHoverIcon = {
|
||||
IconButton(
|
||||
onClick = { showEditRemotesDialog = true },
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.size(16.dp)
|
||||
.handOnHover(),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource("settings.svg"),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
)
|
||||
}
|
||||
},
|
||||
itemContent = { remoteInfo ->
|
||||
RemoteRow(
|
||||
remote = remoteInfo,
|
||||
onBranchClicked = { branch -> remotesViewModel.selectBranch(branch) },
|
||||
onDeleteBranch = { branch -> remotesViewModel.deleteRemoteBranch(branch) },
|
||||
onRemoteClicked = { remotesViewModel.onRemoteClicked(remoteInfo) }
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun RemoteRow(
|
||||
remote: RemoteView,
|
||||
onRemoteClicked: () -> Unit,
|
||||
onBranchClicked: (Ref) -> Unit,
|
||||
onDeleteBranch: (Ref) -> Unit,
|
||||
) {
|
||||
VerticalExpandable(
|
||||
isExpanded = remote.isExpanded,
|
||||
onExpand = onRemoteClicked,
|
||||
header = {
|
||||
SideMenuSubentry(
|
||||
text = remote.remoteInfo.remoteConfig.name,
|
||||
iconResourcePath = "cloud.svg",
|
||||
)
|
||||
}
|
||||
) {
|
||||
val branches = remote.remoteInfo.branchesList
|
||||
Column {
|
||||
branches.forEach { branch ->
|
||||
ContextMenu(
|
||||
items = {
|
||||
remoteBranchesContextMenu(
|
||||
onDeleteBranch = { onDeleteBranch(branch) }
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = branch.simpleName,
|
||||
extraPadding = 24.dp,
|
||||
iconResourcePath = "branch.svg",
|
||||
onClick = { onBranchClicked(branch) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ package com.jetpackduba.gitnuro.ui
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
@ -17,17 +16,14 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.pointer.PointerIcon
|
||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.*
|
||||
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||
import com.jetpackduba.gitnuro.git.DiffEntryType
|
||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
import com.jetpackduba.gitnuro.ui.components.ScrollableColumn
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||
import com.jetpackduba.gitnuro.ui.diff.Diff
|
||||
import com.jetpackduba.gitnuro.ui.log.Log
|
||||
@ -252,39 +248,6 @@ fun RepoContent(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SidePanelOption(title: String, icon: String, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
.fillMaxWidth()
|
||||
.handMouseClickable(onClick)
|
||||
.padding(start = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
||||
Icon(
|
||||
painter = painterResource(icon),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
modifier = Modifier
|
||||
.size(16.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.weight(1f),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.body2,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class)
|
||||
@Composable
|
||||
fun MainContentView(
|
||||
@ -298,18 +261,7 @@ fun MainContentView(
|
||||
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
|
||||
) {
|
||||
first(minSize = 180.dp) {
|
||||
Column {
|
||||
ScrollableColumn(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
Branches()
|
||||
Remotes()
|
||||
Tags()
|
||||
Stashes()
|
||||
// TODO: Enable on 1.2.0 when fully implemented Submodules()
|
||||
}
|
||||
}
|
||||
SidePanel()
|
||||
}
|
||||
|
||||
splitter {
|
||||
|
419
src/main/kotlin/com/jetpackduba/gitnuro/ui/SidePanel.kt
Normal file
419
src/main/kotlin/com/jetpackduba/gitnuro/ui/SidePanel.kt
Normal file
@ -0,0 +1,419 @@
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||
import com.jetpackduba.gitnuro.extensions.isLocal
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.ui.components.*
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.*
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.*
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
|
||||
@Composable
|
||||
fun SidePanel(
|
||||
sidePanelViewModel: SidePanelViewModel = gitnuroViewModel(),
|
||||
branchesViewModel: BranchesViewModel = sidePanelViewModel.branchesViewModel,
|
||||
remotesViewModel: RemotesViewModel = sidePanelViewModel.remotesViewModel,
|
||||
tagsViewModel: TagsViewModel = sidePanelViewModel.tagsViewModel,
|
||||
stashesViewModel: StashesViewModel = sidePanelViewModel.stashesViewModel,
|
||||
) {
|
||||
var filter by remember(sidePanelViewModel) { mutableStateOf(sidePanelViewModel.filter.value) }
|
||||
|
||||
val branchesState by branchesViewModel.branchesState.collectAsState()
|
||||
val remotesState by remotesViewModel.remoteState.collectAsState()
|
||||
val tagsState by tagsViewModel.tagsState.collectAsState()
|
||||
val stashesState by stashesViewModel.stashesState.collectAsState()
|
||||
|
||||
var showEditRemotesDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Column {
|
||||
FilterTextField(
|
||||
value = filter,
|
||||
onValueChange = { newValue ->
|
||||
filter = newValue
|
||||
sidePanelViewModel.newFilter(newValue)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
|
||||
ScrollableLazyColumn(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = 4.dp)
|
||||
) {
|
||||
localBranches(
|
||||
branchesState = branchesState,
|
||||
branchesViewModel = branchesViewModel,
|
||||
)
|
||||
|
||||
remotes(
|
||||
remotesState = remotesState,
|
||||
remotesViewModel = remotesViewModel,
|
||||
onShowEditRemotesDialog = { showEditRemotesDialog = true },
|
||||
)
|
||||
|
||||
tags(
|
||||
tagsState = tagsState,
|
||||
tagsViewModel = tagsViewModel,
|
||||
)
|
||||
|
||||
stashes(
|
||||
stashesState = stashesState,
|
||||
stashesViewModel = stashesViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showEditRemotesDialog) {
|
||||
EditRemotesDialog(
|
||||
remotesViewModel = remotesViewModel,
|
||||
onDismiss = {
|
||||
showEditRemotesDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FilterTextField(value: String, onValueChange: (String) -> Unit, modifier: Modifier) {
|
||||
AdjustableOutlinedTextField(
|
||||
value = value,
|
||||
hint = "Search for branches, tags & more",
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
fontSize = MaterialTheme.typography.body2.fontSize,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
),
|
||||
maxLines = 1,
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource("search.svg"),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = if (value.isEmpty()) MaterialTheme.colors.onBackgroundSecondary else MaterialTheme.colors.onBackground
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun LazyListScope.localBranches(
|
||||
branchesState: BranchesState,
|
||||
branchesViewModel: BranchesViewModel,
|
||||
) {
|
||||
val isExpanded = branchesState.isExpanded
|
||||
val branches = branchesState.branches
|
||||
val currentBranch = branchesState.currentBranch
|
||||
|
||||
item {
|
||||
ContextMenu(
|
||||
items = { emptyList() }
|
||||
) {
|
||||
SideMenuHeader(
|
||||
text = "Local branches",
|
||||
icon = painterResource("branch.svg"),
|
||||
itemsCount = branches.count(),
|
||||
hoverIcon = null,
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { branchesViewModel.onExpand() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
items(branches, key = { it.name }) { branch ->
|
||||
Branch(
|
||||
branch = branch,
|
||||
currentBranch = currentBranch,
|
||||
isCurrentBranch = currentBranch?.name == branch.name,
|
||||
onBranchClicked = { branchesViewModel.selectBranch(branch) },
|
||||
onBranchDoubleClicked = { branchesViewModel.checkoutRef(branch) },
|
||||
onCheckoutBranch = { branchesViewModel.checkoutRef(branch) },
|
||||
onMergeBranch = { branchesViewModel.mergeBranch(branch) },
|
||||
onDeleteBranch = { branchesViewModel.deleteBranch(branch) },
|
||||
onRebaseBranch = { branchesViewModel.rebaseBranch(branch) },
|
||||
onPushToRemoteBranch = { branchesViewModel.pushToRemoteBranch(branch) },
|
||||
onPullFromRemoteBranch = { branchesViewModel.pullFromRemoteBranch(branch) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.remotes(
|
||||
remotesState: RemotesState,
|
||||
remotesViewModel: RemotesViewModel,
|
||||
onShowEditRemotesDialog: () -> Unit,
|
||||
) {
|
||||
val isExpanded = remotesState.isExpanded
|
||||
val remotes = remotesState.remotes
|
||||
|
||||
item {
|
||||
ContextMenu(
|
||||
items = { remoteBranchesContextMenu(onShowEditRemotesDialog) }
|
||||
) {
|
||||
SideMenuHeader(
|
||||
text = "Remotes",
|
||||
icon = painterResource("cloud.svg"),
|
||||
itemsCount = remotes.count(),
|
||||
hoverIcon = {
|
||||
IconButton(
|
||||
onClick = onShowEditRemotesDialog,
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.size(16.dp)
|
||||
.handOnHover(),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource("settings.svg"),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
)
|
||||
}
|
||||
},
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { remotesViewModel.onExpand() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
for (remote in remotes) {
|
||||
item {
|
||||
Remote(
|
||||
remote = remote,
|
||||
onRemoteClicked = { remotesViewModel.onRemoteClicked(remote) }
|
||||
)
|
||||
}
|
||||
|
||||
if (remote.isExpanded) {
|
||||
items(remote.remoteInfo.branchesList) { remoteBranch ->
|
||||
RemoteBranches(
|
||||
remoteBranch = remoteBranch,
|
||||
onBranchClicked = { remotesViewModel.selectBranch(remoteBranch) },
|
||||
onDeleteBranch = { remotesViewModel.deleteRemoteBranch(remoteBranch) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun LazyListScope.tags(
|
||||
tagsState: TagsState,
|
||||
tagsViewModel: TagsViewModel,
|
||||
) {
|
||||
val isExpanded = tagsState.isExpanded
|
||||
val tags = tagsState.tags
|
||||
|
||||
item {
|
||||
ContextMenu(
|
||||
items = { emptyList() }
|
||||
) {
|
||||
SideMenuHeader(
|
||||
text = "Tags",
|
||||
icon = painterResource("tag.svg"),
|
||||
itemsCount = tags.count(),
|
||||
hoverIcon = null,
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { tagsViewModel.onExpand() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
items(tags, key = { it.name }) { tag ->
|
||||
// if () {
|
||||
Tag(
|
||||
tag,
|
||||
onTagClicked = { tagsViewModel.selectTag(tag) },
|
||||
onCheckoutTag = { tagsViewModel.checkoutRef(tag) },
|
||||
onDeleteTag = { tagsViewModel.deleteTag(tag) }
|
||||
)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.stashes(
|
||||
stashesState: StashesState,
|
||||
stashesViewModel: StashesViewModel,
|
||||
) {
|
||||
val isExpanded = stashesState.isExpanded
|
||||
val stashes = stashesState.stashes
|
||||
|
||||
item {
|
||||
ContextMenu(
|
||||
items = { emptyList() }
|
||||
) {
|
||||
SideMenuHeader(
|
||||
text = "Stashes",
|
||||
icon = painterResource("stash.svg"),
|
||||
itemsCount = stashes.count(),
|
||||
hoverIcon = null,
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { stashesViewModel.onExpand() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
items(stashes, key = { it.name }) { stash ->
|
||||
Stash(
|
||||
stash,
|
||||
onClick = { stashesViewModel.selectStash(stash) },
|
||||
onApply = { stashesViewModel.applyStash(stash) },
|
||||
onPop = { stashesViewModel.popStash(stash) },
|
||||
onDelete = { stashesViewModel.deleteStash(stash) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Branch(
|
||||
branch: Ref,
|
||||
currentBranch: Ref?,
|
||||
isCurrentBranch: Boolean,
|
||||
onBranchClicked: () -> Unit,
|
||||
onBranchDoubleClicked: () -> Unit,
|
||||
onCheckoutBranch: () -> Unit,
|
||||
onMergeBranch: () -> Unit,
|
||||
onRebaseBranch: () -> Unit,
|
||||
onDeleteBranch: () -> Unit,
|
||||
onPushToRemoteBranch: () -> Unit,
|
||||
onPullFromRemoteBranch: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
branchContextMenuItems(
|
||||
branch = branch,
|
||||
currentBranch = currentBranch,
|
||||
isCurrentBranch = isCurrentBranch,
|
||||
isLocal = branch.isLocal,
|
||||
onCheckoutBranch = onCheckoutBranch,
|
||||
onMergeBranch = onMergeBranch,
|
||||
onDeleteBranch = onDeleteBranch,
|
||||
onRebaseBranch = onRebaseBranch,
|
||||
onPushToRemoteBranch = onPushToRemoteBranch,
|
||||
onPullFromRemoteBranch = onPullFromRemoteBranch,
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = branch.simpleName,
|
||||
iconResourcePath = "branch.svg",
|
||||
onClick = onBranchClicked,
|
||||
onDoubleClick = onBranchDoubleClicked,
|
||||
) {
|
||||
if (isCurrentBranch) {
|
||||
Text(
|
||||
text = "HEAD",
|
||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun Remote(
|
||||
remote: RemoteView,
|
||||
onRemoteClicked: () -> Unit,
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = remote.remoteInfo.remoteConfig.name,
|
||||
iconResourcePath = "cloud.svg",
|
||||
onClick = onRemoteClicked
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun RemoteBranches(
|
||||
remoteBranch: Ref,
|
||||
onBranchClicked: () -> Unit,
|
||||
onDeleteBranch: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
remoteBranchesContextMenu(
|
||||
onDeleteBranch = onDeleteBranch
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = remoteBranch.simpleName,
|
||||
extraPadding = 24.dp,
|
||||
iconResourcePath = "branch.svg",
|
||||
onClick = onBranchClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Tag(
|
||||
tag: Ref,
|
||||
onTagClicked: () -> Unit,
|
||||
onCheckoutTag: () -> Unit,
|
||||
onDeleteTag: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
tagContextMenuItems(
|
||||
onCheckoutTag = onCheckoutTag,
|
||||
onDeleteTag = onDeleteTag,
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = tag.simpleName,
|
||||
iconResourcePath = "tag.svg",
|
||||
onClick = onTagClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun Stash(
|
||||
stash: RevCommit,
|
||||
onClick: () -> Unit,
|
||||
onApply: () -> Unit,
|
||||
onPop: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
stashesContextMenuItems(
|
||||
onApply = onApply,
|
||||
onPop = onPop,
|
||||
onDelete = onDelete,
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = stash.shortMessage,
|
||||
iconResourcePath = "stash.svg",
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.stashesContextMenuItems
|
||||
import com.jetpackduba.gitnuro.viewmodels.StashStatus
|
||||
import com.jetpackduba.gitnuro.viewmodels.StashesViewModel
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
|
||||
@Composable
|
||||
fun Stashes(
|
||||
stashesViewModel: StashesViewModel = gitnuroViewModel(),
|
||||
) {
|
||||
val stashStatusState = stashesViewModel.stashStatus.collectAsState()
|
||||
val stashStatus = stashStatusState.value
|
||||
val isExpanded by stashesViewModel.isExpanded.collectAsState()
|
||||
|
||||
val stashList = if (stashStatus is StashStatus.Loaded)
|
||||
stashStatus.stashes
|
||||
else
|
||||
listOf()
|
||||
|
||||
SideMenuPanel(
|
||||
title = "Stashes",
|
||||
icon = painterResource("stash.svg"),
|
||||
items = stashList,
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { stashesViewModel.onExpand() },
|
||||
itemContent = { stash ->
|
||||
StashRow(
|
||||
stash = stash,
|
||||
onClick = { stashesViewModel.selectTab(stash) },
|
||||
contextItems = stashesContextMenuItems(
|
||||
onApply = { stashesViewModel.applyStash(stash) },
|
||||
onPop = {
|
||||
stashesViewModel.popStash(stash)
|
||||
},
|
||||
onDelete = {
|
||||
stashesViewModel.deleteStash(stash)
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StashRow(
|
||||
stash: RevCommit,
|
||||
onClick: () -> Unit,
|
||||
contextItems: List<ContextMenuElement>,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = { contextItems }
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = stash.shortMessage,
|
||||
iconResourcePath = "stash.svg",
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import com.jetpackduba.gitnuro.ui.components.Tooltip
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.submoduleContextMenuItems
|
||||
import com.jetpackduba.gitnuro.viewmodels.SubmodulesViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.SubmodulesViewModel
|
||||
import org.eclipse.jgit.submodule.SubmoduleStatus
|
||||
|
||||
@Composable
|
||||
|
@ -1,65 +0,0 @@
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
|
||||
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
|
||||
import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.tagContextMenuItems
|
||||
import com.jetpackduba.gitnuro.viewmodels.TagsViewModel
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Tags(
|
||||
tagsViewModel: TagsViewModel = gitnuroViewModel(),
|
||||
) {
|
||||
val tagsState = tagsViewModel.tags.collectAsState()
|
||||
val tags = tagsState.value
|
||||
val isExpanded by tagsViewModel.isExpanded.collectAsState()
|
||||
|
||||
SideMenuPanel(
|
||||
title = "Tags",
|
||||
items = tags,
|
||||
icon = painterResource("tag.svg"),
|
||||
isExpanded = isExpanded,
|
||||
onExpand = { tagsViewModel.onExpand() },
|
||||
itemContent = { tag ->
|
||||
TagRow(
|
||||
tag = tag,
|
||||
onTagClicked = { tagsViewModel.selectTag(tag) },
|
||||
onCheckoutTag = { tagsViewModel.checkoutRef(tag) },
|
||||
onDeleteTag = { tagsViewModel.deleteTag(tag) }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun TagRow(
|
||||
tag: Ref,
|
||||
onTagClicked: () -> Unit,
|
||||
onCheckoutTag: () -> Unit,
|
||||
onDeleteTag: () -> Unit,
|
||||
) {
|
||||
ContextMenu(
|
||||
items = {
|
||||
tagContextMenuItems(
|
||||
onCheckoutTag = onCheckoutTag,
|
||||
onDeleteTag = onDeleteTag,
|
||||
)
|
||||
}
|
||||
) {
|
||||
SideMenuSubentry(
|
||||
text = tag.simpleName,
|
||||
iconResourcePath = "tag.svg",
|
||||
onClick = onTagClicked,
|
||||
)
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
||||
@ -46,6 +47,7 @@ fun AdjustableOutlinedTextField(
|
||||
shape: Shape = RoundedCornerShape(4.dp),
|
||||
backgroundColor: Color = MaterialTheme.colors.background,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
val textColor = textStyle.color.takeOrElse {
|
||||
colors.textColor(enabled).value
|
||||
@ -60,7 +62,7 @@ fun AdjustableOutlinedTextField(
|
||||
) {
|
||||
BasicTextField(
|
||||
modifier = Modifier
|
||||
.heightIn(min = 40.dp)
|
||||
.heightIn(min = 38.dp)
|
||||
.background(backgroundColor)
|
||||
.fillMaxWidth(),
|
||||
value = value,
|
||||
@ -74,29 +76,44 @@ fun AdjustableOutlinedTextField(
|
||||
singleLine = singleLine,
|
||||
visualTransformation = visualTransformation,
|
||||
decorationBox = { innerTextField ->
|
||||
Box(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 1.dp,
|
||||
width = 2.dp,
|
||||
color = indicatorColor,
|
||||
shape = shape
|
||||
)
|
||||
.padding(horizontal = 12.dp),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (leadingIcon != null) {
|
||||
leadingIcon()
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 12.dp),
|
||||
) {
|
||||
if (leadingIcon != null) {
|
||||
leadingIcon()
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
|
||||
if (value.isEmpty() && hint.isNotEmpty()) {
|
||||
Text(
|
||||
hint,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, top = 12.dp),
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,15 +17,17 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
|
||||
@Composable
|
||||
fun SideMenuEntry(
|
||||
fun SideMenuHeader(
|
||||
text: String,
|
||||
icon: Painter? = null,
|
||||
itemsCount: Int,
|
||||
isExpanded: Boolean,
|
||||
onExpand: () -> Unit = {},
|
||||
hoverIcon: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
val hoverInteraction = remember { MutableInteractionSource() }
|
||||
@ -35,7 +37,8 @@ fun SideMenuEntry(
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
.fillMaxWidth()
|
||||
.hoverable(hoverInteraction),
|
||||
.hoverable(hoverInteraction)
|
||||
.handMouseClickable { onExpand() },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
|
@ -26,7 +26,7 @@ fun <T> SideMenuPanel(
|
||||
ContextMenu(
|
||||
items = contextItems
|
||||
) {
|
||||
SideMenuEntry(
|
||||
SideMenuHeader(
|
||||
text = title,
|
||||
icon = icon,
|
||||
itemsCount = items.count(),
|
||||
|
@ -4,7 +4,10 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.runtime.*
|
||||
@ -16,12 +19,11 @@ import androidx.compose.ui.unit.dp
|
||||
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||
import com.jetpackduba.gitnuro.theme.backgroundSelected
|
||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||
import com.jetpackduba.gitnuro.theme.textButtonColors
|
||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
||||
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
import com.jetpackduba.gitnuro.viewmodels.RemotesViewModel
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.RemotesViewModel
|
||||
import org.eclipse.jgit.transport.RemoteConfig
|
||||
|
||||
@Composable
|
||||
@ -38,7 +40,8 @@ fun EditRemotesDialog(
|
||||
)
|
||||
}
|
||||
|
||||
val remotes by remotesViewModel.remotes.collectAsState()
|
||||
val remotesState by remotesViewModel.remoteState.collectAsState()
|
||||
val remotes = remotesState.remotes
|
||||
var remoteChanged by remember { mutableStateOf(false) }
|
||||
val selectedRemote = remotesEditorData.selectedRemote
|
||||
|
||||
@ -343,7 +346,7 @@ data class RemoteWrapper(
|
||||
) {
|
||||
val haveUrisChanged: Boolean = isNew ||
|
||||
fetchUri != originalFetchUri ||
|
||||
pushUri.toString() != originalPushUri
|
||||
pushUri != originalPushUri
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,22 +1,19 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
|
||||
import com.jetpackduba.gitnuro.di.TabScope
|
||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@TabScope
|
||||
class TabViewModelsHolder @Inject constructor(
|
||||
logViewModel: LogViewModel,
|
||||
branchesViewModel: BranchesViewModel,
|
||||
tagsViewModel: TagsViewModel,
|
||||
remotesViewModel: RemotesViewModel,
|
||||
statusViewModel: StatusViewModel,
|
||||
menuViewModel: MenuViewModel,
|
||||
stashesViewModel: StashesViewModel,
|
||||
submodulesViewModel: SubmodulesViewModel,
|
||||
commitChangesViewModel: CommitChangesViewModel,
|
||||
cloneViewModel: CloneViewModel,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
sidePanelViewModel: SidePanelViewModel,
|
||||
// Dynamic VM
|
||||
private val diffViewModelProvider: Provider<DiffViewModel>,
|
||||
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
||||
@ -25,13 +22,9 @@ class TabViewModelsHolder @Inject constructor(
|
||||
) {
|
||||
val viewModels = mapOf(
|
||||
logViewModel::class to logViewModel,
|
||||
branchesViewModel::class to branchesViewModel,
|
||||
tagsViewModel::class to tagsViewModel,
|
||||
remotesViewModel::class to remotesViewModel,
|
||||
sidePanelViewModel::class to sidePanelViewModel,
|
||||
statusViewModel::class to statusViewModel,
|
||||
menuViewModel::class to menuViewModel,
|
||||
stashesViewModel::class to stashesViewModel,
|
||||
submodulesViewModel::class to submodulesViewModel,
|
||||
commitChangesViewModel::class to commitChangesViewModel,
|
||||
cloneViewModel::class to cloneViewModel,
|
||||
settingsViewModel::class to settingsViewModel,
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import com.jetpackduba.gitnuro.extensions.lowercaseContains
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.git.RefreshType
|
||||
import com.jetpackduba.gitnuro.git.TabState
|
||||
import com.jetpackduba.gitnuro.git.branches.*
|
||||
@ -7,9 +9,10 @@ import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase
|
||||
import com.jetpackduba.gitnuro.git.remote_operations.PullFromSpecificBranchUseCase
|
||||
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
|
||||
import com.jetpackduba.gitnuro.preferences.AppSettings
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
@ -17,7 +20,8 @@ import javax.inject.Inject
|
||||
|
||||
private const val TAG = "BranchesViewModel"
|
||||
|
||||
class BranchesViewModel @Inject constructor(
|
||||
|
||||
class BranchesViewModel @AssistedInject constructor(
|
||||
private val rebaseBranchUseCase: RebaseBranchUseCase,
|
||||
private val tabState: TabState,
|
||||
private val appSettings: AppSettings,
|
||||
@ -30,16 +34,26 @@ class BranchesViewModel @Inject constructor(
|
||||
private val deleteBranchUseCase: DeleteBranchUseCase,
|
||||
private val checkoutRefUseCase: CheckoutRefUseCase,
|
||||
private val tabScope: CoroutineScope,
|
||||
) : ExpandableViewModel(true) {
|
||||
@Assisted
|
||||
private val filter: StateFlow<String>
|
||||
) : SidePanelChildViewModel(true) {
|
||||
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
||||
val branches: StateFlow<List<Ref>>
|
||||
get() = _branches
|
||||
|
||||
private val _currentBranch = MutableStateFlow<Ref?>(null)
|
||||
val currentBranch: StateFlow<Ref?>
|
||||
get() = _currentBranch
|
||||
|
||||
val branchesState = combine(_branches, _currentBranch, isExpanded, filter) { branches, currentBranch, isExpanded, filter ->
|
||||
BranchesState(
|
||||
branches = branches.filter { it.simpleName.lowercaseContains(filter) },
|
||||
isExpanded = isExpanded,
|
||||
currentBranch = currentBranch
|
||||
)
|
||||
}.stateIn(
|
||||
scope = tabScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = BranchesState(emptyList(), isExpanded.value, null)
|
||||
)
|
||||
|
||||
init {
|
||||
|
||||
tabScope.launch {
|
||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA)
|
||||
{
|
||||
@ -117,3 +131,9 @@ class BranchesViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class BranchesState(
|
||||
val branches: List<Ref>,
|
||||
val isExpanded: Boolean,
|
||||
val currentBranch: Ref?,
|
||||
)
|
@ -1,6 +1,8 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import com.jetpackduba.gitnuro.exceptions.InvalidRemoteUrlException
|
||||
import com.jetpackduba.gitnuro.extensions.lowercaseContains
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.git.RefreshType
|
||||
import com.jetpackduba.gitnuro.git.TabState
|
||||
import com.jetpackduba.gitnuro.git.branches.DeleteLocallyRemoteBranchesUseCase
|
||||
@ -8,18 +10,18 @@ import com.jetpackduba.gitnuro.git.branches.GetRemoteBranchesUseCase
|
||||
import com.jetpackduba.gitnuro.git.remote_operations.DeleteRemoteBranchUseCase
|
||||
import com.jetpackduba.gitnuro.git.remotes.*
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.RemoteWrapper
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.api.RemoteSetUrlCommand
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemotesViewModel @Inject constructor(
|
||||
class RemotesViewModel @AssistedInject constructor(
|
||||
private val tabState: TabState,
|
||||
private val deleteRemoteBranchUseCase: DeleteRemoteBranchUseCase,
|
||||
private val getRemoteBranchesUseCase: GetRemoteBranchesUseCase,
|
||||
@ -29,15 +31,37 @@ class RemotesViewModel @Inject constructor(
|
||||
private val updateRemoteUseCase: UpdateRemoteUseCase,
|
||||
private val deleteLocallyRemoteBranchesUseCase: DeleteLocallyRemoteBranchesUseCase,
|
||||
private val tabScope: CoroutineScope,
|
||||
) : ExpandableViewModel() {
|
||||
private val _remotes = MutableStateFlow<List<RemoteView>>(listOf())
|
||||
val remotes: StateFlow<List<RemoteView>>
|
||||
get() = _remotes
|
||||
@Assisted
|
||||
private val filter: StateFlow<String>
|
||||
) : SidePanelChildViewModel(false) {
|
||||
private val remotes = MutableStateFlow<List<RemoteView>>(listOf())
|
||||
|
||||
val remoteState: StateFlow<RemotesState> = combine(remotes, isExpanded, filter) { remotes, isExpanded, filter ->
|
||||
val remotesFiltered = remotes.map { remote ->
|
||||
val remoteInfo = remote.remoteInfo
|
||||
|
||||
val newRemoteInfo = remoteInfo.copy(
|
||||
branchesList = remoteInfo.branchesList.filter { branch ->
|
||||
branch.simpleName.lowercaseContains(filter)
|
||||
}
|
||||
)
|
||||
|
||||
remote.copy(remoteInfo = newRemoteInfo)
|
||||
}
|
||||
|
||||
RemotesState(
|
||||
remotesFiltered,
|
||||
isExpanded
|
||||
)
|
||||
}.stateIn(
|
||||
scope = tabScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = RemotesState(emptyList(), isExpanded.value)
|
||||
)
|
||||
|
||||
init {
|
||||
tabScope.launch {
|
||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REMOTES)
|
||||
{
|
||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REMOTES) {
|
||||
refresh(tabState.git)
|
||||
}
|
||||
}
|
||||
@ -61,7 +85,7 @@ class RemotesViewModel @Inject constructor(
|
||||
RemoteView(remoteInfo, true)
|
||||
}
|
||||
|
||||
_remotes.value = remoteViewList
|
||||
this@RemotesViewModel.remotes.value = remoteViewList
|
||||
}
|
||||
|
||||
fun deleteRemoteBranch(ref: Ref) = tabState.safeProcessing(
|
||||
@ -74,14 +98,19 @@ class RemotesViewModel @Inject constructor(
|
||||
loadRemotes(git)
|
||||
}
|
||||
|
||||
fun onRemoteClicked(remoteInfo: RemoteView) {
|
||||
val remotes = _remotes.value
|
||||
val newRemoteInfo = remoteInfo.copy(isExpanded = !remoteInfo.isExpanded)
|
||||
fun onRemoteClicked(remoteClicked: RemoteView) {
|
||||
val remoteName = remoteClicked.remoteInfo.remoteConfig.name
|
||||
val remotes = this.remotes.value
|
||||
val remoteInfo = remotes.firstOrNull { it.remoteInfo.remoteConfig.name == remoteName }
|
||||
|
||||
if(remoteInfo != null) {
|
||||
val newRemoteInfo = remoteInfo.copy(isExpanded = !remoteClicked.isExpanded)
|
||||
val newRemotesList = remotes.toMutableList()
|
||||
val indexToReplace = newRemotesList.indexOf(remoteInfo)
|
||||
newRemotesList[indexToReplace] = newRemoteInfo
|
||||
|
||||
_remotes.value = newRemotesList
|
||||
this.remotes.value = newRemotesList
|
||||
}
|
||||
}
|
||||
|
||||
fun selectBranch(ref: Ref) {
|
||||
@ -152,3 +181,5 @@ class RemotesViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
data class RemoteView(val remoteInfo: RemoteInfo, val isExpanded: Boolean)
|
||||
|
||||
data class RemotesState(val remotes: List<RemoteView>, val isExpanded: Boolean)
|
@ -1,9 +1,9 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
abstract class ExpandableViewModel(expandedDefault: Boolean = false) {
|
||||
abstract class SidePanelChildViewModel(expandedDefault: Boolean) {
|
||||
private val _isExpanded = MutableStateFlow(expandedDefault)
|
||||
val isExpanded: StateFlow<Boolean> = _isExpanded
|
||||
|
@ -0,0 +1,28 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import com.jetpackduba.gitnuro.di.factories.BranchesViewModelFactory
|
||||
import com.jetpackduba.gitnuro.di.factories.RemotesViewModelFactory
|
||||
import com.jetpackduba.gitnuro.di.factories.StashesViewModelFactory
|
||||
import com.jetpackduba.gitnuro.di.factories.TagsViewModelFactory
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
class SidePanelViewModel @Inject constructor(
|
||||
branchesViewModelFactory: BranchesViewModelFactory,
|
||||
remotesViewModelFactory: RemotesViewModelFactory,
|
||||
tagsViewModelFactory: TagsViewModelFactory,
|
||||
stashesViewModelFactory: StashesViewModelFactory,
|
||||
) {
|
||||
private val _filter = MutableStateFlow("")
|
||||
val filter: StateFlow<String> = _filter
|
||||
|
||||
val branchesViewModel: BranchesViewModel = branchesViewModelFactory.create(filter)
|
||||
val remotesViewModel: RemotesViewModel = remotesViewModelFactory.create(filter)
|
||||
val tagsViewModel: TagsViewModel = tagsViewModelFactory.create(filter)
|
||||
val stashesViewModel: StashesViewModel = stashesViewModelFactory.create(filter)
|
||||
|
||||
fun newFilter(newValue: String) {
|
||||
_filter.value = newValue
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import com.jetpackduba.gitnuro.extensions.lowercaseContains
|
||||
import com.jetpackduba.gitnuro.git.RefreshType
|
||||
import com.jetpackduba.gitnuro.git.TabState
|
||||
import com.jetpackduba.gitnuro.git.stash.ApplyStashUseCase
|
||||
@ -7,26 +8,36 @@ import com.jetpackduba.gitnuro.git.stash.DeleteStashUseCase
|
||||
import com.jetpackduba.gitnuro.git.stash.GetStashListUseCase
|
||||
import com.jetpackduba.gitnuro.git.stash.PopStashUseCase
|
||||
import com.jetpackduba.gitnuro.ui.SelectedItem
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import javax.inject.Inject
|
||||
|
||||
class StashesViewModel @Inject constructor(
|
||||
class StashesViewModel @AssistedInject constructor(
|
||||
private val getStashListUseCase: GetStashListUseCase,
|
||||
private val applyStashUseCase: ApplyStashUseCase,
|
||||
private val popStashUseCase: PopStashUseCase,
|
||||
private val deleteStashUseCase: DeleteStashUseCase,
|
||||
private val tabState: TabState,
|
||||
private val tabScope: CoroutineScope,
|
||||
) : ExpandableViewModel(true) {
|
||||
private val _stashStatus = MutableStateFlow<StashStatus>(StashStatus.Loaded(listOf()))
|
||||
@Assisted
|
||||
private val filter: StateFlow<String>,
|
||||
) : SidePanelChildViewModel(true) {
|
||||
private val stashes = MutableStateFlow<List<RevCommit>>(emptyList())
|
||||
|
||||
val stashStatus: StateFlow<StashStatus>
|
||||
get() = _stashStatus
|
||||
val stashesState: StateFlow<StashesState> = combine(stashes, isExpanded, filter) { stashes, isExpanded, filter ->
|
||||
StashesState(
|
||||
stashes = stashes.filter { it.fullMessage.lowercaseContains(filter) },
|
||||
isExpanded,
|
||||
)
|
||||
}.stateIn(
|
||||
tabScope,
|
||||
SharingStarted.Eagerly,
|
||||
StashesState(emptyList(), isExpanded.value)
|
||||
)
|
||||
|
||||
init {
|
||||
tabScope.launch {
|
||||
@ -41,9 +52,8 @@ class StashesViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun loadStashes(git: Git) {
|
||||
_stashStatus.value = StashStatus.Loading
|
||||
val stashList = getStashListUseCase(git)
|
||||
_stashStatus.value = StashStatus.Loaded(stashList.toList())
|
||||
stashes.value = stashList
|
||||
}
|
||||
|
||||
suspend fun refresh(git: Git) {
|
||||
@ -74,7 +84,7 @@ class StashesViewModel @Inject constructor(
|
||||
stashDropped(stash)
|
||||
}
|
||||
|
||||
fun selectTab(stash: RevCommit) = tabState.runOperation(
|
||||
fun selectStash(stash: RevCommit) = tabState.runOperation(
|
||||
refreshType = RefreshType.NONE,
|
||||
) {
|
||||
tabState.newSelectedStash(stash)
|
||||
@ -94,7 +104,4 @@ class StashesViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
|
||||
sealed class StashStatus {
|
||||
object Loading : StashStatus()
|
||||
data class Loaded(val stashes: List<RevCommit>) : StashStatus()
|
||||
}
|
||||
data class StashesState(val stashes: List<RevCommit>, val isExpanded: Boolean)
|
@ -1,4 +1,4 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import com.jetpackduba.gitnuro.git.RefreshType
|
||||
import com.jetpackduba.gitnuro.git.TabState
|
||||
@ -19,7 +19,7 @@ class SubmodulesViewModel @Inject constructor(
|
||||
private val initializeSubmoduleUseCase: InitializeSubmoduleUseCase,
|
||||
private val updateSubmoduleUseCase: UpdateSubmoduleUseCase,
|
||||
private val tabScope: CoroutineScope,
|
||||
) : ExpandableViewModel() {
|
||||
) : SidePanelChildViewModel(true) {
|
||||
private val _submodules = MutableStateFlow<List<Pair<String, SubmoduleStatus>>>(listOf())
|
||||
val submodules: StateFlow<List<Pair<String, SubmoduleStatus>>>
|
||||
get() = _submodules
|
@ -1,30 +1,43 @@
|
||||
package com.jetpackduba.gitnuro.viewmodels
|
||||
package com.jetpackduba.gitnuro.viewmodels.sidepanel
|
||||
|
||||
import com.jetpackduba.gitnuro.extensions.lowercaseContains
|
||||
import com.jetpackduba.gitnuro.extensions.simpleName
|
||||
import com.jetpackduba.gitnuro.git.RefreshType
|
||||
import com.jetpackduba.gitnuro.git.TabState
|
||||
import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase
|
||||
import com.jetpackduba.gitnuro.git.tags.DeleteTagUseCase
|
||||
import com.jetpackduba.gitnuro.git.tags.GetTagsUseCase
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
import javax.inject.Inject
|
||||
|
||||
class TagsViewModel @Inject constructor(
|
||||
class TagsViewModel @AssistedInject constructor(
|
||||
private val tabState: TabState,
|
||||
private val getTagsUseCase: GetTagsUseCase,
|
||||
private val deleteTagUseCase: DeleteTagUseCase,
|
||||
private val checkoutRefUseCase: CheckoutRefUseCase,
|
||||
private val tabScope: CoroutineScope,
|
||||
) : ExpandableViewModel() {
|
||||
private val _tags = MutableStateFlow<List<Ref>>(listOf())
|
||||
val tags: StateFlow<List<Ref>>
|
||||
get() = _tags
|
||||
@Assisted
|
||||
private val filter: StateFlow<String>
|
||||
) : SidePanelChildViewModel(false) {
|
||||
private val tags = MutableStateFlow<List<Ref>>(listOf())
|
||||
|
||||
val tagsState: StateFlow<TagsState> = combine(tags, isExpanded, filter) { tags, isExpanded, filter ->
|
||||
TagsState(
|
||||
tags.filter { tag -> tag.simpleName.lowercaseContains(filter) },
|
||||
isExpanded,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = tabScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = TagsState(emptyList(), isExpanded.value)
|
||||
)
|
||||
|
||||
init {
|
||||
tabScope.launch {
|
||||
@ -38,7 +51,7 @@ class TagsViewModel @Inject constructor(
|
||||
private suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
|
||||
val tagsList = getTagsUseCase(git)
|
||||
|
||||
_tags.value = tagsList
|
||||
tags.value = tagsList
|
||||
}
|
||||
|
||||
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
|
||||
@ -61,3 +74,5 @@ class TagsViewModel @Inject constructor(
|
||||
loadTags(git)
|
||||
}
|
||||
}
|
||||
|
||||
data class TagsState(val tags: List<Ref>, val isExpanded: Boolean)
|
1
src/main/resources/search.svg
Normal file
1
src/main/resources/search.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
After Width: | Height: | Size: 391 B |
Loading…
Reference in New Issue
Block a user