parent
c24658952e
commit
07e1bbd4ed
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
@ -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<Ref?>(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<String, SubmoduleStatus>,
|
||||
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) {
|
||||
|
@ -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<ContextMenuElement> {
|
||||
return mutableListOf<ContextMenuElement>().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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ fun NewBranchDialog(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Set branch name",
|
||||
text = "New branch",
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
|
@ -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/
|
||||
|
@ -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<HistoryViewModel>,
|
||||
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
||||
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
||||
private val submoduleDialogViewModelProvider: Provider<SubmoduleDialogViewModel>,
|
||||
) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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<Unit>()
|
||||
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 = ""
|
||||
}
|
||||
|
||||
}
|
@ -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<Pair<String, SubmoduleStatus>>, val isExpanded: Boolean)
|
1
src/main/resources/sync.svg
Normal file
1
src/main/resources/sync.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="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
|
After Width: | Height: | Size: 380 B |
1
src/main/resources/update.svg
Normal file
1
src/main/resources/update.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24" x="0"/></g><g><g><g><path d="M21,10.12h-6.78l2.74-2.82c-2.73-2.7-7.15-2.8-9.88-0.1c-2.73,2.71-2.73,7.08,0,9.79s7.15,2.71,9.88,0 C18.32,15.65,19,14.08,19,12.1h2c0,1.98-0.88,4.55-2.64,6.29c-3.51,3.48-9.21,3.48-12.72,0c-3.5-3.47-3.53-9.11-0.02-12.58 s9.14-3.47,12.65,0L21,3V10.12z M12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z"/></g></g></g></svg>
|
After Width: | Height: | Size: 525 B |
Loading…
Reference in New Issue
Block a user