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

View File

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

View File

@ -13,15 +13,15 @@ private var appTheme: ColorsScheme = defaultAppTheme
@Composable @Composable
fun AppTheme( fun AppTheme(
selectedTheme: Themes = Themes.DARK, selectedTheme: Theme = Theme.DARK,
customTheme: ColorsScheme?, customTheme: ColorsScheme?,
content: @Composable() () -> Unit content: @Composable() () -> Unit
) { ) {
val theme = when (selectedTheme) { val theme = when (selectedTheme) {
Themes.LIGHT -> lightTheme Theme.LIGHT -> lightTheme
Themes.DARK -> darkBlueTheme Theme.DARK -> darkBlueTheme
Themes.DARK_GRAY -> darkGrayTheme Theme.DARK_GRAY -> darkGrayTheme
Themes.CUSTOM -> customTheme ?: defaultAppTheme Theme.CUSTOM -> customTheme ?: defaultAppTheme
} }
appTheme = theme appTheme = theme
@ -99,7 +99,7 @@ val Colors.dialogOverlay: Color
get() = appTheme.dialogOverlay get() = appTheme.dialogOverlay
enum class Themes(val displayName: String) : DropDownOption { enum class Theme(val displayName: String) : DropDownOption {
LIGHT("Light"), LIGHT("Light"),
DARK("Dark"), DARK("Dark"),
DARK_GRAY("Dark gray"), DARK_GRAY("Dark gray"),
@ -109,9 +109,9 @@ enum class Themes(val displayName: String) : DropDownOption {
get() = displayName get() = displayName
} }
val themesList = listOf( val themeLists = listOf(
Themes.LIGHT, Theme.LIGHT,
Themes.DARK, Theme.DARK,
Themes.DARK_GRAY, Theme.DARK_GRAY,
Themes.CUSTOM, 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.layout.*
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* 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.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.preferences.AppPreferences
import app.DropDownOption import app.DropDownOption
import app.theme.* import app.theme.*
import app.ui.components.AdjustableOutlinedTextField import app.ui.components.AdjustableOutlinedTextField
import app.ui.components.ScrollableColumn
import app.ui.dialogs.MaterialDialog
import app.ui.openFileDialog import app.ui.openFileDialog
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch 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 @Composable
fun SettingsDialog( fun SettingsDialog(
appPreferences: AppPreferences, settingsViewModel: SettingsViewModel,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val currentTheme by appPreferences.themeState.collectAsState()
val commitsLimitEnabled by appPreferences.commitsLimitEnabledFlow.collectAsState() LaunchedEffect(Unit) {
val ffMerge by appPreferences.ffMergeFlow.collectAsState() settingsViewModel.resetInfo()
var commitsLimit by remember { mutableStateOf(appPreferences.commitsLimit) } }
val categories = remember {
listOf(
SettingsCategory.UI,
SettingsCategory.GIT,
)
}
var selectedCategory by remember { mutableStateOf(SettingsCategory.UI) }
MaterialDialog( MaterialDialog(
onCloseRequested = { onCloseRequested = {
savePendingSettings( settingsViewModel.savePendingChanges()
appPreferences = appPreferences,
commitsLimit = commitsLimit,
)
onDismiss() onDismiss()
} }
) { ) {
Column(modifier = Modifier.width(720.dp)) { Column(modifier = Modifier.height(720.dp)) {
Text( Text(
text = "Settings", text = "Settings",
color = MaterialTheme.colors.primaryTextColor, color = MaterialTheme.colors.primaryTextColor,
@ -46,69 +66,37 @@ fun SettingsDialog(
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp, start = 8.dp) modifier = Modifier.padding(top = 8.dp, bottom = 16.dp, start = 8.dp)
) )
SettingDropDown( Row(modifier = Modifier.weight(1f)) {
title = "Theme", ScrollableColumn(
subtitle = "Select the UI theme between light and dark mode", modifier = Modifier
dropDownOptions = themesList, .width(200.dp)
currentOption = currentTheme, .fillMaxHeight()
onOptionSelected = { theme -> ) {
appPreferences.theme = theme categories.forEach { category ->
} Category(
) category = category,
isSelected = category == selectedCategory,
if (currentTheme == Themes.CUSTOM) { onClick = { selectedCategory = category }
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)
}
} }
) }
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( TextButton(
modifier = Modifier modifier = Modifier
.padding(end = 8.dp) .padding(end = 8.dp)
.align(Alignment.End), .align(Alignment.End),
colors = textButtonColors(), colors = textButtonColors(),
onClick = { onClick = {
savePendingSettings( settingsViewModel.savePendingChanges()
appPreferences = appPreferences,
commitsLimit = commitsLimit,
)
onDismiss() onDismiss()
} }
@ -119,18 +107,126 @@ fun SettingsDialog(
) )
} }
} }
} }
} }
fun savePendingSettings( @Composable
appPreferences: AppPreferences, fun GitSettings(settingsViewModel: SettingsViewModel) {
commitsLimit: Int, val commitsLimitEnabled by settingsViewModel.commitsLimitEnabledFlow.collectAsState()
) { val ffMerge by settingsViewModel.ffMergeFlow.collectAsState()
if (appPreferences.commitsLimit != commitsLimit) { var commitsLimit by remember { mutableStateOf(settingsViewModel.commitsLimit) }
appPreferences.commitsLimit = 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 @Composable
fun <T : DropDownOption> SettingDropDown( fun <T : DropDownOption> SettingDropDown(
title: String, title: String,
@ -203,7 +299,7 @@ fun SettingButton(
} }
@Composable @Composable
fun SettingToogle( fun SettingToggle(
title: String, title: String,
subtitle: String, subtitle: String,
value: Boolean, 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 @Composable
fun SettingIntInput( fun SettingIntInput(
title: String, title: String,
@ -303,4 +439,13 @@ private fun isValidInt(value: String): Boolean {
} catch (ex: Exception) { } catch (ex: Exception) {
false 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( DividerLog(
modifier = Modifier.draggable( modifier = Modifier.draggable(
rememberDraggableState { rememberDraggableState {
weightMod.value += it weightMod.value += it * density
}, Orientation.Horizontal }, Orientation.Horizontal
), ),
graphWidth = graphWidth, 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
}
}
}