diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt b/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt index a24f8bcc..772108e2 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/keybindings/Keybinding.kt @@ -42,6 +42,21 @@ enum class KeybindingOption { * Used to go down in lists */ DOWN, + + /** + * Used to pull in current repository + */ + PULL, + + /** + * Used to push in current repository + */ + PUSH, + + /** + * Used to show branch creation dialog + */ + BRANCH_CREATE, } @@ -66,6 +81,15 @@ private fun baseKeybindings() = mapOf( KeybindingOption.DOWN to listOf( Keybinding(key = Key.DirectionDown), ), + KeybindingOption.PULL to listOf( + Keybinding(key = Key.U, control = true), + ), + KeybindingOption.PUSH to listOf( + Keybinding(key = Key.P, control = true), + ), + KeybindingOption.BRANCH_CREATE to listOf( + Keybinding(key = Key.B, control = true), + ), ) private fun linuxKeybindings(): Map> = baseKeybindings() diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt index 85fffc25..208ed0b7 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt @@ -11,12 +11,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.jetpackduba.gitnuro.AppConstants import com.jetpackduba.gitnuro.extensions.handMouseClickable import com.jetpackduba.gitnuro.git.DiffType import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState +import com.jetpackduba.gitnuro.git.remote_operations.PullType import com.jetpackduba.gitnuro.keybindings.KeybindingOption import com.jetpackduba.gitnuro.keybindings.matchesBinding import com.jetpackduba.gitnuro.models.AuthorInfoSimple @@ -106,7 +108,31 @@ fun RepositoryOpenPage( LaunchedEffect(selectedItem) { focusRequester.requestFocus() } - Column { + Column ( + modifier = Modifier.onPreviewKeyEvent { + println("Key event $it") + when { + it.matchesBinding(KeybindingOption.PULL) -> { + tabViewModel.pull(PullType.DEFAULT) + true + } + it.matchesBinding(KeybindingOption.PUSH) -> { + tabViewModel.push() + true + } + it.matchesBinding(KeybindingOption.BRANCH_CREATE) -> { + if (!showNewBranchDialog) { + showNewBranchDialog = true + true + } else { + false + } + } + else -> false + } + + } + ) { Row(modifier = Modifier.weight(1f)) { Column( modifier = Modifier diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt index e149d430..08ada0a3 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt @@ -33,6 +33,8 @@ import androidx.compose.ui.unit.dp import com.jetpackduba.gitnuro.AppConstants import com.jetpackduba.gitnuro.AppIcons import com.jetpackduba.gitnuro.extensions.* +import com.jetpackduba.gitnuro.keybindings.KeybindingOption +import com.jetpackduba.gitnuro.keybindings.matchesBinding import com.jetpackduba.gitnuro.theme.AppTheme import com.jetpackduba.gitnuro.theme.backgroundSelected import com.jetpackduba.gitnuro.theme.onBackgroundSecondary @@ -343,8 +345,9 @@ fun RecentRepositoriesList( if (it.type != KeyEventType.KeyDown) { return@onPreviewKeyEvent false } - when (it.key) { - Key.DirectionDown -> { + + when { + it.matchesBinding(KeybindingOption.DOWN) -> { if (focusedItemIndex < filteredRepositories.lastIndex) { focusedItemIndex += 1 scope.launch { listState.animateScrollToItem(focusedItemIndex) } @@ -352,7 +355,7 @@ fun RecentRepositoriesList( true } - Key.DirectionUp -> { + it.matchesBinding(KeybindingOption.UP) -> { if (focusedItemIndex > 0) { focusedItemIndex -= 1 scope.launch { listState.animateScrollToItem(focusedItemIndex) } @@ -360,7 +363,7 @@ fun RecentRepositoriesList( true } - Key.Enter -> { + it.matchesBinding(KeybindingOption.SIMPLE_ACCEPT) -> { val repo = filteredRepositories.getOrNull(focusedItemIndex) if (repo != null && isSearchFocused) { onOpenKnownRepository(repo) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt index dd0a78db..8c7b3f00 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt @@ -209,10 +209,6 @@ fun Diff( ViewDiffResult.None -> throw NotImplementedError("None should be a possible state in the diff") } - - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } } } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt new file mode 100644 index 00000000..95b856df --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/GlobalMenuActionsViewModel.kt @@ -0,0 +1,104 @@ +package com.jetpackduba.gitnuro.viewmodels + +import com.jetpackduba.gitnuro.TaskType +import com.jetpackduba.gitnuro.git.RefreshType +import com.jetpackduba.gitnuro.git.TabState +import com.jetpackduba.gitnuro.git.remote_operations.FetchAllRemotesUseCase +import com.jetpackduba.gitnuro.git.remote_operations.PullBranchUseCase +import com.jetpackduba.gitnuro.git.remote_operations.PullType +import com.jetpackduba.gitnuro.git.remote_operations.PushBranchUseCase +import com.jetpackduba.gitnuro.git.stash.PopLastStashUseCase +import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase +import com.jetpackduba.gitnuro.managers.AppStateManager +import com.jetpackduba.gitnuro.models.errorNotification +import com.jetpackduba.gitnuro.models.positiveNotification +import com.jetpackduba.gitnuro.repositories.AppSettingsRepository +import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase +import kotlinx.coroutines.Job +import javax.inject.Inject + +interface IGlobalMenuActionsViewModel { + fun pull(pullType: PullType): Job + fun fetchAll(): Job + fun push(force: Boolean = false, pushTags: Boolean = false): Job + fun stash(): Job + fun popStash(): Job + fun openTerminal(): Job +} + +class GlobalMenuActionsViewModel @Inject constructor( + private val tabState: TabState, + private val pullBranchUseCase: PullBranchUseCase, + private val pushBranchUseCase: PushBranchUseCase, + private val fetchAllRemotesUseCase: FetchAllRemotesUseCase, + private val popLastStashUseCase: PopLastStashUseCase, + private val stashChangesUseCase: StashChangesUseCase, + private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase, + settings: AppSettingsRepository, + appStateManager: AppStateManager, +) : IGlobalMenuActionsViewModel { + override fun pull(pullType: PullType) = tabState.safeProcessing( + refreshType = RefreshType.ALL_DATA, + title = "Pulling", + subtitle = "Pulling changes from the remote branch to the current branch", + refreshEvenIfCrashes = true, + taskType = TaskType.PULL, + ) { git -> + pullBranchUseCase(git, pullType) + + positiveNotification("Pull completed") + } + + override fun fetchAll() = tabState.safeProcessing( + refreshType = RefreshType.ALL_DATA, + title = "Fetching", + subtitle = "Updating references from the remote repositories...", + isCancellable = false, + refreshEvenIfCrashes = true, + taskType = TaskType.FETCH, + ) { git -> + fetchAllRemotesUseCase(git) + + positiveNotification("Fetch all completed") + } + + override fun push(force: Boolean, pushTags: Boolean) = tabState.safeProcessing( + refreshType = RefreshType.ALL_DATA, + title = "Push", + subtitle = "Pushing current branch to the remote repository", + isCancellable = false, + refreshEvenIfCrashes = true, + taskType = TaskType.PUSH, + ) { git -> + pushBranchUseCase(git, force, pushTags) + + positiveNotification("Push completed") + } + + override fun stash() = tabState.safeProcessing( + refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, + taskType = TaskType.STASH, + ) { git -> + if (stashChangesUseCase(git, null)) { + positiveNotification("Changes stashed") + } else { + errorNotification("There are no changes to stash") + } + } + + override fun popStash() = tabState.safeProcessing( + refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, + refreshEvenIfCrashes = true, + taskType = TaskType.POP_STASH, + ) { git -> + popLastStashUseCase(git) + + positiveNotification("Stash popped") + } + + override fun openTerminal() = tabState.runOperation( + refreshType = RefreshType.NONE + ) { git -> + openRepositoryInTerminalUseCase(git.repository.workTree.absolutePath) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt index 9e0a6079..dfb96819 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/MenuViewModel.kt @@ -20,80 +20,11 @@ import javax.inject.Inject class MenuViewModel @Inject constructor( private val tabState: TabState, - private val pullBranchUseCase: PullBranchUseCase, - private val pushBranchUseCase: PushBranchUseCase, - private val fetchAllRemotesUseCase: FetchAllRemotesUseCase, - private val popLastStashUseCase: PopLastStashUseCase, - private val stashChangesUseCase: StashChangesUseCase, - private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase, + private val globalMenuActionsViewModel: GlobalMenuActionsViewModel, settings: AppSettingsRepository, appStateManager: AppStateManager, -) { +): IGlobalMenuActionsViewModel by globalMenuActionsViewModel { val isPullWithRebaseDefault = settings.pullRebaseFlow val lastLoadedTabs = appStateManager.latestOpenedRepositoriesPaths - fun pull(pullType: PullType) = tabState.safeProcessing( - refreshType = RefreshType.ALL_DATA, - title = "Pulling", - subtitle = "Pulling changes from the remote branch to the current branch", - refreshEvenIfCrashes = true, - taskType = TaskType.PULL, - ) { git -> - pullBranchUseCase(git, pullType) - - positiveNotification("Pull completed") - } - - fun fetchAll() = tabState.safeProcessing( - refreshType = RefreshType.ALL_DATA, - title = "Fetching", - subtitle = "Updating references from the remote repositories...", - isCancellable = false, - refreshEvenIfCrashes = true, - taskType = TaskType.FETCH, - ) { git -> - fetchAllRemotesUseCase(git) - - positiveNotification("Fetch all completed") - } - - fun push(force: Boolean = false, pushTags: Boolean = false) = tabState.safeProcessing( - refreshType = RefreshType.ALL_DATA, - title = "Push", - subtitle = "Pushing current branch to the remote repository", - isCancellable = false, - refreshEvenIfCrashes = true, - taskType = TaskType.PUSH, - ) { git -> - pushBranchUseCase(git, force, pushTags) - - positiveNotification("Push completed") - } - - fun stash() = tabState.safeProcessing( - refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, - taskType = TaskType.STASH, - ) { git -> - if (stashChangesUseCase(git, null)) { - positiveNotification("Changes stashed") - } else { - errorNotification("There are no changes to stash") - } - } - - fun popStash() = tabState.safeProcessing( - refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, - refreshEvenIfCrashes = true, - taskType = TaskType.POP_STASH, - ) { git -> - popLastStashUseCase(git) - - positiveNotification("Stash popped") - } - - fun openTerminal() = tabState.runOperation( - refreshType = RefreshType.NONE - ) { git -> - openRepositoryInTerminalUseCase(git.repository.workTree.absolutePath) - } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt index de6aa9d5..673f3a45 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/TabViewModel.kt @@ -22,7 +22,6 @@ import com.jetpackduba.gitnuro.managers.newErrorNow import com.jetpackduba.gitnuro.models.AuthorInfoSimple import com.jetpackduba.gitnuro.models.errorNotification import com.jetpackduba.gitnuro.models.positiveNotification -import com.jetpackduba.gitnuro.models.warningNotification import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase import com.jetpackduba.gitnuro.system.OpenUrlInBrowserUseCase import com.jetpackduba.gitnuro.system.PickerType @@ -77,7 +76,9 @@ class TabViewModel @Inject constructor( private val tabScope: CoroutineScope, private val verticalSplitPaneConfig: VerticalSplitPaneConfig, val tabViewModelsProvider: TabViewModelsProvider, -) : IVerticalSplitPaneConfig by verticalSplitPaneConfig { + private val globalMenuActionsViewModel: GlobalMenuActionsViewModel, +) : IVerticalSplitPaneConfig by verticalSplitPaneConfig, + IGlobalMenuActionsViewModel by globalMenuActionsViewModel { var initialPath: String? = null // Stores the path that should be opened when the tab is selected val errorsManager: ErrorsManager = tabState.errorsManager val selectedItem: StateFlow = tabState.selectedItem