diff --git a/.gitea/issue_template/bug_report.yaml b/.gitea/issue_template/bug_report.yaml new file mode 100644 index 0000000..de6325e --- /dev/null +++ b/.gitea/issue_template/bug_report.yaml @@ -0,0 +1,94 @@ +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 7f8ac7e..53c56a0 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -34,6 +34,33 @@ kotlin { } } +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" @@ -43,7 +70,7 @@ compose.desktop { nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "com.rukira.wowbackup" - packageVersion = "1.0.0" + packageVersion = appVersion } } } diff --git a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt index 160eddc..1287b35 100644 --- a/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt +++ b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt @@ -11,7 +11,6 @@ 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 @@ -43,14 +42,13 @@ fun main() { logger.info { "Auto-detected WoW at: $detectedWoW" } } - val startScreen = if (ConfigManager.isConfigured) Screen.STATUS else Screen.CONFIG + val isConfigured = ConfigManager.isConfigured // Start backup scheduler BackupScheduler.start() application { - var isWindowVisible by remember { mutableStateOf(true) } - var currentScreen by remember { mutableStateOf(startScreen) } + var currentScreen by remember { mutableStateOf(if (isConfigured) null else Screen.CONFIG) } val trayState = rememberTrayState() // Wire up notifications @@ -60,16 +58,10 @@ fun main() { state = trayState, icon = trayIconPainter(), tooltip = "WoW Backup", - onAction = { isWindowVisible = true }, + onAction = { currentScreen = Screen.STATUS }, menu = { - Item("Status", onClick = { - currentScreen = Screen.STATUS - isWindowVisible = true - }) - Item("Settings", onClick = { - currentScreen = Screen.CONFIG - isWindowVisible = true - }) + Item("Status", onClick = { currentScreen = Screen.STATUS }) + Item("Settings", onClick = { currentScreen = Screen.CONFIG }) Separator() Item("Quit", onClick = { BackupScheduler.stop() @@ -78,8 +70,9 @@ fun main() { }, ) - if (isWindowVisible) { - val windowState = remember(isWindowVisible) { + val activeScreen = currentScreen + if (activeScreen != null) { + val windowState = remember(activeScreen) { WindowState( size = DpSize(480.dp, 620.dp), position = trayAlignedPosition(), @@ -87,13 +80,13 @@ fun main() { } Window( - onCloseRequest = { isWindowVisible = false }, + onCloseRequest = { currentScreen = null }, title = "WoW Backup", state = windowState, resizable = true, ) { App( - currentScreen = currentScreen, + currentScreen = activeScreen, onNavigate = { currentScreen = it }, ) } 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 8f3bbad..63922c0 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 @@ -8,14 +8,19 @@ 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 @@ -56,11 +61,13 @@ 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(rememberScrollState()) + .verticalScroll(scrollState) .padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp), ) { @@ -296,6 +303,20 @@ 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/status/StatusScreen.kt b/composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/ui/status/StatusScreen.kt index 5a77680..8809df5 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,6 +24,7 @@ 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 @@ -211,5 +212,14 @@ fun StatusScreen( 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, + ) } }