parent
45e4f9e799
commit
37a65ffc11
@ -7,8 +7,8 @@ import com.jetpackduba.gitnuro.viewmodels.TextDiffType
|
||||
import com.jetpackduba.gitnuro.viewmodels.textDiffTypeFromValue
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
@ -27,6 +27,7 @@ private const val PREF_WINDOW_PLACEMENT = "windowsPlacement"
|
||||
private const val PREF_CUSTOM_THEME = "customTheme"
|
||||
private const val PREF_UI_SCALE = "ui_scale"
|
||||
private const val PREF_DIFF_TYPE = "diffType"
|
||||
private const val PREF_SWAP_UNCOMMITED_CHANGES = "inverseUncommitedChanges"
|
||||
|
||||
|
||||
private const val PREF_GIT_FF_MERGE = "gitFFMerge"
|
||||
@ -34,6 +35,7 @@ private const val PREF_GIT_PULL_REBASE = "gitPullRebase"
|
||||
|
||||
private const val DEFAULT_COMMITS_LIMIT = 1000
|
||||
private const val DEFAULT_COMMITS_LIMIT_ENABLED = true
|
||||
private const val DEFAULT_SWAP_UNCOMMITED_CHANGES = false
|
||||
const val DEFAULT_UI_SCALE = -1f
|
||||
|
||||
@Singleton
|
||||
@ -41,28 +43,31 @@ class AppSettings @Inject constructor() {
|
||||
private val preferences: Preferences = Preferences.userRoot().node(PREFERENCES_NAME)
|
||||
|
||||
private val _themeState = MutableStateFlow(theme)
|
||||
val themeState: StateFlow<Theme> = _themeState
|
||||
val themeState = _themeState.asStateFlow()
|
||||
|
||||
private val _commitsLimitEnabledFlow = MutableStateFlow(commitsLimitEnabled)
|
||||
val commitsLimitEnabledFlow: MutableStateFlow<Boolean> = _commitsLimitEnabledFlow
|
||||
val commitsLimitEnabledFlow = _commitsLimitEnabledFlow.asStateFlow()
|
||||
|
||||
private val _swapUncommitedChangesFlow = MutableStateFlow(swapUncommitedChanges)
|
||||
val swapUncommitedChangesFlow = _swapUncommitedChangesFlow.asStateFlow()
|
||||
|
||||
private val _ffMergeFlow = MutableStateFlow(ffMerge)
|
||||
val ffMergeFlow: StateFlow<Boolean> = _ffMergeFlow
|
||||
val ffMergeFlow = _ffMergeFlow.asStateFlow()
|
||||
|
||||
private val _pullRebaseFlow = MutableStateFlow(pullRebase)
|
||||
val pullRebaseFlow: StateFlow<Boolean> = _pullRebaseFlow
|
||||
val pullRebaseFlow = _pullRebaseFlow.asStateFlow()
|
||||
|
||||
private val _commitsLimitFlow = MutableSharedFlow<Int>()
|
||||
val commitsLimitFlow: SharedFlow<Int> = _commitsLimitFlow
|
||||
val commitsLimitFlow = _commitsLimitFlow.asSharedFlow()
|
||||
|
||||
private val _customThemeFlow = MutableStateFlow<ColorsScheme?>(null)
|
||||
val customThemeFlow: StateFlow<ColorsScheme?> = _customThemeFlow
|
||||
val customThemeFlow = _customThemeFlow.asStateFlow()
|
||||
|
||||
private val _scaleUiFlow = MutableStateFlow(scaleUi)
|
||||
val scaleUiFlow: StateFlow<Float> = _scaleUiFlow
|
||||
val scaleUiFlow = _scaleUiFlow.asStateFlow()
|
||||
|
||||
private val _textDiffTypeFlow = MutableStateFlow(textDiffType)
|
||||
val textDiffTypeFlow: StateFlow<TextDiffType> = _textDiffTypeFlow
|
||||
val textDiffTypeFlow = _textDiffTypeFlow.asStateFlow()
|
||||
|
||||
var latestTabsOpened: String
|
||||
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
|
||||
@ -100,6 +105,15 @@ class AppSettings @Inject constructor() {
|
||||
_commitsLimitEnabledFlow.value = value
|
||||
}
|
||||
|
||||
var swapUncommitedChanges: Boolean
|
||||
get() {
|
||||
return preferences.getBoolean(PREF_SWAP_UNCOMMITED_CHANGES, DEFAULT_SWAP_UNCOMMITED_CHANGES)
|
||||
}
|
||||
set(value) {
|
||||
preferences.putBoolean(PREF_SWAP_UNCOMMITED_CHANGES, value)
|
||||
_swapUncommitedChangesFlow.value = value
|
||||
}
|
||||
|
||||
var scaleUi: Float
|
||||
get() {
|
||||
return preferences.getFloat(PREF_UI_SCALE, DEFAULT_UI_SCALE)
|
||||
|
@ -14,16 +14,12 @@ import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
@ -44,7 +40,10 @@ import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||
import com.jetpackduba.gitnuro.theme.*
|
||||
import com.jetpackduba.gitnuro.ui.components.*
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.*
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.EntryType
|
||||
import com.jetpackduba.gitnuro.ui.context_menu.statusEntriesContextMenuItems
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.CommitAuthorDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.CommitterDataRequestState
|
||||
import com.jetpackduba.gitnuro.viewmodels.StageState
|
||||
@ -62,6 +61,7 @@ fun UncommitedChanges(
|
||||
onHistoryFile: (String) -> Unit,
|
||||
) {
|
||||
val stageStatus = statusViewModel.stageState.collectAsState().value
|
||||
val swapUncommitedChanges by statusViewModel.swapUncommitedChanges.collectAsState()
|
||||
var commitMessage by remember(statusViewModel) { mutableStateOf(statusViewModel.savedCommitMessage.message) }
|
||||
val stagedListState by statusViewModel.stagedLazyListState.collectAsState()
|
||||
val unstagedListState by statusViewModel.unstagedLazyListState.collectAsState()
|
||||
@ -129,88 +129,100 @@ fun UncommitedChanges(
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
EntriesList(
|
||||
modifier = Modifier
|
||||
.weight(5f)
|
||||
.padding(bottom = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
title = "Staged",
|
||||
allActionTitle = "Unstage all",
|
||||
actionTitle = "Unstage",
|
||||
actionIcon = AppIcons.REMOVE_DONE,
|
||||
selectedEntryType = if (selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
|
||||
actionColor = MaterialTheme.colors.error,
|
||||
actionTextColor = MaterialTheme.colors.onError,
|
||||
statusEntries = staged,
|
||||
lazyListState = stagedListState,
|
||||
onDiffEntrySelected = onStagedDiffEntrySelected,
|
||||
showSearch = showSearchStaged,
|
||||
searchFilter = searchFilterStaged,
|
||||
onSearchFilterToggled = {
|
||||
statusViewModel.onSearchFilterToggledStaged(it)
|
||||
},
|
||||
onSearchFilterChanged = {
|
||||
statusViewModel.onSearchFilterChangedStaged(it)
|
||||
},
|
||||
onDiffEntryOptionSelected = {
|
||||
statusViewModel.unstage(it)
|
||||
},
|
||||
onGenerateContextMenu = { statusEntry ->
|
||||
statusEntriesContextMenuItems(
|
||||
statusEntry = statusEntry,
|
||||
entryType = EntryType.STAGED,
|
||||
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||
onReset = { statusViewModel.resetStaged(statusEntry) },
|
||||
onHistory = { onHistoryFile(statusEntry.filePath) },
|
||||
)
|
||||
},
|
||||
onAllAction = {
|
||||
statusViewModel.unstageAll()
|
||||
},
|
||||
)
|
||||
val stagedView: @Composable () -> Unit = {
|
||||
EntriesList(
|
||||
modifier = Modifier
|
||||
.weight(5f)
|
||||
.padding(bottom = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
title = "Staged",
|
||||
allActionTitle = "Unstage all",
|
||||
actionTitle = "Unstage",
|
||||
actionIcon = AppIcons.REMOVE_DONE,
|
||||
selectedEntryType = if (selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
|
||||
actionColor = MaterialTheme.colors.error,
|
||||
actionTextColor = MaterialTheme.colors.onError,
|
||||
statusEntries = staged,
|
||||
lazyListState = stagedListState,
|
||||
onDiffEntrySelected = onStagedDiffEntrySelected,
|
||||
showSearch = showSearchStaged,
|
||||
searchFilter = searchFilterStaged,
|
||||
onSearchFilterToggled = {
|
||||
statusViewModel.onSearchFilterToggledStaged(it)
|
||||
},
|
||||
onSearchFilterChanged = {
|
||||
statusViewModel.onSearchFilterChangedStaged(it)
|
||||
},
|
||||
onDiffEntryOptionSelected = {
|
||||
statusViewModel.unstage(it)
|
||||
},
|
||||
onGenerateContextMenu = { statusEntry ->
|
||||
statusEntriesContextMenuItems(
|
||||
statusEntry = statusEntry,
|
||||
entryType = EntryType.STAGED,
|
||||
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||
onReset = { statusViewModel.resetStaged(statusEntry) },
|
||||
onHistory = { onHistoryFile(statusEntry.filePath) },
|
||||
)
|
||||
},
|
||||
onAllAction = {
|
||||
statusViewModel.unstageAll()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
EntriesList(
|
||||
modifier = Modifier
|
||||
.weight(5f)
|
||||
.padding(bottom = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
title = "Unstaged",
|
||||
actionTitle = "Stage",
|
||||
actionIcon = AppIcons.DONE,
|
||||
selectedEntryType = if (selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
|
||||
actionColor = MaterialTheme.colors.primary,
|
||||
actionTextColor = MaterialTheme.colors.onPrimary,
|
||||
statusEntries = unstaged,
|
||||
lazyListState = unstagedListState,
|
||||
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
||||
showSearch = showSearchUnstaged,
|
||||
searchFilter = searchFilterUnstaged,
|
||||
onSearchFilterToggled = {
|
||||
statusViewModel.onSearchFilterToggledUnstaged(it)
|
||||
},
|
||||
onSearchFilterChanged = {
|
||||
statusViewModel.onSearchFilterChangedUnstaged(it)
|
||||
},
|
||||
onDiffEntryOptionSelected = {
|
||||
statusViewModel.stage(it)
|
||||
},
|
||||
onGenerateContextMenu = { statusEntry ->
|
||||
statusEntriesContextMenuItems(
|
||||
statusEntry = statusEntry,
|
||||
entryType = EntryType.UNSTAGED,
|
||||
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||
onHistory = { onHistoryFile(statusEntry.filePath) },
|
||||
onReset = { statusViewModel.resetUnstaged(statusEntry) },
|
||||
onDelete = {
|
||||
statusViewModel.deleteFile(statusEntry)
|
||||
},
|
||||
)
|
||||
},
|
||||
onAllAction = {
|
||||
statusViewModel.stageAll()
|
||||
},
|
||||
allActionTitle = "Stage all",
|
||||
)
|
||||
val unstagedView: @Composable () -> Unit = {
|
||||
EntriesList(
|
||||
modifier = Modifier
|
||||
.weight(5f)
|
||||
.padding(bottom = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
title = "Unstaged",
|
||||
actionTitle = "Stage",
|
||||
actionIcon = AppIcons.DONE,
|
||||
selectedEntryType = if (selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
|
||||
actionColor = MaterialTheme.colors.primary,
|
||||
actionTextColor = MaterialTheme.colors.onPrimary,
|
||||
statusEntries = unstaged,
|
||||
lazyListState = unstagedListState,
|
||||
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
||||
showSearch = showSearchUnstaged,
|
||||
searchFilter = searchFilterUnstaged,
|
||||
onSearchFilterToggled = {
|
||||
statusViewModel.onSearchFilterToggledUnstaged(it)
|
||||
},
|
||||
onSearchFilterChanged = {
|
||||
statusViewModel.onSearchFilterChangedUnstaged(it)
|
||||
},
|
||||
onDiffEntryOptionSelected = {
|
||||
statusViewModel.stage(it)
|
||||
},
|
||||
onGenerateContextMenu = { statusEntry ->
|
||||
statusEntriesContextMenuItems(
|
||||
statusEntry = statusEntry,
|
||||
entryType = EntryType.UNSTAGED,
|
||||
onBlame = { onBlameFile(statusEntry.filePath) },
|
||||
onHistory = { onHistoryFile(statusEntry.filePath) },
|
||||
onReset = { statusViewModel.resetUnstaged(statusEntry) },
|
||||
onDelete = {
|
||||
statusViewModel.deleteFile(statusEntry)
|
||||
},
|
||||
)
|
||||
},
|
||||
onAllAction = {
|
||||
statusViewModel.stageAll()
|
||||
},
|
||||
allActionTitle = "Stage all",
|
||||
)
|
||||
}
|
||||
|
||||
if (swapUncommitedChanges) {
|
||||
unstagedView()
|
||||
stagedView()
|
||||
} else {
|
||||
stagedView()
|
||||
unstagedView()
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
|
@ -40,7 +40,7 @@ sealed interface SettingsEntry {
|
||||
val settings = listOf(
|
||||
SettingsEntry.Section("User interface"),
|
||||
SettingsEntry.Entry(AppIcons.PALETTE, "Appearance") { UiSettings(it) },
|
||||
SettingsEntry.Entry(AppIcons.LAYOUT, "Layout") { },
|
||||
SettingsEntry.Entry(AppIcons.LAYOUT, "Layout") { Layout(it) },
|
||||
|
||||
SettingsEntry.Section("GIT"),
|
||||
SettingsEntry.Entry(AppIcons.LIST, "Commits history") { GitSettings(it) },
|
||||
@ -234,6 +234,20 @@ fun GitSettings(settingsViewModel: SettingsViewModel) {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Layout(settingsViewModel: SettingsViewModel) {
|
||||
val swapUncommitedChanges by settingsViewModel.swapUncommitedChangesFlow.collectAsState()
|
||||
|
||||
SettingToggle(
|
||||
title = "Swap position for staged/unstaged views",
|
||||
subtitle = "Show the list of unstaged changes above the list of staged changes",
|
||||
value = swapUncommitedChanges,
|
||||
onValueChanged = { value ->
|
||||
settingsViewModel.swapUncommitedChanges = value
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UiSettings(settingsViewModel: SettingsViewModel) {
|
||||
val currentTheme by settingsViewModel.themeState.collectAsState()
|
||||
|
@ -25,6 +25,7 @@ class SettingsViewModel @Inject constructor(
|
||||
val ffMergeFlow = appSettings.ffMergeFlow
|
||||
val pullRebaseFlow = appSettings.pullRebaseFlow
|
||||
val commitsLimitEnabledFlow = appSettings.commitsLimitEnabledFlow
|
||||
val swapUncommitedChangesFlow = appSettings.swapUncommitedChangesFlow
|
||||
|
||||
var scaleUi: Float
|
||||
get() = appSettings.scaleUi
|
||||
@ -38,6 +39,12 @@ class SettingsViewModel @Inject constructor(
|
||||
appSettings.commitsLimitEnabled = value
|
||||
}
|
||||
|
||||
var swapUncommitedChanges: Boolean
|
||||
get() = appSettings.swapUncommitedChanges
|
||||
set(value) {
|
||||
appSettings.swapUncommitedChanges = value
|
||||
}
|
||||
|
||||
var ffMerge: Boolean
|
||||
get() = appSettings.ffMerge
|
||||
set(value) {
|
||||
|
@ -18,6 +18,7 @@ import com.jetpackduba.gitnuro.git.rebase.SkipRebaseUseCase
|
||||
import com.jetpackduba.gitnuro.git.repository.ResetRepositoryStateUseCase
|
||||
import com.jetpackduba.gitnuro.git.workspace.*
|
||||
import com.jetpackduba.gitnuro.models.AuthorInfo
|
||||
import com.jetpackduba.gitnuro.preferences.AppSettings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
@ -52,6 +53,7 @@ class StatusViewModel @Inject constructor(
|
||||
private val loadAuthorUseCase: LoadAuthorUseCase,
|
||||
private val saveAuthorUseCase: SaveAuthorUseCase,
|
||||
private val tabScope: CoroutineScope,
|
||||
private val appSettings: AppSettings,
|
||||
) {
|
||||
private val _showSearchUnstaged = MutableStateFlow(false)
|
||||
val showSearchUnstaged: StateFlow<Boolean> = _showSearchUnstaged
|
||||
@ -65,6 +67,8 @@ class StatusViewModel @Inject constructor(
|
||||
private val _searchFilterStaged = MutableStateFlow(TextFieldValue(""))
|
||||
val searchFilterStaged: StateFlow<TextFieldValue> = _searchFilterStaged
|
||||
|
||||
val swapUncommitedChanges = appSettings.swapUncommitedChangesFlow
|
||||
|
||||
private val _stageState = MutableStateFlow<StageState>(StageState.Loading)
|
||||
|
||||
val stageState: StateFlow<StageState> = combine(
|
||||
|
Loading…
Reference in New Issue
Block a user