Added option to load custom themes.
Fixes https://github.com/JetpackDuba/Gitnuro/issues/3
This commit is contained in:
parent
15827d119a
commit
43330eb3c4
@ -53,11 +53,14 @@ class App {
|
||||
val windowPlacement = appPreferences.windowPlacement.toWindowPlacement
|
||||
|
||||
appStateManager.loadRepositoriesTabs()
|
||||
appPreferences.loadCustomTheme()
|
||||
loadTabs()
|
||||
|
||||
application {
|
||||
var isOpen by remember { mutableStateOf(true) }
|
||||
val theme by appPreferences.themeState.collectAsState()
|
||||
val customTheme by appPreferences.customThemeFlow.collectAsState()
|
||||
|
||||
val windowState = rememberWindowState(
|
||||
placement = windowPlacement,
|
||||
size = DpSize(1280.dp, 720.dp)
|
||||
@ -77,7 +80,10 @@ class App {
|
||||
) {
|
||||
var showSettingsDialog by remember { mutableStateOf(false) }
|
||||
|
||||
AppTheme(selectedTheme = theme) {
|
||||
AppTheme(
|
||||
selectedTheme = theme,
|
||||
customTheme = customTheme,
|
||||
) {
|
||||
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
|
||||
AppTabs(
|
||||
onOpenSettings = {
|
||||
|
@ -1,12 +1,18 @@
|
||||
package app.preferences
|
||||
|
||||
import app.extensions.defaultWindowPlacement
|
||||
import app.theme.ColorsScheme
|
||||
import app.theme.Themes
|
||||
import app.theme.darkBlueTheme
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
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"
|
||||
|
||||
@ -16,6 +22,7 @@ private const val PREF_THEME = "theme"
|
||||
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 DEFAULT_COMMITS_LIMIT = 1000
|
||||
private const val DEFAULT_COMMITS_LIMIT_ENABLED = true
|
||||
@ -33,6 +40,9 @@ class AppPreferences @Inject constructor() {
|
||||
private val _commitsLimitFlow = MutableStateFlow(commitsLimit)
|
||||
val commitsLimitFlow: StateFlow<Int> = _commitsLimitFlow
|
||||
|
||||
private val _customThemeFlow = MutableStateFlow<ColorsScheme?>(null)
|
||||
val customThemeFlow: StateFlow<ColorsScheme?> = _customThemeFlow
|
||||
|
||||
var latestTabsOpened: String
|
||||
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
|
||||
set(value) {
|
||||
@ -87,4 +97,25 @@ class AppPreferences @Inject constructor() {
|
||||
set(placement) {
|
||||
preferences.putInt(PREF_WINDOW_PLACEMENT, placement.value)
|
||||
}
|
||||
|
||||
fun saveCustomTheme(filePath: String) {
|
||||
try {
|
||||
val file = File(filePath)
|
||||
val content = file.readText()
|
||||
|
||||
Json.decodeFromString<ColorsScheme>(content) // Load to see if it's valid (it will crash if not)
|
||||
|
||||
preferences.put(PREF_CUSTOM_THEME, content)
|
||||
loadCustomTheme()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadCustomTheme() {
|
||||
val themeJson = preferences.get(PREF_CUSTOM_THEME, null)
|
||||
if (themeJson != null) {
|
||||
_customThemeFlow.value = Json.decodeFromString<ColorsScheme>(themeJson)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,19 @@
|
||||
@file:UseSerializers(ColorAsStringSerializer::class)
|
||||
package app.theme
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ColorsScheme(
|
||||
val primary: Color,
|
||||
val primaryVariant: Color,
|
||||
@ -46,3 +57,18 @@ data class ColorsScheme(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object ColorAsStringSerializer : KSerializer<Color> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Color) {
|
||||
encoder.encodeString("")
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Color {
|
||||
val value = decoder.decodeString()
|
||||
val longValue = value.toLong(radix = 16)
|
||||
|
||||
return Color(longValue)
|
||||
}
|
||||
}
|
@ -8,14 +8,20 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import app.DropDownOption
|
||||
|
||||
private var appTheme: ColorsScheme = darkBlueTheme
|
||||
private val defaultAppTheme: ColorsScheme = darkBlueTheme
|
||||
private var appTheme: ColorsScheme = defaultAppTheme
|
||||
|
||||
@Composable
|
||||
fun AppTheme(selectedTheme: Themes = Themes.DARK, content: @Composable() () -> Unit) {
|
||||
fun AppTheme(
|
||||
selectedTheme: Themes = Themes.DARK,
|
||||
customTheme: ColorsScheme?,
|
||||
content: @Composable() () -> Unit
|
||||
) {
|
||||
val theme = when (selectedTheme) {
|
||||
Themes.LIGHT -> lightTheme
|
||||
Themes.DARK -> darkBlueTheme
|
||||
Themes.DARK_GRAY -> darkGrayTheme
|
||||
Themes.CUSTOM -> customTheme ?: defaultAppTheme
|
||||
}
|
||||
|
||||
appTheme = theme
|
||||
@ -96,7 +102,8 @@ val Colors.dialogOverlay: Color
|
||||
enum class Themes(val displayName: String) : DropDownOption {
|
||||
LIGHT("Light"),
|
||||
DARK("Dark"),
|
||||
DARK_GRAY("Dark gray");
|
||||
DARK_GRAY("Dark gray"),
|
||||
CUSTOM("Custom");
|
||||
|
||||
override val optionName: String
|
||||
get() = displayName
|
||||
@ -106,4 +113,5 @@ val themesList = listOf(
|
||||
Themes.LIGHT,
|
||||
Themes.DARK,
|
||||
Themes.DARK_GRAY,
|
||||
Themes.CUSTOM,
|
||||
)
|
@ -3,16 +3,13 @@
|
||||
package app.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.input.pointer.PointerIcon
|
||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -25,7 +22,6 @@ import app.ui.dialogs.StashWithMessageDialog
|
||||
import app.ui.log.Log
|
||||
import app.viewmodels.BlameState
|
||||
import app.viewmodels.TabViewModel
|
||||
import openRepositoryDialog
|
||||
import org.eclipse.jgit.lib.RepositoryState
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
|
||||
|
@ -1,73 +1,52 @@
|
||||
import app.extensions.runCommand
|
||||
package app.ui
|
||||
|
||||
import app.viewmodels.TabViewModel
|
||||
import java.io.File
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.UIManager
|
||||
|
||||
|
||||
fun openDirectoryDialog(): String? {
|
||||
val os = System.getProperty("os.name")
|
||||
var dirToOpen: String? = null
|
||||
|
||||
if (os.lowercase() == "linux") {
|
||||
val checkZenityInstalled = runCommand("which zenity 2>/dev/null")
|
||||
val isZenityInstalled = !checkZenityInstalled.isNullOrEmpty()
|
||||
|
||||
if (isZenityInstalled) {
|
||||
val openDirectory = runCommand(
|
||||
"zenity --file-selection --title=Open --directory"
|
||||
)?.replace("\n", "")
|
||||
|
||||
if (!openDirectory.isNullOrEmpty())
|
||||
dirToOpen = openDirectory
|
||||
} else
|
||||
dirToOpen = openJvmDialog("", true)
|
||||
} else {
|
||||
dirToOpen = openJvmDialog("", false)
|
||||
fun openDirectoryDialog(basePath: String? = null): String? {
|
||||
return openPickerDialog(
|
||||
pickerType = PickerType.DIRECTORIES,
|
||||
basePath = basePath,
|
||||
)
|
||||
}
|
||||
|
||||
return dirToOpen
|
||||
fun openFileDialog(basePath: String? = null): String? {
|
||||
return openPickerDialog(
|
||||
pickerType = PickerType.FILES,
|
||||
basePath = basePath,
|
||||
)
|
||||
}
|
||||
|
||||
fun openRepositoryDialog(tabViewModel: TabViewModel) {
|
||||
val os = System.getProperty("os.name")
|
||||
val appStateManager = tabViewModel.appStateManager
|
||||
val latestDirectoryOpened = appStateManager.latestOpenedRepositoryPath
|
||||
var dirToOpen: String? = null
|
||||
|
||||
if (os.lowercase() == "linux") {
|
||||
val checkZenityInstalled = runCommand("which zenity 2>/dev/null")
|
||||
val isZenityInstalled = !checkZenityInstalled.isNullOrEmpty()
|
||||
|
||||
if (isZenityInstalled) {
|
||||
val openDirectory = runCommand(
|
||||
"zenity --file-selection --title=Open --directory --filename=\"$latestDirectoryOpened\""
|
||||
)?.replace("\n", "")
|
||||
|
||||
if (!openDirectory.isNullOrEmpty())
|
||||
dirToOpen = openDirectory
|
||||
} else
|
||||
dirToOpen = openJvmDialog(latestDirectoryOpened, true)
|
||||
} else {
|
||||
dirToOpen = openJvmDialog(latestDirectoryOpened, false)
|
||||
}
|
||||
|
||||
val dirToOpen = openDirectoryDialog(latestDirectoryOpened)
|
||||
if (dirToOpen != null)
|
||||
tabViewModel.openRepository(dirToOpen)
|
||||
}
|
||||
|
||||
private fun openJvmDialog(
|
||||
latestDirectoryOpened: String,
|
||||
isLinux: Boolean,
|
||||
private fun openPickerDialog(
|
||||
pickerType: PickerType,
|
||||
basePath: String?,
|
||||
): String? {
|
||||
|
||||
val os = System.getProperty("os.name")
|
||||
val isLinux = os.lowercase() == "linux"
|
||||
|
||||
if (!isLinux) {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
|
||||
}
|
||||
|
||||
val fileChooser = if (latestDirectoryOpened.isEmpty())
|
||||
val fileChooser = if (basePath.isNullOrEmpty())
|
||||
JFileChooser()
|
||||
else
|
||||
JFileChooser(latestDirectoryOpened)
|
||||
JFileChooser(basePath)
|
||||
|
||||
fileChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
|
||||
fileChooser.fileSelectionMode = pickerType.value
|
||||
fileChooser.showOpenDialog(null)
|
||||
|
||||
return if (fileChooser.selectedFile != null)
|
||||
@ -75,3 +54,9 @@ private fun openJvmDialog(
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
enum class PickerType(val value: Int) {
|
||||
FILES(JFileChooser.FILES_ONLY),
|
||||
DIRECTORIES(JFileChooser.DIRECTORIES_ONLY),
|
||||
FILES_AND_DIRECTORIES(JFileChooser.FILES_AND_DIRECTORIES);
|
||||
}
|
@ -31,8 +31,6 @@ import app.ui.dialogs.AppInfoDialog
|
||||
import app.ui.dialogs.CloneDialog
|
||||
import app.updates.Update
|
||||
import app.viewmodels.TabViewModel
|
||||
import openDirectoryDialog
|
||||
import openRepositoryDialog
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
|
@ -23,7 +23,7 @@ import app.theme.primaryTextColor
|
||||
import app.theme.textButtonColors
|
||||
import app.ui.components.PrimaryButton
|
||||
import app.viewmodels.CloneViewModel
|
||||
import openDirectoryDialog
|
||||
import app.ui.openDirectoryDialog
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
|
@ -12,10 +12,8 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.preferences.AppPreferences
|
||||
import app.DropDownOption
|
||||
import app.theme.outlinedTextFieldColors
|
||||
import app.theme.primaryTextColor
|
||||
import app.theme.textButtonColors
|
||||
import app.theme.themesList
|
||||
import app.theme.*
|
||||
import app.ui.openFileDialog
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -56,6 +54,21 @@ fun SettingsDialog(
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SettingToogle(
|
||||
title = "Limit log commits",
|
||||
subtitle = "Turning off this may affect the performance",
|
||||
@ -162,6 +175,44 @@ fun <T : DropDownOption> SettingDropDown(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingButton(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
buttonText: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.Center) {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colors.primaryTextColor,
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = MaterialTheme.colors.primaryTextColor,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
OutlinedButton(onClick = onClick) {
|
||||
Text(
|
||||
text = buttonText,
|
||||
color = MaterialTheme.colors.primaryTextColor,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingToogle(
|
||||
title: String,
|
||||
|
Loading…
Reference in New Issue
Block a user