Added scaling setting

This commit is contained in:
Abdelilah El Aissaoui 2022-08-04 00:38:46 +02:00
parent 02906a253d
commit 591fa98508
6 changed files with 346 additions and 113 deletions

View File

@ -11,12 +11,13 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerIconDefaults
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import app.di.DaggerAppComponent
@ -29,11 +30,13 @@ import app.theme.secondaryTextColor
import app.ui.AppTab
import app.ui.components.RepositoriesTabPanel
import app.ui.components.TabInformation
import app.ui.dialogs.SettingsDialog
import app.ui.dialogs.settings.SettingsDialog
import app.viewmodels.SettingsViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
class App {
private val appComponent = DaggerAppComponent.create()
@ -43,6 +46,9 @@ class App {
@Inject
lateinit var appPreferences: AppPreferences
@Inject
lateinit var settingsViewModel: SettingsViewModel
init {
appComponent.inject(this)
}
@ -60,6 +66,7 @@ class App {
var isOpen by remember { mutableStateOf(true) }
val theme by appPreferences.themeState.collectAsState()
val customTheme by appPreferences.customThemeFlow.collectAsState()
val scale by appPreferences.scaleUiFlow.collectAsState()
val windowState = rememberWindowState(
placement = windowPlacement,
@ -78,25 +85,32 @@ class App {
state = windowState,
icon = painterResource("logo.svg"),
) {
var showSettingsDialog by remember { mutableStateOf(false) }
val density = if(scale != -1f) {
arrayOf(LocalDensity provides Density(scale, 1f))
} else
emptyArray()
AppTheme(
selectedTheme = theme,
customTheme = customTheme,
) {
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
AppTabs(
onOpenSettings = {
showSettingsDialog = true
}
)
}
CompositionLocalProvider(values = density) {
var showSettingsDialog by remember { mutableStateOf(false) }
if (showSettingsDialog) {
SettingsDialog(
appPreferences = appPreferences,
onDismiss = { showSettingsDialog = false }
)
AppTheme(
selectedTheme = theme,
customTheme = customTheme,
) {
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
AppTabs(
onOpenSettings = {
showSettingsDialog = true
}
)
}
if (showSettingsDialog) {
SettingsDialog(
settingsViewModel = settingsViewModel,
onDismiss = { showSettingsDialog = false }
)
}
}
}
}
@ -104,6 +118,7 @@ class App {
appStateManager.cancelCoroutines()
this.exitApplication()
}
}
}

View File

@ -2,8 +2,7 @@ package app.preferences
import app.extensions.defaultWindowPlacement
import app.theme.ColorsScheme
import app.theme.Themes
import app.theme.darkBlueTheme
import app.theme.Theme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.serialization.json.Json
@ -12,7 +11,6 @@ import java.util.prefs.Preferences
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
private const val PREFERENCES_NAME = "GitnuroConfig"
@ -23,19 +21,21 @@ private const val PREF_COMMITS_LIMIT = "commitsLimit"
private const val PREF_COMMITS_LIMIT_ENABLED = "commitsLimitEnabled"
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_GIT_FF_MERGE = "gitFFMerge"
private const val DEFAULT_COMMITS_LIMIT = 1000
private const val DEFAULT_COMMITS_LIMIT_ENABLED = true
const val DEFAULT_UI_SCALE = -1f
@Singleton
class AppPreferences @Inject constructor() {
private val preferences: Preferences = Preferences.userRoot().node(PREFERENCES_NAME)
private val _themeState = MutableStateFlow(theme)
val themeState: StateFlow<Themes> = _themeState
val themeState: StateFlow<Theme> = _themeState
private val _commitsLimitEnabledFlow = MutableStateFlow(commitsLimitEnabled)
val commitsLimitEnabledFlow: StateFlow<Boolean> = _commitsLimitEnabledFlow
@ -49,6 +49,9 @@ class AppPreferences @Inject constructor() {
private val _customThemeFlow = MutableStateFlow<ColorsScheme?>(null)
val customThemeFlow: StateFlow<ColorsScheme?> = _customThemeFlow
private val _scaleUiFlow = MutableStateFlow(scaleUi)
val scaleUiFlow: StateFlow<Float> = _scaleUiFlow
var latestTabsOpened: String
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
set(value) {
@ -61,14 +64,14 @@ class AppPreferences @Inject constructor() {
preferences.put(PREF_LAST_OPENED_REPOSITORIES_PATH, value)
}
var theme: Themes
var theme: Theme
get() {
val lastTheme = preferences.get(PREF_THEME, Themes.DARK.toString())
val lastTheme = preferences.get(PREF_THEME, Theme.DARK.toString())
return try {
Themes.valueOf(lastTheme)
Theme.valueOf(lastTheme)
} catch (ex: Exception) {
ex.printStackTrace()
Themes.DARK
Theme.DARK
}
}
set(value) {
@ -85,6 +88,15 @@ class AppPreferences @Inject constructor() {
_commitsLimitEnabledFlow.value = value
}
var scaleUi: Float
get() {
return preferences.getFloat(PREF_UI_SCALE, DEFAULT_UI_SCALE)
}
set(value) {
preferences.putFloat(PREF_UI_SCALE, value)
_scaleUiFlow.value = value
}
/**
* Property that decides if the merge should fast-forward when possible
*/

View File

@ -13,15 +13,15 @@ private var appTheme: ColorsScheme = defaultAppTheme
@Composable
fun AppTheme(
selectedTheme: Themes = Themes.DARK,
selectedTheme: Theme = Theme.DARK,
customTheme: ColorsScheme?,
content: @Composable() () -> Unit
) {
val theme = when (selectedTheme) {
Themes.LIGHT -> lightTheme
Themes.DARK -> darkBlueTheme
Themes.DARK_GRAY -> darkGrayTheme
Themes.CUSTOM -> customTheme ?: defaultAppTheme
Theme.LIGHT -> lightTheme
Theme.DARK -> darkBlueTheme
Theme.DARK_GRAY -> darkGrayTheme
Theme.CUSTOM -> customTheme ?: defaultAppTheme
}
appTheme = theme
@ -99,7 +99,7 @@ val Colors.dialogOverlay: Color
get() = appTheme.dialogOverlay
enum class Themes(val displayName: String) : DropDownOption {
enum class Theme(val displayName: String) : DropDownOption {
LIGHT("Light"),
DARK("Dark"),
DARK_GRAY("Dark gray"),
@ -109,9 +109,9 @@ enum class Themes(val displayName: String) : DropDownOption {
get() = displayName
}
val themesList = listOf(
Themes.LIGHT,
Themes.DARK,
Themes.DARK_GRAY,
Themes.CUSTOM,
val themeLists = listOf(
Theme.LIGHT,
Theme.DARK,
Theme.DARK_GRAY,
Theme.CUSTOM,
)

View File

@ -1,5 +1,6 @@
package app.ui.dialogs
package app.ui.dialogs.settings
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
@ -10,35 +11,54 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.preferences.AppPreferences
import app.DropDownOption
import app.theme.*
import app.ui.components.AdjustableOutlinedTextField
import app.ui.components.ScrollableColumn
import app.ui.dialogs.MaterialDialog
import app.ui.openFileDialog
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalDensity
import app.extensions.handMouseClickable
import app.preferences.DEFAULT_UI_SCALE
import app.viewmodels.SettingsViewModel
enum class SettingsCategory(val displayName: String) {
UI("UI"),
GIT("Git"),
}
@Composable
fun SettingsDialog(
appPreferences: AppPreferences,
settingsViewModel: SettingsViewModel,
onDismiss: () -> Unit,
) {
val currentTheme by appPreferences.themeState.collectAsState()
val commitsLimitEnabled by appPreferences.commitsLimitEnabledFlow.collectAsState()
val ffMerge by appPreferences.ffMergeFlow.collectAsState()
var commitsLimit by remember { mutableStateOf(appPreferences.commitsLimit) }
LaunchedEffect(Unit) {
settingsViewModel.resetInfo()
}
val categories = remember {
listOf(
SettingsCategory.UI,
SettingsCategory.GIT,
)
}
var selectedCategory by remember { mutableStateOf(SettingsCategory.UI) }
MaterialDialog(
onCloseRequested = {
savePendingSettings(
appPreferences = appPreferences,
commitsLimit = commitsLimit,
)
settingsViewModel.savePendingChanges()
onDismiss()
}
) {
Column(modifier = Modifier.width(720.dp)) {
Column(modifier = Modifier.height(720.dp)) {
Text(
text = "Settings",
color = MaterialTheme.colors.primaryTextColor,
@ -46,69 +66,37 @@ fun SettingsDialog(
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp, start = 8.dp)
)
SettingDropDown(
title = "Theme",
subtitle = "Select the UI theme between light and dark mode",
dropDownOptions = themesList,
currentOption = currentTheme,
onOptionSelected = { theme ->
appPreferences.theme = theme
}
)
if (currentTheme == Themes.CUSTOM) {
SettingButton(
title = "Custom theme",
subtitle = "Select a JSON file to load the custom theme",
buttonText = "Open file",
onClick = {
val filePath = openFileDialog()
if (filePath != null) {
appPreferences.saveCustomTheme(filePath)
}
Row(modifier = Modifier.weight(1f)) {
ScrollableColumn(
modifier = Modifier
.width(200.dp)
.fillMaxHeight()
) {
categories.forEach { category ->
Category(
category = category,
isSelected = category == selectedCategory,
onClick = { selectedCategory = category }
)
}
)
}
Column(modifier = Modifier.width(720.dp)) {
when (selectedCategory) {
SettingsCategory.UI -> UiSettings(settingsViewModel)
SettingsCategory.GIT -> GitSettings(settingsViewModel)
}
}
}
SettingToogle(
title = "Limit log commits",
subtitle = "Turning off this may affect the performance",
value = commitsLimitEnabled,
onValueChanged = { value ->
appPreferences.commitsLimitEnabled = value
}
)
SettingIntInput(
title = "Max commits",
subtitle = "Increasing this value may affect the performance",
value = commitsLimit,
enabled = commitsLimitEnabled,
onValueChanged = { value ->
commitsLimit = value
}
)
SettingToogle(
title = "Fast-forward merge",
subtitle = "Try to fast-forward merges when possible",
value = ffMerge,
onValueChanged = { value ->
appPreferences.ffMerge = value
}
)
TextButton(
modifier = Modifier
.padding(end = 8.dp)
.align(Alignment.End),
colors = textButtonColors(),
onClick = {
savePendingSettings(
appPreferences = appPreferences,
commitsLimit = commitsLimit,
)
settingsViewModel.savePendingChanges()
onDismiss()
}
@ -119,18 +107,126 @@ fun SettingsDialog(
)
}
}
}
}
fun savePendingSettings(
appPreferences: AppPreferences,
commitsLimit: Int,
) {
if (appPreferences.commitsLimit != commitsLimit) {
appPreferences.commitsLimit = commitsLimit
}
@Composable
fun GitSettings(settingsViewModel: SettingsViewModel) {
val commitsLimitEnabled by settingsViewModel.commitsLimitEnabledFlow.collectAsState()
val ffMerge by settingsViewModel.ffMergeFlow.collectAsState()
var commitsLimit by remember { mutableStateOf(settingsViewModel.commitsLimit) }
SettingToggle(
title = "Limit log commits",
subtitle = "Turning off this may affect the performance",
value = commitsLimitEnabled,
onValueChanged = { value ->
settingsViewModel.commitsLimitEnabled = value
}
)
SettingIntInput(
title = "Max commits",
subtitle = "Increasing this value may affect the performance",
value = commitsLimit,
enabled = commitsLimitEnabled,
onValueChanged = { value ->
commitsLimit = value
settingsViewModel.commitsLimit = value
}
)
SettingToggle(
title = "Fast-forward merge",
subtitle = "Try to fast-forward merges when possible",
value = ffMerge,
onValueChanged = { value ->
settingsViewModel.ffMerge = value
}
)
}
@Composable
fun UiSettings(settingsViewModel: SettingsViewModel) {
val currentTheme by settingsViewModel.themeState.collectAsState()
SettingDropDown(
title = "Theme",
subtitle = "Select the UI theme between light and dark mode",
dropDownOptions = themeLists,
currentOption = currentTheme,
onOptionSelected = { theme ->
settingsViewModel.theme = theme
}
)
if (currentTheme == Theme.CUSTOM) {
SettingButton(
title = "Custom theme",
subtitle = "Select a JSON file to load the custom theme",
buttonText = "Open file",
onClick = {
val filePath = openFileDialog()
if (filePath != null) {
settingsViewModel.saveCustomTheme(filePath)
}
}
)
}
val density = LocalDensity.current.density
var scaleValue by remember {
val savedScaleUi = settingsViewModel.scaleUi
val scaleUi = if (savedScaleUi == DEFAULT_UI_SCALE) {
density
} else {
savedScaleUi
} * 100
mutableStateOf(scaleUi)
}
SettingSlider(
title = "Scale",
subtitle = "Adapt the size the UI to your preferred scale",
value = scaleValue,
onValueChanged = { newValue ->
scaleValue = newValue
},
onValueChangeFinished = {
settingsViewModel.scaleUi = scaleValue / 100
},
steps = 5,
minValue = 100f,
maxValue = 300f,
)
}
@Composable
fun Category(
category: SettingsCategory,
isSelected: Boolean,
onClick: () -> Unit,
) {
val backgroundColor = if (isSelected)
MaterialTheme.colors.backgroundSelected
else
MaterialTheme.colors.background
Text(
text = category.displayName,
modifier = Modifier
.fillMaxWidth()
.background(color = backgroundColor)
.handMouseClickable(onClick)
.padding(8.dp),
style = MaterialTheme.typography.body1,
)
}
@Composable
fun <T : DropDownOption> SettingDropDown(
title: String,
@ -203,7 +299,7 @@ fun SettingButton(
}
@Composable
fun SettingToogle(
fun SettingToggle(
title: String,
subtitle: String,
value: Boolean,
@ -221,6 +317,46 @@ fun SettingToogle(
}
}
@Composable
fun SettingSlider(
title: String,
subtitle: String,
value: Float,
minValue: Float,
maxValue: Float,
steps: Int,
onValueChanged: (Float) -> Unit,
onValueChangeFinished: () -> Unit,
) {
Row(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
FieldTitles(title, subtitle)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "$minValue%",
style = MaterialTheme.typography.caption,
)
Slider(
value = value,
onValueChange = onValueChanged,
onValueChangeFinished = onValueChangeFinished,
steps = steps,
valueRange = minValue..maxValue,
modifier = Modifier.width(200.dp)
)
Text(
text = "$maxValue%",
style = MaterialTheme.typography.caption,
)
}
}
@Composable
fun SettingIntInput(
title: String,
@ -303,4 +439,13 @@ private fun isValidInt(value: String): Boolean {
} catch (ex: Exception) {
false
}
}
private fun isValidFloat(value: String): Boolean {
return try {
value.toFloat()
true
} catch (ex: Exception) {
false
}
}

View File

@ -192,10 +192,11 @@ fun Log(
}
)
val density = LocalDensity.current.density
DividerLog(
modifier = Modifier.draggable(
rememberDraggableState {
weightMod.value += it
weightMod.value += it * density
}, Orientation.Horizontal
),
graphWidth = graphWidth,

View File

@ -0,0 +1,60 @@
package app.viewmodels
import app.preferences.AppPreferences
import app.theme.Theme
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SettingsViewModel @Inject constructor(
val appPreferences: AppPreferences,
) {
// Temporary values to detect changed variables
var commitsLimit: Int = -1
val themeState = appPreferences.themeState
val customThemeFlow = appPreferences.customThemeFlow
val ffMergeFlow = appPreferences.ffMergeFlow
val commitsLimitEnabledFlow = appPreferences.commitsLimitEnabledFlow
var scaleUi: Float
get() = appPreferences.scaleUi
set(value) {
appPreferences.scaleUi = value
}
var commitsLimitEnabled: Boolean
get() = appPreferences.commitsLimitEnabled
set(value) {
appPreferences.commitsLimitEnabled = value
}
var ffMerge: Boolean
get() = appPreferences.ffMerge
set(value) {
appPreferences.ffMerge = value
}
var theme: Theme
get() = appPreferences.theme
set(value) {
appPreferences.theme = value
}
fun saveCustomTheme(filePath: String) {
appPreferences.saveCustomTheme(filePath)
}
fun resetInfo() {
commitsLimit = appPreferences.commitsLimit
}
fun savePendingChanges() {
val commitsLimit = this.commitsLimit
if (appPreferences.commitsLimit != commitsLimit) {
appPreferences.commitsLimit = commitsLimit
}
}
}