diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt b/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt index 5678c58..e7a7300 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/Icons.kt @@ -45,10 +45,12 @@ object AppIcons { const val SOURCE = "source.svg" const val START = "start.svg" const val STASH = "stash.svg" + const val SYNC = "sync.svg" const val TAG = "tag.svg" const val TERMINAL = "terminal.svg" const val TOPIC = "topic.svg" const val UNDO = "undo.svg" + const val UPDATE = "update.svg" const val UPLOAD = "upload.svg" const val VISIBILITY = "visibility.svg" const val VISIBILITY_OFF = "visibility_off.svg" diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/AddSubmoduleUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/AddSubmoduleUseCase.kt index 235ec12..0c6d48d 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/AddSubmoduleUseCase.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/AddSubmoduleUseCase.kt @@ -1,16 +1,31 @@ package com.jetpackduba.gitnuro.git.submodules +import com.jetpackduba.gitnuro.models.ProgressMonitorInfo import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.ProgressMonitor import javax.inject.Inject class AddSubmoduleUseCase @Inject constructor() { - suspend operator fun invoke(git: Git, name: String, path: String, uri: String): Unit = withContext(Dispatchers.IO) { + suspend operator fun invoke(git: Git, name: String, path: String, uri: String) = withContext(Dispatchers.IO) { + git.submoduleAdd() .setName(name) .setPath(path) .setURI(uri) + .setProgressMonitor(object : ProgressMonitor { + override fun start(totalTasks: Int) {} + override fun beginTask(title: String?, totalWork: Int) {} + override fun update(completed: Int) {} + override fun endTask() {} + override fun showDuration(enabled: Boolean) {} + + override fun isCancelled() = !isActive + + }) .call() } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/DeleteSubmoduleUseCase.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/DeleteSubmoduleUseCase.kt new file mode 100644 index 0000000..983fb62 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/submodules/DeleteSubmoduleUseCase.kt @@ -0,0 +1,41 @@ +package com.jetpackduba.gitnuro.git.submodules + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Config +import org.eclipse.jgit.storage.file.FileBasedConfig +import java.io.File +import javax.inject.Inject + +private const val TAG = "DeleteSubmoduleUseCase" + +class DeleteSubmoduleUseCase @Inject constructor() { + suspend operator fun invoke(git: Git, path: String): Unit = withContext(Dispatchers.IO) { + git.rm() + .addFilepattern(path) + .call() + + val repository = git.repository + val gitModules = File(repository.workTree, ".gitmodules") + + if (gitModules.exists() && gitModules.isFile) { + val config = FileBasedConfig(gitModules, repository.fs) + + config.load() + config.unsetSection("submodule", path) + config.save() + } + + val moduleDir = File(repository.directory, "modules/$path") + val workspace = File(repository.workTree, path) + + if (moduleDir.exists()) { + moduleDir.deleteRecursively() + } + + if (workspace.exists()) { + workspace.deleteRecursively() + } + } +} diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/models/ProgressMonitorInfo.kt b/src/main/kotlin/com/jetpackduba/gitnuro/models/ProgressMonitorInfo.kt new file mode 100644 index 0000000..6c02c39 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/models/ProgressMonitorInfo.kt @@ -0,0 +1,25 @@ +package com.jetpackduba.gitnuro.models + +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.withContext +import org.eclipse.jgit.lib.ProgressMonitor + +sealed interface ProgressMonitorInfo { + object Loading : ProgressMonitorInfo + + data class Processing( + val totalTasks: Int, + val currentTaskCount: Int, + val currentTask: CurrentTask, + ) : ProgressMonitorInfo + + data class Failed(val ex: Exception) : ProgressMonitorInfo + + object Completed : ProgressMonitorInfo +} + +data class CurrentTask( + val title: String, + val totalWork: Int, + val completedWork: Int, +) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/SidePanel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/SidePanel.kt index b8f807f..6deda72 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/SidePanel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/SidePanel.kt @@ -20,6 +20,7 @@ import com.jetpackduba.gitnuro.ui.components.* import com.jetpackduba.gitnuro.ui.context_menu.* import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog +import com.jetpackduba.gitnuro.ui.dialogs.AddSubmodulesDialog import com.jetpackduba.gitnuro.viewmodels.sidepanel.* import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.revwalk.RevCommit @@ -44,6 +45,7 @@ fun SidePanel( var showEditRemotesDialog by remember { mutableStateOf(false) } val (branchToChangeUpstream, setBranchToChangeUpstream) = remember { mutableStateOf(null) } + var showEditSubmodulesDialog by remember { mutableStateOf(false) } Column { FilterTextField( @@ -85,7 +87,8 @@ fun SidePanel( submodules( submodulesState = submodulesState, - submodulesViewModel = submodulesViewModel + submodulesViewModel = submodulesViewModel, + onShowEditSubmodulesDialog = { showEditSubmodulesDialog = true }, ) } } @@ -106,6 +109,18 @@ fun SidePanel( onClose = { setBranchToChangeUpstream(null) } ) } + + if (showEditSubmodulesDialog) { + AddSubmodulesDialog( + onCancel = { + showEditSubmodulesDialog = false + }, + onAccept = { repository, directory -> + submodulesViewModel.onCreateSubmodule(repository, directory) + showEditSubmodulesDialog = false + } + ) + } } @Composable @@ -262,14 +277,12 @@ fun LazyListScope.tags( if (isExpanded) { items(tags, key = { it.name }) { tag -> -// if () { Tag( tag, onTagClicked = { tagsViewModel.selectTag(tag) }, onCheckoutTag = { tagsViewModel.checkoutRef(tag) }, onDeleteTag = { tagsViewModel.deleteTag(tag) } ) -// } } } } @@ -312,6 +325,7 @@ fun LazyListScope.stashes( fun LazyListScope.submodules( submodulesState: SubmodulesState, submodulesViewModel: SubmodulesViewModel, + onShowEditSubmodulesDialog: () -> Unit, ) { val isExpanded = submodulesState.isExpanded val submodules = submodulesState.submodules @@ -324,7 +338,23 @@ fun LazyListScope.submodules( text = "Submodules", icon = painterResource(AppIcons.TOPIC), itemsCount = submodules.count(), - hoverIcon = null, + hoverIcon = { + IconButton( + onClick = onShowEditSubmodulesDialog, + modifier = Modifier + .padding(end = 16.dp) + .size(16.dp) + .handOnHover(), + ) { + Icon( + painter = painterResource(AppIcons.ADD), + contentDescription = null, + modifier = Modifier + .fillMaxSize(), + tint = MaterialTheme.colors.onBackground, + ) + } + }, isExpanded = isExpanded, onExpand = { submodulesViewModel.onExpand() } ) @@ -336,10 +366,11 @@ fun LazyListScope.submodules( Submodule( submodule = submodule, onInitializeSubmodule = { submodulesViewModel.initializeSubmodule(submodule.first) }, - onDeinitializeSubmodule = { submodulesViewModel.onDeinitializeSubmodule(submodule.first) }, +// onDeinitializeSubmodule = { submodulesViewModel.onDeinitializeSubmodule(submodule.first) }, onSyncSubmodule = { submodulesViewModel.onSyncSubmodule(submodule.first) }, onUpdateSubmodule = { submodulesViewModel.onUpdateSubmodule(submodule.first) }, onOpenSubmoduleInTab = { submodulesViewModel.onOpenSubmoduleInTab(submodule.first) }, + onDeleteSubmodule = { submodulesViewModel.onDeleteSubmodule(submodule.first) }, ) } } @@ -486,26 +517,29 @@ private fun Stash( private fun Submodule( submodule: Pair, onInitializeSubmodule: () -> Unit, - onDeinitializeSubmodule: () -> Unit, +// onDeinitializeSubmodule: () -> Unit, onSyncSubmodule: () -> Unit, onUpdateSubmodule: () -> Unit, onOpenSubmoduleInTab: () -> Unit, + onDeleteSubmodule: () -> Unit, ) { ContextMenu( items = { submoduleContextMenuItems( submodule.second, onInitializeSubmodule = onInitializeSubmodule, - onDeinitializeSubmodule = onDeinitializeSubmodule, +// onDeinitializeSubmodule = onDeinitializeSubmodule, onSyncSubmodule = onSyncSubmodule, onUpdateSubmodule = onUpdateSubmodule, onOpenSubmoduleInTab = onOpenSubmoduleInTab, + onDeleteSubmodule = onDeleteSubmodule, ) } ) { SideMenuSubentry( text = submodule.first, iconResourcePath = AppIcons.TOPIC, + onClick = onOpenSubmoduleInTab, ) { val stateName = submodule.second.type.toString() Tooltip(stateName) { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/SubmoduleContextMenu.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/SubmoduleContextMenu.kt index 60736ed..d673375 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/SubmoduleContextMenu.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/SubmoduleContextMenu.kt @@ -1,15 +1,18 @@ package com.jetpackduba.gitnuro.ui.context_menu +import androidx.compose.ui.res.painterResource +import com.jetpackduba.gitnuro.AppIcons import org.eclipse.jgit.submodule.SubmoduleStatus import org.eclipse.jgit.submodule.SubmoduleStatusType fun submoduleContextMenuItems( submoduleStatus: SubmoduleStatus, onInitializeSubmodule: () -> Unit, - onDeinitializeSubmodule: () -> Unit, +// onDeinitializeSubmodule: () -> Unit, onSyncSubmodule: () -> Unit, onUpdateSubmodule: () -> Unit, onOpenSubmoduleInTab: () -> Unit, + onDeleteSubmodule: () -> Unit, ): List { return mutableListOf().apply { if (submoduleStatus.type == SubmoduleStatusType.UNINITIALIZED) { @@ -24,12 +27,14 @@ fun submoduleContextMenuItems( add( ContextMenuElement.ContextTextEntry( label = "Open submodule in new tab", + icon = { painterResource(AppIcons.OPEN) }, onClick = onOpenSubmoduleInTab, ) ) add( ContextMenuElement.ContextTextEntry( label = "Sync submodule", + icon = { painterResource(AppIcons.SYNC) }, onClick = onSyncSubmodule, ) ) @@ -37,16 +42,29 @@ fun submoduleContextMenuItems( add( ContextMenuElement.ContextTextEntry( label = "Update submodule", + icon = { painterResource(AppIcons.UPDATE) }, onClick = onUpdateSubmodule, ) ) - - add( - ContextMenuElement.ContextTextEntry( - label = "DeInitialize submodule", - onClick = onDeinitializeSubmodule, - ) - ) +// TODO This crashes with a NPE in jgit even when providing correct arguments, feature removed for now +// add( +// ContextMenuElement.ContextTextEntry( +// label = "DeInitialize submodule", +// onClick = onDeinitializeSubmodule, +// ) +// ) } + + add( + ContextMenuElement.ContextSeparator, + ) + + add( + ContextMenuElement.ContextTextEntry( + label = "Delete submodule", + icon = { painterResource(AppIcons.DELETE) }, + onClick = onDeleteSubmodule, + ), + ) } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/AddSubmoduleDialog.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/AddSubmoduleDialog.kt new file mode 100644 index 0000000..2c72cc6 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/AddSubmoduleDialog.kt @@ -0,0 +1,201 @@ +package com.jetpackduba.gitnuro.ui.dialogs + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.jetpackduba.gitnuro.AppIcons +import com.jetpackduba.gitnuro.keybindings.KeybindingOption +import com.jetpackduba.gitnuro.keybindings.matchesBinding +import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField +import com.jetpackduba.gitnuro.ui.components.PrimaryButton +import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel +import com.jetpackduba.gitnuro.viewmodels.sidepanel.SubmoduleDialogViewModel + +@Composable +fun AddSubmodulesDialog( + viewModel: SubmoduleDialogViewModel = gitnuroDynamicViewModel(), + onCancel: () -> Unit, + onAccept: (repository: String, directory: String) -> Unit, +) { + var repositoryField by remember { mutableStateOf("") } + var directoryField by remember { mutableStateOf(TextFieldValue("")) } + val repositoryFocusRequester = remember { FocusRequester() } + val directoryFocusRequester = remember { FocusRequester() } + val buttonFieldFocusRequester = remember { FocusRequester() } + + val error by viewModel.error.collectAsState() + + LaunchedEffect(viewModel) { + viewModel.onDataIsValid.collect { + onAccept(repositoryField, directoryField.text) + } + } + + MaterialDialog( + background = MaterialTheme.colors.surface, + onCloseRequested = onCancel + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.animateContentSize().width(IntrinsicSize.Min) + ) { + Icon( + painterResource(AppIcons.TOPIC), + contentDescription = null, + modifier = Modifier + .padding(bottom = 16.dp) + .size(64.dp), + tint = MaterialTheme.colors.onBackground, + ) + + Text( + text = "New submodule", + modifier = Modifier + .padding(bottom = 8.dp), + color = MaterialTheme.colors.onBackground, + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.SemiBold, + ) + + Text( + text = "Create a new submodule from an existing repository", + modifier = Modifier + .padding(bottom = 16.dp), + color = MaterialTheme.colors.onBackground, + style = MaterialTheme.typography.body2, + textAlign = TextAlign.Center, + ) + + Text( + "Repository URL", + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onBackground, + modifier = Modifier.padding(bottom = 8.dp) + .align(Alignment.Start), + ) + + AdjustableOutlinedTextField( + modifier = Modifier + .focusRequester(repositoryFocusRequester) + .focusProperties { + this.next = directoryFocusRequester + } + .width(480.dp) + .onFocusChanged { focusState -> + if (!focusState.isFocused && directoryField.text.isBlank() && repositoryField.isNotBlank()) { + val repo = repositoryField.split("/", "\\").last().removeSuffix(".git") + directoryField = TextFieldValue(repo, selection = TextRange(repo.count())) + } + }, + value = repositoryField, + maxLines = 1, + onValueChange = { + viewModel.resetError() + repositoryField = it + }, + ) + + Text( + "Directory", + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onBackground, + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp) + .align(Alignment.Start), + ) + + AdjustableOutlinedTextField( + modifier = Modifier + .focusRequester(directoryFocusRequester) + .focusProperties { + this.next = buttonFieldFocusRequester + } + .width(480.dp) + .onPreviewKeyEvent { keyEvent -> + if (keyEvent.matchesBinding(KeybindingOption.SIMPLE_ACCEPT) && repositoryField.isNotBlank() && directoryField.text.isNotBlank()) { + viewModel.verifyData(repositoryField, directoryField.text) + true + } else { + false + } + }, + value = directoryField, + maxLines = 1, + onValueChange = { + viewModel.resetError() + directoryField = it + }, + ) + + AnimatedVisibility (error.isNotBlank()) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clip(RoundedCornerShape(4.dp)) + .background(MaterialTheme.colors.error) + ) { + Text( + error, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp, horizontal = 8.dp), + color = MaterialTheme.colors.onError, + ) + } + } + + Row( + modifier = Modifier + .padding(top = 24.dp) + .align(Alignment.End) + ) { + PrimaryButton( + text = "Cancel", + modifier = Modifier.padding(end = 8.dp), + onClick = onCancel, + backgroundColor = Color.Transparent, + textColor = MaterialTheme.colors.onBackground, + ) + PrimaryButton( + modifier = Modifier + .focusRequester(buttonFieldFocusRequester) + .focusProperties { + this.previous = repositoryFocusRequester + this.next = repositoryFocusRequester + }, + enabled = repositoryField.isNotBlank() && directoryField.text.isNotBlank(), + onClick = { + viewModel.verifyData(repositoryField, directoryField.text) + }, + text = "Create submodule" + ) + } + } + + LaunchedEffect(Unit) { + repositoryFocusRequester.requestFocus() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/NewBranchDialog.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/NewBranchDialog.kt index 2b9f1dd..ca37e7b 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/NewBranchDialog.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/dialogs/NewBranchDialog.kt @@ -46,7 +46,7 @@ fun NewBranchDialog( ) Text( - text = "Set branch name", + text = "New branch", modifier = Modifier .padding(bottom = 8.dp), color = MaterialTheme.colors.onBackground, diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/CloneViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/CloneViewModel.kt index eb0cb26..9b5e8f2 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/CloneViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/CloneViewModel.kt @@ -46,7 +46,6 @@ class CloneViewModel @Inject constructor( return@safeProcessingWithoutGit } - val urlSplit = url.split("/", "\\").toMutableList() // Removes the last element for URLs that end with "/" or "\" instead of the repo name like https://github.com/JetpackDuba/Gitnuro/ diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt index c068999..4e4c890 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModelsHolder.kt @@ -2,6 +2,7 @@ package com.jetpackduba.gitnuro.viewmodels import com.jetpackduba.gitnuro.di.TabScope import com.jetpackduba.gitnuro.viewmodels.sidepanel.SidePanelViewModel +import com.jetpackduba.gitnuro.viewmodels.sidepanel.SubmoduleDialogViewModel import javax.inject.Inject import javax.inject.Provider import kotlin.reflect.KClass @@ -21,6 +22,7 @@ class TabViewModelsHolder @Inject constructor( private val historyViewModelProvider: Provider, private val authorViewModelProvider: Provider, private val changeDefaultUpstreamBranchViewModelProvider: Provider, + private val submoduleDialogViewModelProvider: Provider, ) { val viewModels = mapOf( logViewModel::class to logViewModel, @@ -40,6 +42,7 @@ class TabViewModelsHolder @Inject constructor( HistoryViewModel::class -> historyViewModelProvider.get() AuthorViewModel::class -> authorViewModelProvider.get() ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get() + SubmoduleDialogViewModel::class -> submoduleDialogViewModelProvider.get() else -> throw NotImplementedError("View model provider not implemented") } } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmoduleDialogViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmoduleDialogViewModel.kt new file mode 100644 index 0000000..37fb886 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmoduleDialogViewModel.kt @@ -0,0 +1,58 @@ +package com.jetpackduba.gitnuro.viewmodels.sidepanel + +import com.jetpackduba.gitnuro.git.RefreshType +import com.jetpackduba.gitnuro.git.TabState +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.* +import org.eclipse.jgit.transport.URIish +import java.io.File +import javax.inject.Inject + +class SubmoduleDialogViewModel @Inject constructor( + private val tabState: TabState, +) { + private val _error = MutableStateFlow("") + val error = _error.asStateFlow() + + private val _onDataIsValid = MutableSharedFlow() + val onDataIsValid = _onDataIsValid.asSharedFlow() + + fun verifyData( + repositoryUrl: String, + directoryPath: String, + ) = tabState.runOperation( + refreshType = RefreshType.NONE, + ) { git -> + val message = when { + directoryPath.isBlank() -> "Invalid empty directory" + repositoryUrl.isBlank() -> "Invalid empty URL" + else -> null + } + + if (message != null) { + _error.value = message + return@runOperation + } + + try { + URIish(repositoryUrl) + } catch (ex: Exception) { + _error.value = "${ex.message.orEmpty()}. Check your repository URL and try again." + return@runOperation + } + + val directory = File(git.repository.workTree, directoryPath) + + if (directory.exists() && (directory.listFiles()?.count() ?: 0) > 0) { + _error.value = "Directory $directoryPath contains files. Try again with a different name." + return@runOperation + } + + _onDataIsValid.emit(Unit) + } + + fun resetError() { + _error.value = "" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmodulesViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmodulesViewModel.kt index 65464df..1010ba7 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmodulesViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/sidepanel/SubmodulesViewModel.kt @@ -20,6 +20,8 @@ class SubmodulesViewModel @AssistedInject constructor( private val updateSubmoduleUseCase: UpdateSubmoduleUseCase, private val syncSubmoduleUseCase: SyncSubmoduleUseCase, private val deInitializeSubmoduleUseCase: DeInitializeSubmoduleUseCase, + private val addSubmoduleUseCase: AddSubmoduleUseCase, + private val deleteSubmoduleUseCase: DeleteSubmoduleUseCase, private val tabScope: CoroutineScope, private val tabsManager: TabsManager, @Assisted @@ -41,8 +43,7 @@ class SubmodulesViewModel @AssistedInject constructor( init { tabScope.launch { - tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.SUBMODULES) - { + tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.UNCOMMITED_CHANGES, RefreshType.SUBMODULES) { refresh(tabState.git) } } @@ -90,6 +91,23 @@ class SubmodulesViewModel @AssistedInject constructor( ) { git -> updateSubmoduleUseCase(git, path) } + + fun onCreateSubmodule(repository: String, directory: String) = tabState.safeProcessing( + refreshType = RefreshType.ALL_DATA, + ) { git -> + addSubmoduleUseCase( + git = git, + name = directory, + path = directory, + uri = repository, + ) + } + + fun onDeleteSubmodule(path: String) = tabState.safeProcessing( + refreshType = RefreshType.ALL_DATA, + ) { git -> + deleteSubmoduleUseCase(git, path) + } } data class SubmodulesState(val submodules: List>, val isExpanded: Boolean) \ No newline at end of file diff --git a/src/main/resources/sync.svg b/src/main/resources/sync.svg new file mode 100644 index 0000000..7e10ba1 --- /dev/null +++ b/src/main/resources/sync.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/update.svg b/src/main/resources/update.svg new file mode 100644 index 0000000..fe04927 --- /dev/null +++ b/src/main/resources/update.svg @@ -0,0 +1 @@ + \ No newline at end of file