parent
c24658952e
commit
07e1bbd4ed
@ -45,10 +45,12 @@ object AppIcons {
|
|||||||
const val SOURCE = "source.svg"
|
const val SOURCE = "source.svg"
|
||||||
const val START = "start.svg"
|
const val START = "start.svg"
|
||||||
const val STASH = "stash.svg"
|
const val STASH = "stash.svg"
|
||||||
|
const val SYNC = "sync.svg"
|
||||||
const val TAG = "tag.svg"
|
const val TAG = "tag.svg"
|
||||||
const val TERMINAL = "terminal.svg"
|
const val TERMINAL = "terminal.svg"
|
||||||
const val TOPIC = "topic.svg"
|
const val TOPIC = "topic.svg"
|
||||||
const val UNDO = "undo.svg"
|
const val UNDO = "undo.svg"
|
||||||
|
const val UPDATE = "update.svg"
|
||||||
const val UPLOAD = "upload.svg"
|
const val UPLOAD = "upload.svg"
|
||||||
const val VISIBILITY = "visibility.svg"
|
const val VISIBILITY = "visibility.svg"
|
||||||
const val VISIBILITY_OFF = "visibility_off.svg"
|
const val VISIBILITY_OFF = "visibility_off.svg"
|
||||||
|
@ -1,16 +1,31 @@
|
|||||||
package com.jetpackduba.gitnuro.git.submodules
|
package com.jetpackduba.gitnuro.git.submodules
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.models.ProgressMonitorInfo
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
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.ProgressMonitor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AddSubmoduleUseCase @Inject constructor() {
|
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()
|
git.submoduleAdd()
|
||||||
.setName(name)
|
.setName(name)
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.setURI(uri)
|
.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()
|
.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.context_menu.*
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.SetDefaultUpstreamBranchDialog
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog
|
||||||
|
import com.jetpackduba.gitnuro.ui.dialogs.AddSubmodulesDialog
|
||||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.*
|
import com.jetpackduba.gitnuro.viewmodels.sidepanel.*
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
@ -44,6 +45,7 @@ fun SidePanel(
|
|||||||
|
|
||||||
var showEditRemotesDialog by remember { mutableStateOf(false) }
|
var showEditRemotesDialog by remember { mutableStateOf(false) }
|
||||||
val (branchToChangeUpstream, setBranchToChangeUpstream) = remember { mutableStateOf<Ref?>(null) }
|
val (branchToChangeUpstream, setBranchToChangeUpstream) = remember { mutableStateOf<Ref?>(null) }
|
||||||
|
var showEditSubmodulesDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
FilterTextField(
|
FilterTextField(
|
||||||
@ -85,7 +87,8 @@ fun SidePanel(
|
|||||||
|
|
||||||
submodules(
|
submodules(
|
||||||
submodulesState = submodulesState,
|
submodulesState = submodulesState,
|
||||||
submodulesViewModel = submodulesViewModel
|
submodulesViewModel = submodulesViewModel,
|
||||||
|
onShowEditSubmodulesDialog = { showEditSubmodulesDialog = true },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,6 +109,18 @@ fun SidePanel(
|
|||||||
onClose = { setBranchToChangeUpstream(null) }
|
onClose = { setBranchToChangeUpstream(null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showEditSubmodulesDialog) {
|
||||||
|
AddSubmodulesDialog(
|
||||||
|
onCancel = {
|
||||||
|
showEditSubmodulesDialog = false
|
||||||
|
},
|
||||||
|
onAccept = { repository, directory ->
|
||||||
|
submodulesViewModel.onCreateSubmodule(repository, directory)
|
||||||
|
showEditSubmodulesDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -262,14 +277,12 @@ fun LazyListScope.tags(
|
|||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
items(tags, key = { it.name }) { tag ->
|
items(tags, key = { it.name }) { tag ->
|
||||||
// if () {
|
|
||||||
Tag(
|
Tag(
|
||||||
tag,
|
tag,
|
||||||
onTagClicked = { tagsViewModel.selectTag(tag) },
|
onTagClicked = { tagsViewModel.selectTag(tag) },
|
||||||
onCheckoutTag = { tagsViewModel.checkoutRef(tag) },
|
onCheckoutTag = { tagsViewModel.checkoutRef(tag) },
|
||||||
onDeleteTag = { tagsViewModel.deleteTag(tag) }
|
onDeleteTag = { tagsViewModel.deleteTag(tag) }
|
||||||
)
|
)
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,6 +325,7 @@ fun LazyListScope.stashes(
|
|||||||
fun LazyListScope.submodules(
|
fun LazyListScope.submodules(
|
||||||
submodulesState: SubmodulesState,
|
submodulesState: SubmodulesState,
|
||||||
submodulesViewModel: SubmodulesViewModel,
|
submodulesViewModel: SubmodulesViewModel,
|
||||||
|
onShowEditSubmodulesDialog: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isExpanded = submodulesState.isExpanded
|
val isExpanded = submodulesState.isExpanded
|
||||||
val submodules = submodulesState.submodules
|
val submodules = submodulesState.submodules
|
||||||
@ -324,7 +338,23 @@ fun LazyListScope.submodules(
|
|||||||
text = "Submodules",
|
text = "Submodules",
|
||||||
icon = painterResource(AppIcons.TOPIC),
|
icon = painterResource(AppIcons.TOPIC),
|
||||||
itemsCount = submodules.count(),
|
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,
|
isExpanded = isExpanded,
|
||||||
onExpand = { submodulesViewModel.onExpand() }
|
onExpand = { submodulesViewModel.onExpand() }
|
||||||
)
|
)
|
||||||
@ -336,10 +366,11 @@ fun LazyListScope.submodules(
|
|||||||
Submodule(
|
Submodule(
|
||||||
submodule = submodule,
|
submodule = submodule,
|
||||||
onInitializeSubmodule = { submodulesViewModel.initializeSubmodule(submodule.first) },
|
onInitializeSubmodule = { submodulesViewModel.initializeSubmodule(submodule.first) },
|
||||||
onDeinitializeSubmodule = { submodulesViewModel.onDeinitializeSubmodule(submodule.first) },
|
// onDeinitializeSubmodule = { submodulesViewModel.onDeinitializeSubmodule(submodule.first) },
|
||||||
onSyncSubmodule = { submodulesViewModel.onSyncSubmodule(submodule.first) },
|
onSyncSubmodule = { submodulesViewModel.onSyncSubmodule(submodule.first) },
|
||||||
onUpdateSubmodule = { submodulesViewModel.onUpdateSubmodule(submodule.first) },
|
onUpdateSubmodule = { submodulesViewModel.onUpdateSubmodule(submodule.first) },
|
||||||
onOpenSubmoduleInTab = { submodulesViewModel.onOpenSubmoduleInTab(submodule.first) },
|
onOpenSubmoduleInTab = { submodulesViewModel.onOpenSubmoduleInTab(submodule.first) },
|
||||||
|
onDeleteSubmodule = { submodulesViewModel.onDeleteSubmodule(submodule.first) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,26 +517,29 @@ private fun Stash(
|
|||||||
private fun Submodule(
|
private fun Submodule(
|
||||||
submodule: Pair<String, SubmoduleStatus>,
|
submodule: Pair<String, SubmoduleStatus>,
|
||||||
onInitializeSubmodule: () -> Unit,
|
onInitializeSubmodule: () -> Unit,
|
||||||
onDeinitializeSubmodule: () -> Unit,
|
// onDeinitializeSubmodule: () -> Unit,
|
||||||
onSyncSubmodule: () -> Unit,
|
onSyncSubmodule: () -> Unit,
|
||||||
onUpdateSubmodule: () -> Unit,
|
onUpdateSubmodule: () -> Unit,
|
||||||
onOpenSubmoduleInTab: () -> Unit,
|
onOpenSubmoduleInTab: () -> Unit,
|
||||||
|
onDeleteSubmodule: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ContextMenu(
|
ContextMenu(
|
||||||
items = {
|
items = {
|
||||||
submoduleContextMenuItems(
|
submoduleContextMenuItems(
|
||||||
submodule.second,
|
submodule.second,
|
||||||
onInitializeSubmodule = onInitializeSubmodule,
|
onInitializeSubmodule = onInitializeSubmodule,
|
||||||
onDeinitializeSubmodule = onDeinitializeSubmodule,
|
// onDeinitializeSubmodule = onDeinitializeSubmodule,
|
||||||
onSyncSubmodule = onSyncSubmodule,
|
onSyncSubmodule = onSyncSubmodule,
|
||||||
onUpdateSubmodule = onUpdateSubmodule,
|
onUpdateSubmodule = onUpdateSubmodule,
|
||||||
onOpenSubmoduleInTab = onOpenSubmoduleInTab,
|
onOpenSubmoduleInTab = onOpenSubmoduleInTab,
|
||||||
|
onDeleteSubmodule = onDeleteSubmodule,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
SideMenuSubentry(
|
SideMenuSubentry(
|
||||||
text = submodule.first,
|
text = submodule.first,
|
||||||
iconResourcePath = AppIcons.TOPIC,
|
iconResourcePath = AppIcons.TOPIC,
|
||||||
|
onClick = onOpenSubmoduleInTab,
|
||||||
) {
|
) {
|
||||||
val stateName = submodule.second.type.toString()
|
val stateName = submodule.second.type.toString()
|
||||||
Tooltip(stateName) {
|
Tooltip(stateName) {
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
package com.jetpackduba.gitnuro.ui.context_menu
|
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.SubmoduleStatus
|
||||||
import org.eclipse.jgit.submodule.SubmoduleStatusType
|
import org.eclipse.jgit.submodule.SubmoduleStatusType
|
||||||
|
|
||||||
fun submoduleContextMenuItems(
|
fun submoduleContextMenuItems(
|
||||||
submoduleStatus: SubmoduleStatus,
|
submoduleStatus: SubmoduleStatus,
|
||||||
onInitializeSubmodule: () -> Unit,
|
onInitializeSubmodule: () -> Unit,
|
||||||
onDeinitializeSubmodule: () -> Unit,
|
// onDeinitializeSubmodule: () -> Unit,
|
||||||
onSyncSubmodule: () -> Unit,
|
onSyncSubmodule: () -> Unit,
|
||||||
onUpdateSubmodule: () -> Unit,
|
onUpdateSubmodule: () -> Unit,
|
||||||
onOpenSubmoduleInTab: () -> Unit,
|
onOpenSubmoduleInTab: () -> Unit,
|
||||||
|
onDeleteSubmodule: () -> Unit,
|
||||||
): List<ContextMenuElement> {
|
): List<ContextMenuElement> {
|
||||||
return mutableListOf<ContextMenuElement>().apply {
|
return mutableListOf<ContextMenuElement>().apply {
|
||||||
if (submoduleStatus.type == SubmoduleStatusType.UNINITIALIZED) {
|
if (submoduleStatus.type == SubmoduleStatusType.UNINITIALIZED) {
|
||||||
@ -24,12 +27,14 @@ fun submoduleContextMenuItems(
|
|||||||
add(
|
add(
|
||||||
ContextMenuElement.ContextTextEntry(
|
ContextMenuElement.ContextTextEntry(
|
||||||
label = "Open submodule in new tab",
|
label = "Open submodule in new tab",
|
||||||
|
icon = { painterResource(AppIcons.OPEN) },
|
||||||
onClick = onOpenSubmoduleInTab,
|
onClick = onOpenSubmoduleInTab,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
ContextMenuElement.ContextTextEntry(
|
ContextMenuElement.ContextTextEntry(
|
||||||
label = "Sync submodule",
|
label = "Sync submodule",
|
||||||
|
icon = { painterResource(AppIcons.SYNC) },
|
||||||
onClick = onSyncSubmodule,
|
onClick = onSyncSubmodule,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -37,16 +42,29 @@ fun submoduleContextMenuItems(
|
|||||||
add(
|
add(
|
||||||
ContextMenuElement.ContextTextEntry(
|
ContextMenuElement.ContextTextEntry(
|
||||||
label = "Update submodule",
|
label = "Update submodule",
|
||||||
|
icon = { painterResource(AppIcons.UPDATE) },
|
||||||
onClick = onUpdateSubmodule,
|
onClick = onUpdateSubmodule,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
// 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(
|
add(
|
||||||
ContextMenuElement.ContextTextEntry(
|
ContextMenuElement.ContextTextEntry(
|
||||||
label = "DeInitialize submodule",
|
label = "Delete submodule",
|
||||||
onClick = onDeinitializeSubmodule,
|
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(
|
||||||
text = "Set branch name",
|
text = "New branch",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 8.dp),
|
.padding(bottom = 8.dp),
|
||||||
color = MaterialTheme.colors.onBackground,
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
@ -46,7 +46,6 @@ class CloneViewModel @Inject constructor(
|
|||||||
return@safeProcessingWithoutGit
|
return@safeProcessingWithoutGit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val urlSplit = url.split("/", "\\").toMutableList()
|
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/
|
// 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.di.TabScope
|
||||||
import com.jetpackduba.gitnuro.viewmodels.sidepanel.SidePanelViewModel
|
import com.jetpackduba.gitnuro.viewmodels.sidepanel.SidePanelViewModel
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.sidepanel.SubmoduleDialogViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -21,6 +22,7 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
||||||
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
||||||
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
||||||
|
private val submoduleDialogViewModelProvider: Provider<SubmoduleDialogViewModel>,
|
||||||
) {
|
) {
|
||||||
val viewModels = mapOf(
|
val viewModels = mapOf(
|
||||||
logViewModel::class to logViewModel,
|
logViewModel::class to logViewModel,
|
||||||
@ -40,6 +42,7 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
HistoryViewModel::class -> historyViewModelProvider.get()
|
HistoryViewModel::class -> historyViewModelProvider.get()
|
||||||
AuthorViewModel::class -> authorViewModelProvider.get()
|
AuthorViewModel::class -> authorViewModelProvider.get()
|
||||||
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
||||||
|
SubmoduleDialogViewModel::class -> submoduleDialogViewModelProvider.get()
|
||||||
else -> throw NotImplementedError("View model provider not implemented")
|
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 updateSubmoduleUseCase: UpdateSubmoduleUseCase,
|
||||||
private val syncSubmoduleUseCase: SyncSubmoduleUseCase,
|
private val syncSubmoduleUseCase: SyncSubmoduleUseCase,
|
||||||
private val deInitializeSubmoduleUseCase: DeInitializeSubmoduleUseCase,
|
private val deInitializeSubmoduleUseCase: DeInitializeSubmoduleUseCase,
|
||||||
|
private val addSubmoduleUseCase: AddSubmoduleUseCase,
|
||||||
|
private val deleteSubmoduleUseCase: DeleteSubmoduleUseCase,
|
||||||
private val tabScope: CoroutineScope,
|
private val tabScope: CoroutineScope,
|
||||||
private val tabsManager: TabsManager,
|
private val tabsManager: TabsManager,
|
||||||
@Assisted
|
@Assisted
|
||||||
@ -41,8 +43,7 @@ class SubmodulesViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
tabScope.launch {
|
tabScope.launch {
|
||||||
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.SUBMODULES)
|
tabState.refreshFlowFiltered(RefreshType.ALL_DATA, RefreshType.UNCOMMITED_CHANGES, RefreshType.SUBMODULES) {
|
||||||
{
|
|
||||||
refresh(tabState.git)
|
refresh(tabState.git)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,6 +91,23 @@ class SubmodulesViewModel @AssistedInject constructor(
|
|||||||
) { git ->
|
) { git ->
|
||||||
updateSubmoduleUseCase(git, path)
|
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)
|
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