Make it pretty

This commit is contained in:
Rukira 2026-03-04 14:40:04 +00:00
parent 5cdcc9a490
commit 923835203a
8 changed files with 144 additions and 2 deletions

View file

@ -8,22 +8,28 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.rukira.wowbackup.config.ConfigManager
import com.rukira.wowbackup.ui.Screen
import com.rukira.wowbackup.ui.config.ConfigScreen
import com.rukira.wowbackup.ui.config.ConfigViewModel
import com.rukira.wowbackup.ui.status.StatusScreen
import com.rukira.wowbackup.ui.status.StatusViewModel
import com.rukira.wowbackup.ui.theme.WoWBackupTheme
@Composable
fun App(
currentScreen: Screen,
onNavigate: (Screen) -> Unit,
) {
MaterialTheme {
val config by ConfigManager.config.collectAsState()
WoWBackupTheme(themeMode = config.themeMode, accentColor = config.accentColor) {
Surface(modifier = Modifier.fillMaxSize()) {
when (currentScreen) {
Screen.STATUS -> {
@ -48,6 +54,7 @@ fun App(
}
}
@Composable
private fun PlaceholderScreen(title: String, subtitle: String) {
Column(

View file

@ -1,8 +1,26 @@
package com.rukira.wowbackup.config
import androidx.compose.ui.graphics.Color
import kotlinx.datetime.LocalTime
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
data class AppConfig(
val wowInstallPath: String? = null,
@ -15,6 +33,8 @@ data class AppConfig(
val compressionEnabled: Boolean = false,
val notificationsEnabled: Boolean = true,
val runAtStartup: Boolean = false,
val themeMode: ThemeMode = ThemeMode.SYSTEM,
val accentColor: AccentColor = AccentColor.PURPLE,
) {
val isConfigured: Boolean
get() = !wowInstallPath.isNullOrBlank() && !backupPath.isNullOrBlank()

View file

@ -1,6 +1,10 @@
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
@ -17,6 +23,9 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
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.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -26,7 +35,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.TimePicker
import com.rukira.wowbackup.ui.components.pickFolder
@ -185,6 +197,64 @@ fun ConfigScreen(
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 ===
Spacer(Modifier.height(8.dp))
HorizontalDivider()

View file

@ -1,8 +1,10 @@
package com.rukira.wowbackup.ui.config
import androidx.lifecycle.ViewModel
import com.rukira.wowbackup.config.AccentColor
import com.rukira.wowbackup.config.AppConfig
import com.rukira.wowbackup.config.ConfigManager
import com.rukira.wowbackup.config.ThemeMode
import com.rukira.wowbackup.platform.WoWLocations
import io.github.oshai.kotlinlogging.KotlinLogging
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() {
val detected = WoWLocations.findWoWInstall()
if (detected != null) {

View file

@ -205,7 +205,7 @@ fun StatusScreen(
},
enabled = uiState.backupPath != null,
) {
Text("Open Folder")
Text("Open Backups Folder")
}
OutlinedButton(onClick = onNavigateToRestore, enabled = false) {
Text("Restore (Coming Soon)")

View file

@ -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,
)
}