This commit was merged in pull request #72.
This commit is contained in:
@@ -320,7 +320,12 @@ private fun MainScaffold(
|
||||
)
|
||||
}
|
||||
composable(AppRoutes.SETTINGS) {
|
||||
SettingsPage()
|
||||
SettingsPage(
|
||||
character = character,
|
||||
onImportCharacter = { imported ->
|
||||
characterViewModel.setCharacter(imported)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,20 @@ object TestTags {
|
||||
const val SETTINGS_THEME_SYSTEM = "settings_theme_system"
|
||||
const val SETTINGS_THEME_LIGHT = "settings_theme_light"
|
||||
const val SETTINGS_THEME_DARK = "settings_theme_dark"
|
||||
const val SETTINGS_EXPORT_BUTTON = "settings_export_button"
|
||||
const val SETTINGS_IMPORT_BUTTON = "settings_import_button"
|
||||
|
||||
// --- Export dialog ---
|
||||
const val EXPORT_DIALOG = "export_dialog"
|
||||
const val EXPORT_JSON_TEXT = "export_json_text"
|
||||
const val EXPORT_DISMISS = "export_dismiss"
|
||||
|
||||
// --- Import dialog ---
|
||||
const val IMPORT_DIALOG = "import_dialog"
|
||||
const val IMPORT_JSON_INPUT = "import_json_input"
|
||||
const val IMPORT_CONFIRM = "import_confirm"
|
||||
const val IMPORT_DISMISS = "import_dismiss"
|
||||
const val IMPORT_ERROR = "import_error"
|
||||
|
||||
// --- Top app bar ---
|
||||
const val TOP_APP_BAR = "top_app_bar"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.shahondin1624.lib.components.settings
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.shahondin1624.lib.components.TestTags
|
||||
|
||||
/**
|
||||
* Dialog displaying the character JSON for export.
|
||||
* The user can select and copy the text manually.
|
||||
*/
|
||||
@Composable
|
||||
fun ExportCharacterDialog(
|
||||
json: String,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
modifier = Modifier.testTag(TestTags.EXPORT_DIALOG),
|
||||
title = { Text("Export Character") },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = "Copy the JSON below to save your character:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 300.dp)
|
||||
) {
|
||||
Text(
|
||||
text = json,
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(8.dp)
|
||||
.testTag(TestTags.EXPORT_JSON_TEXT)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.testTag(TestTags.EXPORT_DISMISS)
|
||||
) {
|
||||
Text("Close")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.shahondin1624.lib.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.shahondin1624.lib.components.TestTags
|
||||
import org.shahondin1624.lib.functions.DataLoader
|
||||
import org.shahondin1624.model.charactermodel.ShadowrunCharacter
|
||||
|
||||
/**
|
||||
* Dialog for importing a character from pasted JSON.
|
||||
* Validates the JSON and shows an error message for invalid input.
|
||||
*/
|
||||
@Composable
|
||||
fun ImportCharacterDialog(
|
||||
onImport: (ShadowrunCharacter) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
var jsonText by remember { mutableStateOf("") }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
modifier = Modifier.testTag(TestTags.IMPORT_DIALOG),
|
||||
title = { Text("Import Character") },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = "Paste the character JSON below:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = jsonText,
|
||||
onValueChange = {
|
||||
jsonText = it
|
||||
errorMessage = null
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 150.dp, max = 300.dp)
|
||||
.testTag(TestTags.IMPORT_JSON_INPUT),
|
||||
placeholder = { Text("{ \"characterData\": ... }") },
|
||||
isError = errorMessage != null,
|
||||
maxLines = Int.MAX_VALUE
|
||||
)
|
||||
if (errorMessage != null) {
|
||||
Text(
|
||||
text = errorMessage!!,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.testTag(TestTags.IMPORT_ERROR)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
try {
|
||||
val character = DataLoader.deserialize(jsonText)
|
||||
onImport(character)
|
||||
} catch (e: Exception) {
|
||||
errorMessage = "Invalid JSON: ${e.message?.take(100) ?: "unknown error"}"
|
||||
}
|
||||
},
|
||||
modifier = Modifier.testTag(TestTags.IMPORT_CONFIRM),
|
||||
enabled = jsonText.isNotBlank()
|
||||
) {
|
||||
Text("Import")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.testTag(TestTags.IMPORT_DISMISS)
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package org.shahondin1624.lib.components.settings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Upload
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
@@ -14,23 +17,52 @@ import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.shahondin1624.lib.components.TestTags
|
||||
import org.shahondin1624.lib.functions.DataLoader
|
||||
import org.shahondin1624.model.charactermodel.ShadowrunCharacter
|
||||
import org.shahondin1624.theme.LocalThemePreference
|
||||
import org.shahondin1624.theme.ThemePreference
|
||||
|
||||
/**
|
||||
* Settings page with theme selection (Light / Dark / System Default).
|
||||
* Settings page with theme selection and character export/import.
|
||||
*/
|
||||
@Composable
|
||||
fun SettingsPage() {
|
||||
fun SettingsPage(
|
||||
character: ShadowrunCharacter? = null,
|
||||
onImportCharacter: ((ShadowrunCharacter) -> Unit)? = null
|
||||
) {
|
||||
var themePreference by LocalThemePreference.current
|
||||
var showExportDialog by remember { mutableStateOf(false) }
|
||||
var showImportDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// Export dialog
|
||||
if (showExportDialog && character != null) {
|
||||
val json = remember(character) { DataLoader.serialize(character) }
|
||||
ExportCharacterDialog(
|
||||
json = json,
|
||||
onDismiss = { showExportDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
// Import dialog
|
||||
if (showImportDialog && onImportCharacter != null) {
|
||||
ImportCharacterDialog(
|
||||
onImport = { importedCharacter ->
|
||||
onImportCharacter(importedCharacter)
|
||||
showImportDialog = false
|
||||
},
|
||||
onDismiss = { showImportDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
.testTag(TestTags.SETTINGS_PAGE),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// --- Appearance section ---
|
||||
Text(
|
||||
text = "Appearance",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
@@ -78,6 +110,62 @@ fun SettingsPage() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Character Data section ---
|
||||
if (character != null && onImportCharacter != null) {
|
||||
Text(
|
||||
text = "Character Data",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = { showExportDialog = true },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.testTag(TestTags.SETTINGS_EXPORT_BUTTON)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Upload,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Export")
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = { showImportDialog = true },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.testTag(TestTags.SETTINGS_IMPORT_BUTTON)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Import")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user