Make it pretty
This commit is contained in:
parent
5cdcc9a490
commit
923835203a
8 changed files with 144 additions and 2 deletions
|
|
@ -29,6 +29,7 @@ kotlin {
|
||||||
implementation(libs.cardiologist)
|
implementation(libs.cardiologist)
|
||||||
implementation(libs.kotlin.logging.jvm)
|
implementation(libs.kotlin.logging.jvm)
|
||||||
implementation(libs.logback.classic)
|
implementation(libs.logback.classic)
|
||||||
|
implementation(libs.material.kolor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,28 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.rukira.wowbackup.config.ConfigManager
|
||||||
import com.rukira.wowbackup.ui.Screen
|
import com.rukira.wowbackup.ui.Screen
|
||||||
import com.rukira.wowbackup.ui.config.ConfigScreen
|
import com.rukira.wowbackup.ui.config.ConfigScreen
|
||||||
import com.rukira.wowbackup.ui.config.ConfigViewModel
|
import com.rukira.wowbackup.ui.config.ConfigViewModel
|
||||||
import com.rukira.wowbackup.ui.status.StatusScreen
|
import com.rukira.wowbackup.ui.status.StatusScreen
|
||||||
import com.rukira.wowbackup.ui.status.StatusViewModel
|
import com.rukira.wowbackup.ui.status.StatusViewModel
|
||||||
|
import com.rukira.wowbackup.ui.theme.WoWBackupTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun App(
|
fun App(
|
||||||
currentScreen: Screen,
|
currentScreen: Screen,
|
||||||
onNavigate: (Screen) -> Unit,
|
onNavigate: (Screen) -> Unit,
|
||||||
) {
|
) {
|
||||||
MaterialTheme {
|
val config by ConfigManager.config.collectAsState()
|
||||||
|
|
||||||
|
WoWBackupTheme(themeMode = config.themeMode, accentColor = config.accentColor) {
|
||||||
Surface(modifier = Modifier.fillMaxSize()) {
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
when (currentScreen) {
|
when (currentScreen) {
|
||||||
Screen.STATUS -> {
|
Screen.STATUS -> {
|
||||||
|
|
@ -48,6 +54,7 @@ fun App(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlaceholderScreen(title: String, subtitle: String) {
|
private fun PlaceholderScreen(title: String, subtitle: String) {
|
||||||
Column(
|
Column(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,26 @@
|
||||||
package com.rukira.wowbackup.config
|
package com.rukira.wowbackup.config
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import kotlinx.datetime.LocalTime
|
import kotlinx.datetime.LocalTime
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class ThemeMode { SYSTEM, LIGHT, DARK }
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class AccentColor(val displayName: String, private val colorValue: Long) {
|
||||||
|
PURPLE("Purple", 0xFF7C4DFF),
|
||||||
|
BLUE("Blue", 0xFF448AFF),
|
||||||
|
TEAL("Teal", 0xFF1DE9B6),
|
||||||
|
GREEN("Green", 0xFF69F0AE),
|
||||||
|
ORANGE("Orange", 0xFFFF9100),
|
||||||
|
RED("Red", 0xFFFF5252),
|
||||||
|
PINK("Pink", 0xFFFF4081),
|
||||||
|
INDIGO("Indigo", 0xFF536DFE);
|
||||||
|
|
||||||
|
val seedColor: Color get() = Color(colorValue)
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AppConfig(
|
data class AppConfig(
|
||||||
val wowInstallPath: String? = null,
|
val wowInstallPath: String? = null,
|
||||||
|
|
@ -15,6 +33,8 @@ data class AppConfig(
|
||||||
val compressionEnabled: Boolean = false,
|
val compressionEnabled: Boolean = false,
|
||||||
val notificationsEnabled: Boolean = true,
|
val notificationsEnabled: Boolean = true,
|
||||||
val runAtStartup: Boolean = false,
|
val runAtStartup: Boolean = false,
|
||||||
|
val themeMode: ThemeMode = ThemeMode.SYSTEM,
|
||||||
|
val accentColor: AccentColor = AccentColor.PURPLE,
|
||||||
) {
|
) {
|
||||||
val isConfigured: Boolean
|
val isConfigured: Boolean
|
||||||
get() = !wowInstallPath.isNullOrBlank() && !backupPath.isNullOrBlank()
|
get() = !wowInstallPath.isNullOrBlank() && !backupPath.isNullOrBlank()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package com.rukira.wowbackup.ui.config
|
package com.rukira.wowbackup.ui.config
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -8,8 +12,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
|
|
@ -17,6 +23,9 @@ import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|
@ -26,7 +35,10 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.rukira.wowbackup.config.AccentColor
|
||||||
|
import com.rukira.wowbackup.config.ThemeMode
|
||||||
import com.rukira.wowbackup.ui.components.ConfirmationDialog
|
import com.rukira.wowbackup.ui.components.ConfirmationDialog
|
||||||
import com.rukira.wowbackup.ui.components.TimePicker
|
import com.rukira.wowbackup.ui.components.TimePicker
|
||||||
import com.rukira.wowbackup.ui.components.pickFolder
|
import com.rukira.wowbackup.ui.components.pickFolder
|
||||||
|
|
@ -185,6 +197,64 @@ fun ConfigScreen(
|
||||||
description = "Launch WoW Backup automatically when you log in.",
|
description = "Launch WoW Backup automatically when you log in.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// === Appearance ===
|
||||||
|
SectionHeader("Appearance")
|
||||||
|
|
||||||
|
Text("Theme", style = MaterialTheme.typography.bodyMedium)
|
||||||
|
SingleChoiceSegmentedButtonRow {
|
||||||
|
ThemeMode.entries.forEachIndexed { index, mode ->
|
||||||
|
SegmentedButton(
|
||||||
|
selected = config.themeMode == mode,
|
||||||
|
onClick = { viewModel.updateThemeMode(mode) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(index, ThemeMode.entries.size),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
when (mode) {
|
||||||
|
ThemeMode.SYSTEM -> "System"
|
||||||
|
ThemeMode.LIGHT -> "Light"
|
||||||
|
ThemeMode.DARK -> "Dark"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Accent color", style = MaterialTheme.typography.bodyMedium)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
AccentColor.entries.forEach { color ->
|
||||||
|
val isSelected = config.accentColor == color
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color.seedColor, CircleShape)
|
||||||
|
.then(
|
||||||
|
if (isSelected) {
|
||||||
|
Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.clickable { viewModel.updateAccentColor(color) },
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (isSelected) {
|
||||||
|
Text(
|
||||||
|
"\u2713",
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Theme changes apply immediately.",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
|
||||||
// === Footer ===
|
// === Footer ===
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.rukira.wowbackup.ui.config
|
package com.rukira.wowbackup.ui.config
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.rukira.wowbackup.config.AccentColor
|
||||||
import com.rukira.wowbackup.config.AppConfig
|
import com.rukira.wowbackup.config.AppConfig
|
||||||
import com.rukira.wowbackup.config.ConfigManager
|
import com.rukira.wowbackup.config.ConfigManager
|
||||||
|
import com.rukira.wowbackup.config.ThemeMode
|
||||||
import com.rukira.wowbackup.platform.WoWLocations
|
import com.rukira.wowbackup.platform.WoWLocations
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -134,6 +136,20 @@ class ConfigViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateThemeMode(mode: ThemeMode) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(config = it.config.copy(themeMode = mode))
|
||||||
|
}
|
||||||
|
ConfigManager.save(_state.value.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAccentColor(color: AccentColor) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(config = it.config.copy(accentColor = color))
|
||||||
|
}
|
||||||
|
ConfigManager.save(_state.value.config)
|
||||||
|
}
|
||||||
|
|
||||||
fun detectWoWLocation() {
|
fun detectWoWLocation() {
|
||||||
val detected = WoWLocations.findWoWInstall()
|
val detected = WoWLocations.findWoWInstall()
|
||||||
if (detected != null) {
|
if (detected != null) {
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ fun StatusScreen(
|
||||||
},
|
},
|
||||||
enabled = uiState.backupPath != null,
|
enabled = uiState.backupPath != null,
|
||||||
) {
|
) {
|
||||||
Text("Open Folder")
|
Text("Open Backups Folder")
|
||||||
}
|
}
|
||||||
OutlinedButton(onClick = onNavigateToRestore, enabled = false) {
|
OutlinedButton(onClick = onNavigateToRestore, enabled = false) {
|
||||||
Text("Restore (Coming Soon)")
|
Text("Restore (Coming Soon)")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.rukira.wowbackup.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.materialkolor.DynamicMaterialTheme
|
||||||
|
import com.rukira.wowbackup.config.AccentColor
|
||||||
|
import com.rukira.wowbackup.config.ThemeMode
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WoWBackupTheme(
|
||||||
|
themeMode: ThemeMode = ThemeMode.SYSTEM,
|
||||||
|
accentColor: AccentColor = AccentColor.PURPLE,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val useDarkTheme = when (themeMode) {
|
||||||
|
ThemeMode.SYSTEM -> isSystemInDarkTheme()
|
||||||
|
ThemeMode.LIGHT -> false
|
||||||
|
ThemeMode.DARK -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicMaterialTheme(
|
||||||
|
seedColor = accentColor.seedColor,
|
||||||
|
useDarkTheme = useDarkTheme,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ kotlinx-serialization = "1.10.0"
|
||||||
cardiologist = "0.8.0"
|
cardiologist = "0.8.0"
|
||||||
kotlin-logging = "7.0.3"
|
kotlin-logging = "7.0.3"
|
||||||
logback = "1.5.18"
|
logback = "1.5.18"
|
||||||
|
materialKolor = "2.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||||
|
|
@ -18,6 +19,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
|
||||||
cardiologist = { module = "io.github.kevincianfarini.cardiologist:cardiologist", version.ref = "cardiologist" }
|
cardiologist = { module = "io.github.kevincianfarini.cardiologist:cardiologist", version.ref = "cardiologist" }
|
||||||
kotlin-logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlin-logging" }
|
kotlin-logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlin-logging" }
|
||||||
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
|
material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue