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?
|
val String.nullIfEmpty: String?
|
||||||
get() = this.ifBlank { null }
|
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(
|
fun outlinedTextFieldColors() = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
cursorColor = MaterialTheme.colors.primaryVariant,
|
cursorColor = MaterialTheme.colors.primaryVariant,
|
||||||
focusedBorderColor = MaterialTheme.colors.primaryVariant,
|
focusedBorderColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
unfocusedBorderColor = MaterialTheme.colors.onBackgroundSecondary.copy(alpha = 0.2f),
|
||||||
focusedLabelColor = MaterialTheme.colors.primaryVariant,
|
focusedLabelColor = MaterialTheme.colors.primaryVariant,
|
||||||
backgroundColor = MaterialTheme.colors.background,
|
backgroundColor = MaterialTheme.colors.background,
|
||||||
textColor = MaterialTheme.colors.onBackground,
|
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.background
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.*
|
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.key.onKeyEvent
|
||||||
import androidx.compose.ui.input.pointer.PointerIcon
|
import androidx.compose.ui.input.pointer.PointerIcon
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
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.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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.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.components.PrimaryButton
|
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.dialogs.*
|
||||||
import com.jetpackduba.gitnuro.ui.diff.Diff
|
import com.jetpackduba.gitnuro.ui.diff.Diff
|
||||||
import com.jetpackduba.gitnuro.ui.log.Log
|
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)
|
@OptIn(ExperimentalSplitPaneApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainContentView(
|
fun MainContentView(
|
||||||
@ -298,18 +261,7 @@ fun MainContentView(
|
|||||||
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
|
splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.20f)
|
||||||
) {
|
) {
|
||||||
first(minSize = 180.dp) {
|
first(minSize = 180.dp) {
|
||||||
Column {
|
SidePanel()
|
||||||
ScrollableColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f),
|
|
||||||
) {
|
|
||||||
Branches()
|
|
||||||
Remotes()
|
|
||||||
Tags()
|
|
||||||
Stashes()
|
|
||||||
// TODO: Enable on 1.2.0 when fully implemented Submodules()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
splitter {
|
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.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.submoduleContextMenuItems
|
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
|
import org.eclipse.jgit.submodule.SubmoduleStatus
|
||||||
|
|
||||||
@Composable
|
@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.graphics.takeOrElse
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
||||||
@ -46,6 +47,7 @@ fun AdjustableOutlinedTextField(
|
|||||||
shape: Shape = RoundedCornerShape(4.dp),
|
shape: Shape = RoundedCornerShape(4.dp),
|
||||||
backgroundColor: Color = MaterialTheme.colors.background,
|
backgroundColor: Color = MaterialTheme.colors.background,
|
||||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val textColor = textStyle.color.takeOrElse {
|
val textColor = textStyle.color.takeOrElse {
|
||||||
colors.textColor(enabled).value
|
colors.textColor(enabled).value
|
||||||
@ -60,7 +62,7 @@ fun AdjustableOutlinedTextField(
|
|||||||
) {
|
) {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.heightIn(min = 40.dp)
|
.heightIn(min = 38.dp)
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
value = value,
|
value = value,
|
||||||
@ -74,29 +76,44 @@ fun AdjustableOutlinedTextField(
|
|||||||
singleLine = singleLine,
|
singleLine = singleLine,
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
Box(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.border(
|
.border(
|
||||||
width = 1.dp,
|
width = 2.dp,
|
||||||
color = indicatorColor,
|
color = indicatorColor,
|
||||||
shape = shape
|
shape = shape
|
||||||
)
|
)
|
||||||
.padding(horizontal = 12.dp),
|
.padding(horizontal = 12.dp),
|
||||||
contentAlignment = Alignment.CenterStart,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
if (leadingIcon != null) {
|
||||||
|
leadingIcon()
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
innerTextField()
|
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()) {
|
if (value.isEmpty() && hint.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
hint,
|
hint,
|
||||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
maxLines = 1,
|
||||||
modifier = Modifier
|
overflow = TextOverflow.Ellipsis,
|
||||||
.padding(start = 12.dp, top = 12.dp),
|
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||||
style = MaterialTheme.typography.body2
|
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.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||||
|
|
||||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SideMenuEntry(
|
fun SideMenuHeader(
|
||||||
text: String,
|
text: String,
|
||||||
icon: Painter? = null,
|
icon: Painter? = null,
|
||||||
itemsCount: Int,
|
itemsCount: Int,
|
||||||
isExpanded: Boolean,
|
isExpanded: Boolean,
|
||||||
|
onExpand: () -> Unit = {},
|
||||||
hoverIcon: @Composable (() -> Unit)? = null,
|
hoverIcon: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val hoverInteraction = remember { MutableInteractionSource() }
|
val hoverInteraction = remember { MutableInteractionSource() }
|
||||||
@ -35,7 +37,8 @@ fun SideMenuEntry(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(36.dp)
|
.height(36.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.hoverable(hoverInteraction),
|
.hoverable(hoverInteraction)
|
||||||
|
.handMouseClickable { onExpand() },
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -26,7 +26,7 @@ fun <T> SideMenuPanel(
|
|||||||
ContextMenu(
|
ContextMenu(
|
||||||
items = contextItems
|
items = contextItems
|
||||||
) {
|
) {
|
||||||
SideMenuEntry(
|
SideMenuHeader(
|
||||||
text = title,
|
text = title,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
itemsCount = items.count(),
|
itemsCount = items.count(),
|
||||||
|
@ -4,7 +4,10 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.Clear
|
import androidx.compose.material.icons.filled.Clear
|
||||||
import androidx.compose.runtime.*
|
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.handMouseClickable
|
||||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||||
import com.jetpackduba.gitnuro.theme.backgroundSelected
|
import com.jetpackduba.gitnuro.theme.backgroundSelected
|
||||||
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
|
|
||||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
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.AdjustableOutlinedTextField
|
||||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
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
|
import org.eclipse.jgit.transport.RemoteConfig
|
||||||
|
|
||||||
@Composable
|
@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) }
|
var remoteChanged by remember { mutableStateOf(false) }
|
||||||
val selectedRemote = remotesEditorData.selectedRemote
|
val selectedRemote = remotesEditorData.selectedRemote
|
||||||
|
|
||||||
@ -343,7 +346,7 @@ data class RemoteWrapper(
|
|||||||
) {
|
) {
|
||||||
val haveUrisChanged: Boolean = isNew ||
|
val haveUrisChanged: Boolean = isNew ||
|
||||||
fetchUri != originalFetchUri ||
|
fetchUri != originalFetchUri ||
|
||||||
pushUri.toString() != originalPushUri
|
pushUri != originalPushUri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
package com.jetpackduba.gitnuro.viewmodels
|
package com.jetpackduba.gitnuro.viewmodels
|
||||||
|
|
||||||
import com.jetpackduba.gitnuro.di.TabScope
|
import com.jetpackduba.gitnuro.di.TabScope
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.sidepanel.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
@TabScope
|
@TabScope
|
||||||
class TabViewModelsHolder @Inject constructor(
|
class TabViewModelsHolder @Inject constructor(
|
||||||
logViewModel: LogViewModel,
|
logViewModel: LogViewModel,
|
||||||
branchesViewModel: BranchesViewModel,
|
|
||||||
tagsViewModel: TagsViewModel,
|
|
||||||
remotesViewModel: RemotesViewModel,
|
|
||||||
statusViewModel: StatusViewModel,
|
statusViewModel: StatusViewModel,
|
||||||
menuViewModel: MenuViewModel,
|
menuViewModel: MenuViewModel,
|
||||||
stashesViewModel: StashesViewModel,
|
|
||||||
submodulesViewModel: SubmodulesViewModel,
|
|
||||||
commitChangesViewModel: CommitChangesViewModel,
|
commitChangesViewModel: CommitChangesViewModel,
|
||||||
cloneViewModel: CloneViewModel,
|
cloneViewModel: CloneViewModel,
|
||||||
settingsViewModel: SettingsViewModel,
|
settingsViewModel: SettingsViewModel,
|
||||||
|
sidePanelViewModel: SidePanelViewModel,
|
||||||
// Dynamic VM
|
// Dynamic VM
|
||||||
private val diffViewModelProvider: Provider<DiffViewModel>,
|
private val diffViewModelProvider: Provider<DiffViewModel>,
|
||||||
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
||||||
@ -25,13 +22,9 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
val viewModels = mapOf(
|
val viewModels = mapOf(
|
||||||
logViewModel::class to logViewModel,
|
logViewModel::class to logViewModel,
|
||||||
branchesViewModel::class to branchesViewModel,
|
sidePanelViewModel::class to sidePanelViewModel,
|
||||||
tagsViewModel::class to tagsViewModel,
|
|
||||||
remotesViewModel::class to remotesViewModel,
|
|
||||||
statusViewModel::class to statusViewModel,
|
statusViewModel::class to statusViewModel,
|
||||||
menuViewModel::class to menuViewModel,
|
menuViewModel::class to menuViewModel,
|
||||||
stashesViewModel::class to stashesViewModel,
|
|
||||||
submodulesViewModel::class to submodulesViewModel,
|
|
||||||
commitChangesViewModel::class to commitChangesViewModel,
|
commitChangesViewModel::class to commitChangesViewModel,
|
||||||
cloneViewModel::class to cloneViewModel,
|
cloneViewModel::class to cloneViewModel,
|
||||||
settingsViewModel::class to settingsViewModel,
|
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.RefreshType
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
import com.jetpackduba.gitnuro.git.branches.*
|
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.PullFromSpecificBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
|
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.preferences.AppSettings
|
import com.jetpackduba.gitnuro.preferences.AppSettings
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
@ -17,7 +20,8 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
private const val TAG = "BranchesViewModel"
|
private const val TAG = "BranchesViewModel"
|
||||||
|
|
||||||
class BranchesViewModel @Inject constructor(
|
|
||||||
|
class BranchesViewModel @AssistedInject constructor(
|
||||||
private val rebaseBranchUseCase: RebaseBranchUseCase,
|
private val rebaseBranchUseCase: RebaseBranchUseCase,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val appSettings: AppSettings,
|
private val appSettings: AppSettings,
|
||||||
@ -30,16 +34,26 @@ class BranchesViewModel @Inject constructor(
|
|||||||
private val deleteBranchUseCase: DeleteBranchUseCase,
|
private val deleteBranchUseCase: DeleteBranchUseCase,
|
||||||
private val checkoutRefUseCase: CheckoutRefUseCase,
|
private val checkoutRefUseCase: CheckoutRefUseCase,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
) : ExpandableViewModel(true) {
|
@Assisted
|
||||||
|
private val filter: StateFlow<String>
|
||||||
|
) : SidePanelChildViewModel(true) {
|
||||||
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
||||||
val branches: StateFlow<List<Ref>>
|
|
||||||
get() = _branches
|
|
||||||
|
|
||||||
private val _currentBranch = MutableStateFlow<Ref?>(null)
|
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 {
|
init {
|
||||||
|
|
||||||
tabScope.launch {
|
tabScope.launch {
|
||||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA)
|
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.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.RefreshType
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
import com.jetpackduba.gitnuro.git.branches.DeleteLocallyRemoteBranchesUseCase
|
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.remote_operations.DeleteRemoteBranchUseCase
|
||||||
import com.jetpackduba.gitnuro.git.remotes.*
|
import com.jetpackduba.gitnuro.git.remotes.*
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.RemoteWrapper
|
import com.jetpackduba.gitnuro.ui.dialogs.RemoteWrapper
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.api.RemoteSetUrlCommand
|
import org.eclipse.jgit.api.RemoteSetUrlCommand
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class RemotesViewModel @Inject constructor(
|
class RemotesViewModel @AssistedInject constructor(
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val deleteRemoteBranchUseCase: DeleteRemoteBranchUseCase,
|
private val deleteRemoteBranchUseCase: DeleteRemoteBranchUseCase,
|
||||||
private val getRemoteBranchesUseCase: GetRemoteBranchesUseCase,
|
private val getRemoteBranchesUseCase: GetRemoteBranchesUseCase,
|
||||||
@ -29,15 +31,37 @@ class RemotesViewModel @Inject constructor(
|
|||||||
private val updateRemoteUseCase: UpdateRemoteUseCase,
|
private val updateRemoteUseCase: UpdateRemoteUseCase,
|
||||||
private val deleteLocallyRemoteBranchesUseCase: DeleteLocallyRemoteBranchesUseCase,
|
private val deleteLocallyRemoteBranchesUseCase: DeleteLocallyRemoteBranchesUseCase,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
) : ExpandableViewModel() {
|
@Assisted
|
||||||
private val _remotes = MutableStateFlow<List<RemoteView>>(listOf())
|
private val filter: StateFlow<String>
|
||||||
val remotes: StateFlow<List<RemoteView>>
|
) : SidePanelChildViewModel(false) {
|
||||||
get() = _remotes
|
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 {
|
init {
|
||||||
tabScope.launch {
|
tabScope.launch {
|
||||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REMOTES)
|
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.REMOTES) {
|
||||||
{
|
|
||||||
refresh(tabState.git)
|
refresh(tabState.git)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +85,7 @@ class RemotesViewModel @Inject constructor(
|
|||||||
RemoteView(remoteInfo, true)
|
RemoteView(remoteInfo, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
_remotes.value = remoteViewList
|
this@RemotesViewModel.remotes.value = remoteViewList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteRemoteBranch(ref: Ref) = tabState.safeProcessing(
|
fun deleteRemoteBranch(ref: Ref) = tabState.safeProcessing(
|
||||||
@ -74,14 +98,19 @@ class RemotesViewModel @Inject constructor(
|
|||||||
loadRemotes(git)
|
loadRemotes(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRemoteClicked(remoteInfo: RemoteView) {
|
fun onRemoteClicked(remoteClicked: RemoteView) {
|
||||||
val remotes = _remotes.value
|
val remoteName = remoteClicked.remoteInfo.remoteConfig.name
|
||||||
val newRemoteInfo = remoteInfo.copy(isExpanded = !remoteInfo.isExpanded)
|
val remotes = this.remotes.value
|
||||||
val newRemotesList = remotes.toMutableList()
|
val remoteInfo = remotes.firstOrNull { it.remoteInfo.remoteConfig.name == remoteName }
|
||||||
val indexToReplace = newRemotesList.indexOf(remoteInfo)
|
|
||||||
newRemotesList[indexToReplace] = newRemoteInfo
|
|
||||||
|
|
||||||
_remotes.value = newRemotesList
|
if(remoteInfo != null) {
|
||||||
|
val newRemoteInfo = remoteInfo.copy(isExpanded = !remoteClicked.isExpanded)
|
||||||
|
val newRemotesList = remotes.toMutableList()
|
||||||
|
val indexToReplace = newRemotesList.indexOf(remoteInfo)
|
||||||
|
newRemotesList[indexToReplace] = newRemoteInfo
|
||||||
|
|
||||||
|
this.remotes.value = newRemotesList
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectBranch(ref: Ref) {
|
fun selectBranch(ref: Ref) {
|
||||||
@ -152,3 +181,5 @@ class RemotesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class RemoteView(val remoteInfo: RemoteInfo, val isExpanded: Boolean)
|
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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
abstract class ExpandableViewModel(expandedDefault: Boolean = false) {
|
abstract class SidePanelChildViewModel(expandedDefault: Boolean) {
|
||||||
private val _isExpanded = MutableStateFlow(expandedDefault)
|
private val _isExpanded = MutableStateFlow(expandedDefault)
|
||||||
val isExpanded: StateFlow<Boolean> = _isExpanded
|
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.RefreshType
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
import com.jetpackduba.gitnuro.git.stash.ApplyStashUseCase
|
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.GetStashListUseCase
|
||||||
import com.jetpackduba.gitnuro.git.stash.PopStashUseCase
|
import com.jetpackduba.gitnuro.git.stash.PopStashUseCase
|
||||||
import com.jetpackduba.gitnuro.ui.SelectedItem
|
import com.jetpackduba.gitnuro.ui.SelectedItem
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class StashesViewModel @Inject constructor(
|
class StashesViewModel @AssistedInject constructor(
|
||||||
private val getStashListUseCase: GetStashListUseCase,
|
private val getStashListUseCase: GetStashListUseCase,
|
||||||
private val applyStashUseCase: ApplyStashUseCase,
|
private val applyStashUseCase: ApplyStashUseCase,
|
||||||
private val popStashUseCase: PopStashUseCase,
|
private val popStashUseCase: PopStashUseCase,
|
||||||
private val deleteStashUseCase: DeleteStashUseCase,
|
private val deleteStashUseCase: DeleteStashUseCase,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
) : ExpandableViewModel(true) {
|
@Assisted
|
||||||
private val _stashStatus = MutableStateFlow<StashStatus>(StashStatus.Loaded(listOf()))
|
private val filter: StateFlow<String>,
|
||||||
|
) : SidePanelChildViewModel(true) {
|
||||||
|
private val stashes = MutableStateFlow<List<RevCommit>>(emptyList())
|
||||||
|
|
||||||
val stashStatus: StateFlow<StashStatus>
|
val stashesState: StateFlow<StashesState> = combine(stashes, isExpanded, filter) { stashes, isExpanded, filter ->
|
||||||
get() = _stashStatus
|
StashesState(
|
||||||
|
stashes = stashes.filter { it.fullMessage.lowercaseContains(filter) },
|
||||||
|
isExpanded,
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
tabScope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
StashesState(emptyList(), isExpanded.value)
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
tabScope.launch {
|
tabScope.launch {
|
||||||
@ -41,9 +52,8 @@ class StashesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadStashes(git: Git) {
|
suspend fun loadStashes(git: Git) {
|
||||||
_stashStatus.value = StashStatus.Loading
|
|
||||||
val stashList = getStashListUseCase(git)
|
val stashList = getStashListUseCase(git)
|
||||||
_stashStatus.value = StashStatus.Loaded(stashList.toList())
|
stashes.value = stashList
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun refresh(git: Git) {
|
suspend fun refresh(git: Git) {
|
||||||
@ -74,7 +84,7 @@ class StashesViewModel @Inject constructor(
|
|||||||
stashDropped(stash)
|
stashDropped(stash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTab(stash: RevCommit) = tabState.runOperation(
|
fun selectStash(stash: RevCommit) = tabState.runOperation(
|
||||||
refreshType = RefreshType.NONE,
|
refreshType = RefreshType.NONE,
|
||||||
) {
|
) {
|
||||||
tabState.newSelectedStash(stash)
|
tabState.newSelectedStash(stash)
|
||||||
@ -94,7 +104,4 @@ class StashesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed class StashStatus {
|
data class StashesState(val stashes: List<RevCommit>, val isExpanded: Boolean)
|
||||||
object Loading : StashStatus()
|
|
||||||
data class Loaded(val stashes: List<RevCommit>) : StashStatus()
|
|
||||||
}
|
|
@ -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.RefreshType
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
@ -19,7 +19,7 @@ class SubmodulesViewModel @Inject constructor(
|
|||||||
private val initializeSubmoduleUseCase: InitializeSubmoduleUseCase,
|
private val initializeSubmoduleUseCase: InitializeSubmoduleUseCase,
|
||||||
private val updateSubmoduleUseCase: UpdateSubmoduleUseCase,
|
private val updateSubmoduleUseCase: UpdateSubmoduleUseCase,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
) : ExpandableViewModel() {
|
) : SidePanelChildViewModel(true) {
|
||||||
private val _submodules = MutableStateFlow<List<Pair<String, SubmoduleStatus>>>(listOf())
|
private val _submodules = MutableStateFlow<List<Pair<String, SubmoduleStatus>>>(listOf())
|
||||||
val submodules: StateFlow<List<Pair<String, SubmoduleStatus>>>
|
val submodules: StateFlow<List<Pair<String, SubmoduleStatus>>>
|
||||||
get() = _submodules
|
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.RefreshType
|
||||||
import com.jetpackduba.gitnuro.git.TabState
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase
|
import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase
|
||||||
import com.jetpackduba.gitnuro.git.tags.DeleteTagUseCase
|
import com.jetpackduba.gitnuro.git.tags.DeleteTagUseCase
|
||||||
import com.jetpackduba.gitnuro.git.tags.GetTagsUseCase
|
import com.jetpackduba.gitnuro.git.tags.GetTagsUseCase
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class TagsViewModel @Inject constructor(
|
class TagsViewModel @AssistedInject constructor(
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val getTagsUseCase: GetTagsUseCase,
|
private val getTagsUseCase: GetTagsUseCase,
|
||||||
private val deleteTagUseCase: DeleteTagUseCase,
|
private val deleteTagUseCase: DeleteTagUseCase,
|
||||||
private val checkoutRefUseCase: CheckoutRefUseCase,
|
private val checkoutRefUseCase: CheckoutRefUseCase,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
) : ExpandableViewModel() {
|
@Assisted
|
||||||
private val _tags = MutableStateFlow<List<Ref>>(listOf())
|
private val filter: StateFlow<String>
|
||||||
val tags: StateFlow<List<Ref>>
|
) : SidePanelChildViewModel(false) {
|
||||||
get() = _tags
|
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 {
|
init {
|
||||||
tabScope.launch {
|
tabScope.launch {
|
||||||
@ -38,7 +51,7 @@ class TagsViewModel @Inject constructor(
|
|||||||
private suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
|
private suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
|
||||||
val tagsList = getTagsUseCase(git)
|
val tagsList = getTagsUseCase(git)
|
||||||
|
|
||||||
_tags.value = tagsList
|
tags.value = tagsList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
|
fun checkoutRef(ref: Ref) = tabState.safeProcessing(
|
||||||
@ -61,3 +74,5 @@ class TagsViewModel @Inject constructor(
|
|||||||
loadTags(git)
|
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