diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt index d5ed6fd..876d414 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/TabState.kt @@ -12,6 +12,8 @@ import com.jetpackduba.gitnuro.models.Notification import com.jetpackduba.gitnuro.ui.SelectedItem import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.Ref @@ -54,9 +56,10 @@ class TabState @Inject constructor( } private val refreshData = MutableSharedFlow() - private val closeableViews = ArrayDeque() + private val closeableViews = ArrayDeque() + private val closeableViewsMutex = Mutex() - private val _closeView = MutableSharedFlow() + private val _closeView = MutableSharedFlow() val closeViewFlow = _closeView.asSharedFlow() /** @@ -311,15 +314,16 @@ class TabState @Inject constructor( } } - fun addCloseableView(id: Int) { - closeableViews.add(id) + suspend fun addCloseableView(view: CloseableView): Unit = closeableViewsMutex.withLock { + closeableViews.remove(view) // Remove any previous elements if present + closeableViews.add(view) } - fun removeCloseableView(id: Int) { - closeableViews.remove(id) + suspend fun removeCloseableView(view: CloseableView): Unit = closeableViewsMutex.withLock { + closeableViews.remove(view) } - suspend fun closeLastView() { + suspend fun closeLastView(): Unit = closeableViewsMutex.withLock { val last = closeableViews.removeLastOrNull() if (last != null) { @@ -344,3 +348,8 @@ enum class RefreshType { REMOTES, REBASE_INTERACTIVE_STATE, } + +enum class CloseableView { + DIFF, + LOG_SEARCH, +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt index ff1243b..248c1b6 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/RepositoryOpen.kt @@ -105,50 +105,54 @@ fun RepositoryOpenPage( val focusRequester = remember { FocusRequester() } - LaunchedEffect(selectedItem) { - focusRequester.requestFocus() - } - Column ( - modifier = Modifier.onPreviewKeyEvent { - println("Key event $it") - when { - it.matchesBinding(KeybindingOption.PULL) -> { - repositoryOpenViewModel.pull(PullType.DEFAULT) - true - } - it.matchesBinding(KeybindingOption.PUSH) -> { - repositoryOpenViewModel.push() - true - } - it.matchesBinding(KeybindingOption.BRANCH_CREATE) -> { - if (!showNewBranchDialog) { - showNewBranchDialog = true + Column( + modifier = Modifier + .focusRequester(focusRequester) + .focusable(true) + .onPreviewKeyEvent { + when { + it.matchesBinding(KeybindingOption.PULL) -> { + repositoryOpenViewModel.pull(PullType.DEFAULT) true - } else { - false } - } - it.matchesBinding(KeybindingOption.STASH) -> { - repositoryOpenViewModel.stash() - true - } - it.matchesBinding(KeybindingOption.STASH_POP) -> { - repositoryOpenViewModel.popStash() - true - } - it.matchesBinding(KeybindingOption.EXIT) -> { - repositoryOpenViewModel.closeLastView() - true - } - else -> false - } - } + it.matchesBinding(KeybindingOption.PUSH) -> { + repositoryOpenViewModel.push() + true + } + + it.matchesBinding(KeybindingOption.BRANCH_CREATE) -> { + if (!showNewBranchDialog) { + showNewBranchDialog = true + true + } else { + false + } + } + + it.matchesBinding(KeybindingOption.STASH) -> { + repositoryOpenViewModel.stash() + true + } + + it.matchesBinding(KeybindingOption.STASH_POP) -> { + repositoryOpenViewModel.popStash() + true + } + + it.matchesBinding(KeybindingOption.EXIT) -> { + repositoryOpenViewModel.closeLastView() + true + } + + else -> false + } + + } ) { Row(modifier = Modifier.weight(1f)) { Column( modifier = Modifier - .focusRequester(focusRequester) .focusable() .onKeyEvent { keyEvent -> if (keyEvent.matchesBinding(KeybindingOption.REFRESH)) { @@ -211,6 +215,10 @@ fun RepositoryOpenPage( onShowAuthorInfoDialog = { repositoryOpenViewModel.showAuthorInfoDialog() }, ) } + + LaunchedEffect(repositoryOpenViewModel) { + focusRequester.requestFocus() + } } @Composable 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 8c7b3f0..38c25b5 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap @@ -38,6 +39,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.jetpackduba.gitnuro.AppIcons import com.jetpackduba.gitnuro.extensions.* +import com.jetpackduba.gitnuro.git.CloseableView import com.jetpackduba.gitnuro.git.DiffType import com.jetpackduba.gitnuro.git.EntryContent import com.jetpackduba.gitnuro.git.animatedImages @@ -62,6 +64,7 @@ import com.jetpackduba.gitnuro.viewmodels.DiffViewModel import com.jetpackduba.gitnuro.viewmodels.TextDiffType import com.jetpackduba.gitnuro.viewmodels.ViewDiffResult import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.withContext import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.submodule.SubmoduleStatusType @@ -93,12 +96,23 @@ fun Diff( val viewDiffResult = diffResultState.value ?: return val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + diffViewModel.closeViewFlow.collectLatest { + if (it == CloseableView.DIFF) onCloseDiffView() + } + } + Column( modifier = Modifier .background(MaterialTheme.colors.background) .fillMaxSize() - .focusable() + .focusable(true) .focusRequester(focusRequester) + .onFocusChanged { + if (it.isFocused) { + diffViewModel.addToCloseables() + } + } .onPreviewKeyEvent { keyEvent -> if (keyEvent.matchesBinding(KeybindingOption.EXIT) && keyEvent.type == KeyEventType.KeyDown) { onCloseDiffView() diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt index 31f009f..c24c323 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -115,6 +116,7 @@ fun Log( repositoryState = repositoryState, changeDefaultUpstreamBranchViewModel = changeDefaultUpstreamBranchViewModel, ) + LogStatus.Loading -> LogLoading() } } @@ -204,8 +206,13 @@ private fun LogLoaded( if (searchFilterValue is LogSearch.SearchResults) { - SearchFilter(logViewModel, searchFilterValue) + SearchFilter( + logViewModel = logViewModel, + searchFilterResults = searchFilterValue, + searchFocused = { logViewModel.addSearchToCloseableView() }, + ) } + GraphHeader( graphWidth = graphWidth, onPaddingChange = { @@ -353,7 +360,8 @@ suspend fun scrollToUncommittedChanges( @Composable fun SearchFilter( logViewModel: LogViewModel, - searchFilterResults: LogSearch.SearchResults + searchFilterResults: LogSearch.SearchResults, + searchFocused: () -> Unit, ) { val scope = rememberCoroutineScope() var searchFilterText by remember { mutableStateOf(logViewModel.savedSearchFilter) } @@ -381,6 +389,11 @@ fun SearchFilter( modifier = Modifier .fillMaxSize() .focusRequester(textFieldFocusRequester) + .onFocusChanged { + if (it.isFocused) { + searchFocused() + } + } .onPreviewKeyEvent { keyEvent -> when { keyEvent.matchesBinding(KeybindingOption.SIMPLE_ACCEPT) -> { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/DiffViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/DiffViewModel.kt index aca37a3..4c39422 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/DiffViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/DiffViewModel.kt @@ -3,6 +3,7 @@ package com.jetpackduba.gitnuro.viewmodels import androidx.compose.foundation.lazy.LazyListState import com.jetpackduba.gitnuro.exceptions.MissingDiffEntryException import com.jetpackduba.gitnuro.extensions.delayedStateChange +import com.jetpackduba.gitnuro.git.CloseableView import com.jetpackduba.gitnuro.git.DiffType import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.TabState @@ -38,10 +39,12 @@ class DiffViewModel @Inject constructor( private val discardUnstagedHunkLineUseCase: DiscardUnstagedHunkLineUseCase, private val tabsManager: TabsManager, tabScope: CoroutineScope, -) { +) : AutoCloseable { private val _diffResult = MutableStateFlow(ViewDiffResult.Loading("")) val diffResult: StateFlow = _diffResult + val closeViewFlow = tabState.closeViewFlow + val diffTypeFlow = settings.textDiffTypeFlow val isDisplayFullFile = settings.diffDisplayFullFileFlow @@ -92,6 +95,8 @@ class DiffViewModel @Inject constructor( ) fun updateDiff(diffType: DiffType) { + addToCloseables() + diffJob = tabState.runOperation(refreshType = RefreshType.NONE) { git -> this.diffType = diffType @@ -223,6 +228,19 @@ class DiffViewModel @Inject constructor( fun openSubmodule(path: String) = tabState.runOperation(refreshType = RefreshType.NONE) { git -> tabsManager.addNewTabFromPath("${git.repository.workTree}/$path", true) } + + fun addToCloseables() = tabState.runOperation(refreshType = RefreshType.NONE) { _ -> + tabState.addCloseableView(CloseableView.DIFF) + } + + private fun removeFromCloseables() = tabState.runOperation(refreshType = RefreshType.NONE) { _ -> + tabState.removeCloseableView(CloseableView.DIFF) + } + + override fun close() { + cancelRunningJobs() + removeFromCloseables() + } } enum class TextDiffType(val value: Int) { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt index 967572e..592e55f 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/LogViewModel.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.lazy.LazyListState import com.jetpackduba.gitnuro.TaskType import com.jetpackduba.gitnuro.extensions.delayedStateChange import com.jetpackduba.gitnuro.extensions.shortName +import com.jetpackduba.gitnuro.git.CloseableView import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.TabState import com.jetpackduba.gitnuro.git.TaskEvent @@ -124,6 +125,14 @@ class LogViewModel @Inject constructor( refresh(tabState.git) } } + + tabScope.launch { + tabState.closeViewFlow.collectLatest { + if (it == CloseableView.LOG_SEARCH) { + _logSearchFilterResults.value = LogSearch.NotSearching + } + } + } } @@ -325,8 +334,11 @@ class LogViewModel @Inject constructor( } _logSearchFilterResults.value = LogSearch.SearchResults(matchingCommits, startingUiIndex) - } else + addSearchToCloseableView() + } else { _logSearchFilterResults.value = LogSearch.SearchResults(emptyList(), NONE_MATCHING_INDEX) + addSearchToCloseableView() + } } suspend fun selectPreviousFilterCommit() { @@ -347,6 +359,7 @@ class LogViewModel @Inject constructor( _logSearchFilterResults.value = logSearchFilterResultsValue.copy(index = newIndex) _focusCommit.emit(newCommitToSelect) + addSearchToCloseableView() } suspend fun selectNextFilterCommit() { @@ -369,6 +382,7 @@ class LogViewModel @Inject constructor( _logSearchFilterResults.value = logSearchFilterResultsValue.copy(index = newIndex) _focusCommit.emit(newCommitToSelect) + addSearchToCloseableView() } fun showDialog(dialog: LogDialog) { @@ -377,6 +391,15 @@ class LogViewModel @Inject constructor( fun closeSearch() { _logSearchFilterResults.value = LogSearch.NotSearching + removeSearchFromCloseableView() + } + + fun addSearchToCloseableView() = tabState.runOperation(refreshType = RefreshType.NONE) { _ -> + tabState.addCloseableView(CloseableView.LOG_SEARCH) + } + + private fun removeSearchFromCloseableView() = tabState.runOperation(refreshType = RefreshType.NONE) { _ -> + tabState.removeCloseableView(CloseableView.LOG_SEARCH) } fun rebaseInteractive(revCommit: RevCommit) = tabState.safeProcessing( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt index f26e576..27aadfc 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/RepositoryOpenViewModel.kt @@ -122,8 +122,6 @@ class RepositoryOpenViewModel @Inject constructor( launch { watchRepositoryChanges(tabState.git) } - - launch { tabState.refreshData(RefreshType.ALL_DATA) } } } @@ -252,7 +250,7 @@ class RepositoryOpenViewModel @Inject constructor( diffViewModel?.cancelRunningJobs() diffViewModel?.updateDiff(diffSelected) } else { - diffViewModel?.cancelRunningJobs() + diffViewModel?.close() diffViewModel = null // Free the view model from the memory if not being used. } }