diff --git a/.gitea/issue_template/bug_report.yaml b/.gitea/issue_template/bug_report.yaml deleted file mode 100644 index de6325e..0000000 --- a/.gitea/issue_template/bug_report.yaml +++ /dev/null @@ -1,94 +0,0 @@ -name: Bug Report -about: Something not working right? Let us know so we can fix it! -title: "[Bug] " -labels: - - bug - -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to report this! The more detail you can give, the faster we can track it down. - Don't worry about being too technical — just describe what happened in your own words. - - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Tell us what went wrong. What did you see or experience? - placeholder: "e.g. The app froze after I clicked Save, and my backup never ran." - validations: - required: true - - - type: textarea - id: expected - attributes: - label: What did you expect to happen? - placeholder: "e.g. I expected my settings to save and the backup to start at the scheduled time." - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: | - Walk us through exactly what you did before the problem showed up. - Try to be specific — even small details can help! Number each step. - placeholder: | - 1. Opened WoW Backup - 2. Went to Settings - 3. Changed the backup time to 5:00 PM - 4. Clicked Save - 5. The app froze and stopped responding - validations: - required: true - - - type: dropdown - id: frequency - attributes: - label: How often does this happen? - options: - - Every time - - Sometimes - - It only happened once - validations: - required: true - - - type: dropdown - id: os - attributes: - label: Operating system - options: - - macOS - - Windows - validations: - required: true - - - type: input - id: app-version - attributes: - label: App version - description: You can find this in the app's title bar or About screen. - placeholder: "e.g. 1.0.0" - - - type: textarea - id: logs - attributes: - label: Log file - description: | - Logs help us understand what went wrong behind the scenes. Here's how to find them: - - **macOS:** Open Finder, press **Cmd+Shift+G**, and paste: `~/Library/Application Support/WoWBackup/logs` - **Windows:** Press **Win+R**, and paste: `%APPDATA%\WoWBackup\logs` - - You can also open this folder from the app: go to **Settings** and click the **Open Logs** button at the bottom. - - Attach the most recent `wowbackup.log` file by dragging it into this text box. - placeholder: Drag your log file here, or paste any relevant log lines. - - - type: textarea - id: extra - attributes: - label: Anything else? - description: Screenshots, screen recordings, or any other context that might help. diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 53c56a0..ea1e38a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -29,38 +29,10 @@ kotlin { implementation(libs.cardiologist) implementation(libs.kotlin.logging.jvm) implementation(libs.logback.classic) - implementation(libs.material.kolor) } } } -val appVersion = "0.1.0" - -val generatedSrcDir = layout.buildDirectory.dir("generated/src/jvmMain/kotlin") - -val generateBuildConfig by tasks.registering { - val outputDir = generatedSrcDir - val version = appVersion - outputs.dir(outputDir) - doLast { - val dir = outputDir.get().asFile.resolve("com/rukira/wowbackup") - dir.mkdirs() - dir.resolve("BuildConfig.kt").writeText( - """ - |package com.rukira.wowbackup - | - |object BuildConfig { - | const val VERSION = "$version" - |} - """.trimMargin() - ) - } -} - -kotlin.sourceSets.named("jvmMain") { - kotlin.srcDir(generateBuildConfig) -} - compose.desktop { application { mainClass = "com.rukira.wowbackup.MainKt" @@ -70,7 +42,7 @@ compose.desktop { nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "com.rukira.wowbackup" - packageVersion = appVersion + packageVersion = "1.0.0" } } } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/App.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/App.kt index f417ce0..e053ea8 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/App.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/App.kt @@ -8,28 +8,22 @@ 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, ) { - val config by ConfigManager.config.collectAsState() - - WoWBackupTheme(themeMode = config.themeMode, accentColor = config.accentColor) { + MaterialTheme { Surface(modifier = Modifier.fillMaxSize()) { when (currentScreen) { Screen.STATUS -> { @@ -54,7 +48,6 @@ fun App( } } - @Composable private fun PlaceholderScreen(title: String, subtitle: String) { Column( diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupHistory.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupHistory.kt index f03c20d..e140a2d 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupHistory.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupHistory.kt @@ -17,29 +17,12 @@ class BackupEntry( val sizeBytes: Long by lazy { sizeComputer() } } -data class BackupMetrics(val fileCount: Int, val sizeBytes: Long) - object BackupHistory { // Used internally for parsing — java.time formatter since kotlinx.datetime // doesn't have custom format patterns built in private val JAVA_TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") - /** - * Counts backups without parsing timestamps or sorting — just matches the naming pattern. - */ - fun countBackups(backupDir: File): Int { - if (!backupDir.exists() || !backupDir.isDirectory) return 0 - return backupDir.listFiles()?.count { file -> - val name = file.nameWithoutExtension - val isZip = file.extension.equals("zip", ignoreCase = true) && file.isFile - val isDir = file.isDirectory - if (!isZip && !isDir) return@count false - try { java.time.LocalDateTime.parse(name, JAVA_TIMESTAMP_FORMAT); true } - catch (_: DateTimeParseException) { false } - } ?: 0 - } - /** * Lists all existing backups in the backup directory, sorted newest first. */ @@ -54,18 +37,13 @@ object BackupHistory { } /** - * Computes file count and size in a single pass over the backup tree. + * Returns the file count for a backup entry without computing its full size. */ - fun computeMetrics(entry: BackupEntry): BackupMetrics { + fun fileCount(entry: BackupEntry): Int { return if (entry.isCompressed) { - java.util.zip.ZipFile(entry.path).use { zip -> - BackupMetrics(zip.size(), entry.path.length()) - } + java.util.zip.ZipFile(entry.path).use { it.size() } } else { - var count = 0 - var size = 0L - entry.path.walkTopDown().filter { it.isFile }.forEach { count++; size += it.length() } - BackupMetrics(count, size) + entry.path.walkTopDown().count { it.isFile } } } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupScheduler.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupScheduler.kt index 552f1f5..f8f7e85 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupScheduler.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupScheduler.kt @@ -46,19 +46,6 @@ object BackupScheduler { if (schedulerJob?.isActive == true) return logger.info { "Backup scheduler started" } - // Eagerly set timestamps so StatusScreen has data immediately. - // Only set lastBackupTime and nextBackupTime here (cheap). - // The full lastBackupResult (fileCount, sizeBytes) is filled by the async path - // since fileCount() walks the directory tree and would block the main thread. - val config = ConfigManager.config.value - if (config.isConfigured && config.backupPath != null) { - val backups = BackupHistory.listBackups(File(config.backupPath)) - if (backups.isNotEmpty()) { - _state.update { it.copy(lastBackupTime = backups.first().timestamp) } - } - updateNextBackupTime(TimeZone.currentSystemDefault()) - } - schedulerJob = scope.launch { // Collect progress from BackupEngine launch { @@ -78,23 +65,23 @@ object BackupScheduler { } // On first config emission, restore last backup info from disk - if (_state.value.lastBackupResult == null && config.backupPath != null) { + if (_state.value.lastBackupTime == null && config.backupPath != null) { val backups = BackupHistory.listBackups(File(config.backupPath)) if (backups.isNotEmpty()) { val latest = backups.first() - val metrics = BackupHistory.computeMetrics(latest) + val fileCount = BackupHistory.fileCount(latest) _state.update { it.copy( lastBackupTime = latest.timestamp, lastBackupResult = BackupResult.Success( backupPath = latest.path, - fileCount = metrics.fileCount, - sizeBytes = metrics.sizeBytes, + fileCount = fileCount, + sizeBytes = latest.sizeBytes, durationMs = 0, ), ) } - logger.info { "Restored last backup info from disk: ${latest.path.name} (${metrics.fileCount} files)" } + logger.info { "Restored last backup info from disk: ${latest.path.name} ($fileCount files)" } } } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/config/AppConfig.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/config/AppConfig.kt index ae7649e..65ddf1d 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/config/AppConfig.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/config/AppConfig.kt @@ -1,26 +1,8 @@ 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, @@ -33,8 +15,6 @@ 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() diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt index 1287b35..8432651 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.application +import androidx.compose.ui.window.TrayState import androidx.compose.ui.window.rememberTrayState import com.rukira.wowbackup.backup.BackupNotifier import com.rukira.wowbackup.backup.BackupScheduler @@ -18,7 +19,6 @@ import com.rukira.wowbackup.config.ConfigManager import com.rukira.wowbackup.logging.LoggingSetup import com.rukira.wowbackup.platform.OS import com.rukira.wowbackup.platform.Platform -import com.rukira.wowbackup.platform.StartupManager import com.rukira.wowbackup.platform.WoWLocations import com.rukira.wowbackup.ui.Screen import com.rukira.wowbackup.ui.trayIconPainter @@ -34,21 +34,19 @@ fun main() { ConfigManager.load() logger.info { "Config loaded. Configured: ${ConfigManager.isConfigured}" } - // Sync OS startup registration with persisted config - StartupManager.setEnabled(ConfigManager.config.value.runAtStartup) - val detectedWoW = WoWLocations.findWoWInstall() if (detectedWoW != null) { logger.info { "Auto-detected WoW at: $detectedWoW" } } - val isConfigured = ConfigManager.isConfigured + val startScreen = if (ConfigManager.isConfigured) Screen.STATUS else Screen.CONFIG // Start backup scheduler BackupScheduler.start() application { - var currentScreen by remember { mutableStateOf(if (isConfigured) null else Screen.CONFIG) } + var isWindowVisible by remember { mutableStateOf(true) } + var currentScreen by remember { mutableStateOf(startScreen) } val trayState = rememberTrayState() // Wire up notifications @@ -58,10 +56,20 @@ fun main() { state = trayState, icon = trayIconPainter(), tooltip = "WoW Backup", - onAction = { currentScreen = Screen.STATUS }, + onAction = { isWindowVisible = true }, menu = { - Item("Status", onClick = { currentScreen = Screen.STATUS }) - Item("Settings", onClick = { currentScreen = Screen.CONFIG }) + Item("Status", onClick = { + currentScreen = Screen.STATUS + isWindowVisible = true + }) + Item("Settings", onClick = { + currentScreen = Screen.CONFIG + isWindowVisible = true + }) + Separator() + Item("Backup Now", onClick = { + BackupScheduler.triggerBackupNow() + }) Separator() Item("Quit", onClick = { BackupScheduler.stop() @@ -70,9 +78,8 @@ fun main() { }, ) - val activeScreen = currentScreen - if (activeScreen != null) { - val windowState = remember(activeScreen) { + if (isWindowVisible) { + val windowState = remember(isWindowVisible) { WindowState( size = DpSize(480.dp, 620.dp), position = trayAlignedPosition(), @@ -80,13 +87,13 @@ fun main() { } Window( - onCloseRequest = { currentScreen = null }, + onCloseRequest = { isWindowVisible = false }, title = "WoW Backup", state = windowState, resizable = true, ) { App( - currentScreen = activeScreen, + currentScreen = currentScreen, onNavigate = { currentScreen = it }, ) } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/platform/StartupManager.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/platform/StartupManager.kt deleted file mode 100644 index 8cb15f7..0000000 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/platform/StartupManager.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.rukira.wowbackup.platform - -import io.github.oshai.kotlinlogging.KotlinLogging -import java.io.File - -private val logger = KotlinLogging.logger {} - -object StartupManager { - - private const val APP_ID = "com.rukira.wowbackup" - - fun setEnabled(enabled: Boolean) { - val appPath = getAppPath() - if (appPath == null) { - logger.warn { "Cannot register startup: not running from a packaged app (dev mode?)" } - return - } - - logger.info { "Setting launch-at-startup to $enabled (appPath=$appPath)" } - - when (Platform.current) { - OS.Mac -> if (enabled) enableMac(appPath) else disableMac() - OS.Windows -> if (enabled) enableWindows(appPath) else disableWindows() - else -> logger.warn { "Launch at startup is not supported on ${Platform.current}" } - } - } - - private fun getAppPath(): String? = when (Platform.current) { - OS.Mac -> { - // java.home is e.g. /Applications/com.rukira.wowbackup.app/Contents/runtime/Contents/Home - val javaHome = System.getProperty("java.home") ?: return null - val marker = ".app/Contents" - val idx = javaHome.indexOf(marker) - if (idx < 0) null else javaHome.substring(0, idx + ".app".length) - } - OS.Windows -> { - ProcessHandle.current().info().command().orElse(null)?.takeIf { it.endsWith(".exe", ignoreCase = true) } - } - else -> null - } - - // --- macOS: LaunchAgent plist --- - - private val plistFile: File - get() = File(System.getProperty("user.home"), "Library/LaunchAgents/$APP_ID.plist") - - private fun enableMac(appPath: String) { - val plist = """ - | - | - | - | - | Label - | $APP_ID - | ProgramArguments - | - | open - | -a - | $appPath - | - | RunAtLoad - | - | - | - """.trimMargin() - - try { - plistFile.parentFile.mkdirs() - plistFile.writeText(plist) - logger.info { "LaunchAgent plist written to ${plistFile.absolutePath}" } - } catch (e: Exception) { - logger.error(e) { "Failed to write LaunchAgent plist" } - } - } - - private fun disableMac() { - try { - if (plistFile.exists()) { - plistFile.delete() - logger.info { "LaunchAgent plist removed" } - } - } catch (e: Exception) { - logger.error(e) { "Failed to remove LaunchAgent plist" } - } - } - - // --- Windows: Registry --- - - private const val REG_KEY = """HKCU\Software\Microsoft\Windows\CurrentVersion\Run""" - private const val REG_VALUE = "WoWBackup" - - private fun enableWindows(appPath: String) { - try { - val process = ProcessBuilder( - "reg", "add", REG_KEY, "/v", REG_VALUE, "/d", appPath, "/f" - ).redirectErrorStream(true).start() - val exitCode = process.waitFor() - if (exitCode == 0) { - logger.info { "Registry startup entry added" } - } else { - val output = process.inputStream.bufferedReader().readText() - logger.error { "reg add failed (exit $exitCode): $output" } - } - } catch (e: Exception) { - logger.error(e) { "Failed to add registry startup entry" } - } - } - - private fun disableWindows() { - try { - val process = ProcessBuilder( - "reg", "delete", REG_KEY, "/v", REG_VALUE, "/f" - ).redirectErrorStream(true).start() - val exitCode = process.waitFor() - if (exitCode == 0) { - logger.info { "Registry startup entry removed" } - } else { - val output = process.inputStream.bufferedReader().readText() - // Exit code 1 with "not found" is expected when disabling and key doesn't exist - logger.warn { "reg delete exited $exitCode: $output" } - } - } catch (e: Exception) { - logger.error(e) { "Failed to remove registry startup entry" } - } - } -} diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigScreen.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigScreen.kt index 63922c0..a5ef35a 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigScreen.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigScreen.kt @@ -1,26 +1,15 @@ 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 -import androidx.compose.foundation.layout.fillMaxHeight 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.ScrollbarStyle -import androidx.compose.foundation.VerticalScrollbar import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.rememberScrollbarAdapter -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Checkbox @@ -28,9 +17,6 @@ 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 @@ -40,12 +26,7 @@ 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.platform.AppDirectories -import com.rukira.wowbackup.platform.DesktopActions import com.rukira.wowbackup.ui.components.ConfirmationDialog import com.rukira.wowbackup.ui.components.TimePicker import com.rukira.wowbackup.ui.components.pickFolder @@ -61,13 +42,11 @@ fun ConfigScreen( val config = uiState.config val errors = uiState.errors var showForceBackupDialog by remember { mutableStateOf(false) } - val scrollState = rememberScrollState() - Box(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier .fillMaxSize() - .verticalScroll(scrollState) + .verticalScroll(rememberScrollState()) .padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp), ) { @@ -206,64 +185,6 @@ 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() @@ -274,10 +195,6 @@ fun ConfigScreen( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, ) { - OutlinedButton(onClick = { DesktopActions.openFolder(AppDirectories.logsDir) }) { - Text("Open Logs") - } - Spacer(Modifier.weight(1f)) if (uiState.saveSuccess) { Text( "Saved!", @@ -303,20 +220,6 @@ fun ConfigScreen( } } - VerticalScrollbar( - modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), - adapter = rememberScrollbarAdapter(scrollState), - style = ScrollbarStyle( - minimalHeight = 48.dp, - thickness = 8.dp, - shape = RoundedCornerShape(4.dp), - hoverDurationMillis = 300, - unhoverColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f), - hoverColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), - ), - ) - } - // Force backup confirmation dialog if (showForceBackupDialog) { ConfirmationDialog( diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigViewModel.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigViewModel.kt index d78b938..345ca6d 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigViewModel.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/config/ConfigViewModel.kt @@ -1,11 +1,8 @@ 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.StartupManager import com.rukira.wowbackup.platform.WoWLocations import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.flow.MutableStateFlow @@ -137,20 +134,6 @@ 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) { @@ -171,7 +154,6 @@ class ConfigViewModel : ViewModel() { } ConfigManager.save(_state.value.config) - StartupManager.setEnabled(_state.value.config.runAtStartup) _state.update { it.copy(errors = emptyMap(), saveSuccess = true) } logger.info { "Configuration saved" } return true diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusScreen.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusScreen.kt index 8809df5..e26d6a6 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusScreen.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusScreen.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.rukira.wowbackup.BuildConfig import com.rukira.wowbackup.backup.BackupScheduler import com.rukira.wowbackup.platform.DesktopActions import java.io.File @@ -206,20 +205,11 @@ fun StatusScreen( }, enabled = uiState.backupPath != null, ) { - Text("Open Backups Folder") + Text("Open Folder") } OutlinedButton(onClick = onNavigateToRestore, enabled = false) { Text("Restore (Coming Soon)") } } - - Spacer(Modifier.weight(1f)) - Text( - "WoW Backup v${BuildConfig.VERSION}", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.fillMaxWidth(), - textAlign = androidx.compose.ui.text.style.TextAlign.Center, - ) } } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusViewModel.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusViewModel.kt index ff36ca0..08a3fee 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusViewModel.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusViewModel.kt @@ -43,7 +43,7 @@ class StatusViewModel : ViewModel() { BackupScheduler.state.map { it.lastBackupResult }.distinctUntilChanged(), ) { backupPath, _ -> if (backupPath != null) { - BackupHistory.countBackups(File(backupPath)) + BackupHistory.listBackups(File(backupPath)).size } else { 0 } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/theme/Theme.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/theme/Theme.kt deleted file mode 100644 index 9b4c7e7..0000000 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/theme/Theme.kt +++ /dev/null @@ -1,26 +0,0 @@ -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, - ) -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e629fd..e0c7f93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,6 @@ kotlinx-serialization = "1.10.0" cardiologist = "0.8.0" kotlin-logging = "7.0.3" logback = "1.5.18" -materialKolor = "2.0.0" [libraries] androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } @@ -19,7 +18,6 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa cardiologist = { module = "io.github.kevincianfarini.cardiologist:cardiologist", version.ref = "cardiologist" } 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" } -material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" } [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }