Add specific alteration error management.
This commit is contained in:
parent
6213d5ac15
commit
f94a530621
48 changed files with 606 additions and 511 deletions
|
|
@ -242,6 +242,7 @@
|
|||
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
|
||||
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
||||
<string name="game_master__create_character_sheet">Créer un personnage</string>
|
||||
<string name="game_master__alteration__title">Édition d'Altération</string>
|
||||
<string name="game_master__alteration__filter">Filtrer par nom :</string>
|
||||
<string name="game_master__alteration__create">Créer une altération</string>
|
||||
<string name="game_master__alteration__delete">Supprimer l'altération</string>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
|||
|
||||
class LwaNetworkException(
|
||||
val status: Int,
|
||||
val code: APIResponse.ErrorCode?,
|
||||
message: String,
|
||||
) : Exception(message) {
|
||||
constructor(error: APIResponse<*>) : this(
|
||||
status = error.status,
|
||||
code = error.code,
|
||||
message = error.message ?: "An unknown error occurred"
|
||||
)
|
||||
}
|
||||
|
|
@ -7,6 +7,10 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.pixelized.desktop.lwa.LocalErrorSnackHost
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.error__default__action
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
@Stable
|
||||
class ErrorSnackUio(
|
||||
|
|
@ -15,10 +19,10 @@ class ErrorSnackUio(
|
|||
val duration: SnackbarDuration,
|
||||
) {
|
||||
companion object {
|
||||
fun from(exception: Exception) = ErrorSnackUio(
|
||||
suspend fun from(exception: Exception) = ErrorSnackUio(
|
||||
message = exception.localizedMessage,
|
||||
action = "Ok",
|
||||
duration = SnackbarDuration.Indefinite
|
||||
action = getString(Res.string.error__default__action),
|
||||
duration = SnackbarDuration.Long,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +33,7 @@ fun ErrorSnackHandler(
|
|||
error: SharedFlow<ErrorSnackUio>,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
error.collect {
|
||||
error.collectLatest {
|
||||
snack.showSnackbar(
|
||||
message = it.message,
|
||||
actionLabel = it.action,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.textfield
|
||||
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.TextFieldColors
|
||||
|
|
@ -9,20 +10,21 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
|
||||
import com.pixelized.desktop.lwa.utils.rememberKeyboardActions
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@Stable
|
||||
data class LwaTextFieldUio(
|
||||
val enable: Boolean = true,
|
||||
val isError: StateFlow<Boolean>,
|
||||
val labelFlow: StateFlow<String?>?,
|
||||
val valueFlow: StateFlow<String>,
|
||||
val placeHolderFlow: StateFlow<String?>?,
|
||||
val isError: MutableStateFlow<Boolean>,
|
||||
val labelFlow: MutableStateFlow<String?>?,
|
||||
val valueFlow: MutableStateFlow<String>,
|
||||
val placeHolderFlow: MutableStateFlow<String?>?,
|
||||
val onValueChange: (String) -> Unit,
|
||||
)
|
||||
|
||||
|
|
@ -30,6 +32,7 @@ data class LwaTextFieldUio(
|
|||
fun LwaTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
colors: TextFieldColors = LwaTextFieldColors(),
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
singleLine: Boolean = true,
|
||||
|
|
@ -51,6 +54,7 @@ fun LwaTextField(
|
|||
TextField(
|
||||
modifier = localModifier.then(other = modifier),
|
||||
colors = colors,
|
||||
shape = shape,
|
||||
keyboardActions = rememberKeyboardActions {
|
||||
focus.moveFocus(FocusDirection.Next)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditScreen
|
||||
import com.pixelized.desktop.lwa.utils.extention.ARG
|
||||
|
||||
@Stable
|
||||
|
|
@ -44,7 +44,7 @@ fun NavGraphBuilder.composableGameMasterAlterationEditPage() {
|
|||
route = GMAlterationEditDestination.baseRoute(),
|
||||
arguments = GMAlterationEditDestination.arguments(),
|
||||
) {
|
||||
GMAlterationEditPage()
|
||||
GMAlterationEditScreen()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,39 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_expression
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_id
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_id
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_label
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_tags
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import java.util.UUID
|
||||
|
||||
class GMAlterationEditFactory(
|
||||
private val expressionParser: ExpressionParser,
|
||||
private val tagFactory: GMTagFactory,
|
||||
) {
|
||||
suspend fun createForm(
|
||||
originId: String?,
|
||||
alteration: Alteration?,
|
||||
tags: Collection<Tag>,
|
||||
): GMAlterationEditPageUio {
|
||||
val id = MutableStateFlow(alteration?.id ?: "")
|
||||
val label = MutableStateFlow(alteration?.metadata?.name ?: "")
|
||||
val description = MutableStateFlow(alteration?.metadata?.description ?: "")
|
||||
val tags = MutableStateFlow(alteration?.tags?.joinToString(", ") { it } ?: "")
|
||||
val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(createField(null)))
|
||||
val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(
|
||||
createField(null)
|
||||
))
|
||||
|
||||
return GMAlterationEditPageUio(
|
||||
id = LwaTextFieldUio(
|
||||
enable = true,
|
||||
enable = originId == null,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)),
|
||||
valueFlow = id,
|
||||
|
|
@ -51,13 +56,9 @@ class GMAlterationEditFactory(
|
|||
placeHolderFlow = null,
|
||||
onValueChange = { description.value = it },
|
||||
),
|
||||
tags = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_tags)),
|
||||
valueFlow = tags,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { tags.value = it },
|
||||
tags = tagFactory.convertToGMTagItemUio(
|
||||
tags = tags,
|
||||
selectedTagIds = alteration?.tags ?: emptyList(),
|
||||
),
|
||||
fields = fields,
|
||||
)
|
||||
|
|
@ -101,7 +102,7 @@ class GMAlterationEditFactory(
|
|||
name = form.label.valueFlow.value,
|
||||
description = form.description.valueFlow.value,
|
||||
),
|
||||
tags = form.tags.valueFlow.value.split(","),
|
||||
tags = form.tags.filter { it.highlight }.map { it.id },
|
||||
fields = form.fields.value.mapNotNull { field ->
|
||||
expressionParser.parse(input = field.expression.valueFlow.value)?.let {
|
||||
Alteration.Field(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -11,21 +18,31 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.Row
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -34,21 +51,27 @@ import androidx.compose.ui.input.key.Key
|
|||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.LocalGMScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagButton
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_add_field
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_cancel
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_save
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__title
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_save_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -59,7 +82,7 @@ data class GMAlterationEditPageUio(
|
|||
val id: LwaTextFieldUio,
|
||||
val label: LwaTextFieldUio,
|
||||
val description: LwaTextFieldUio,
|
||||
val tags: LwaTextFieldUio,
|
||||
val tags: List<GMTagUio>,
|
||||
val fields: MutableStateFlow<List<SkillUio>>,
|
||||
) {
|
||||
@Stable
|
||||
|
|
@ -76,40 +99,37 @@ object GMAlterationEditPageDefault {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun GMAlterationEditPage(
|
||||
fun GMAlterationEditScreen(
|
||||
viewModel: GMAlterationEditViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val form = viewModel.form.collectAsState()
|
||||
|
||||
AnimatedContent(
|
||||
targetState = form.value,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
when (it) {
|
||||
null -> Box(modifier = Modifier.fillMaxSize())
|
||||
else -> GMAlterationEditContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
form = it,
|
||||
paddings = GMAlterationEditPageDefault.paddings,
|
||||
addField = {
|
||||
scope.launch {
|
||||
viewModel.addField()
|
||||
}
|
||||
},
|
||||
removeField = viewModel::removeField,
|
||||
onSave = {
|
||||
scope.launch {
|
||||
viewModel.save()
|
||||
}
|
||||
},
|
||||
onCancel = {
|
||||
screen.popBackStack()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
GMAlterationEditContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
form = form,
|
||||
paddings = GMAlterationEditPageDefault.paddings,
|
||||
onBack = {
|
||||
screen.navigateBack()
|
||||
},
|
||||
addField = {
|
||||
scope.launch {
|
||||
viewModel.addField()
|
||||
}
|
||||
},
|
||||
removeField = viewModel::removeField,
|
||||
onSave = {
|
||||
scope.launch {
|
||||
if (viewModel.save()) {
|
||||
screen.navigateBack()
|
||||
}
|
||||
}
|
||||
},
|
||||
onTag = { tag ->
|
||||
viewModel.addTag(tag = tag)
|
||||
},
|
||||
)
|
||||
|
||||
ErrorSnackHandler(
|
||||
error = viewModel.error,
|
||||
|
|
@ -117,7 +137,7 @@ fun GMAlterationEditPage(
|
|||
|
||||
AlterationEditKeyHandler(
|
||||
onDismissRequest = {
|
||||
screen.popBackStack()
|
||||
screen.navigateBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -125,148 +145,218 @@ fun GMAlterationEditPage(
|
|||
@Composable
|
||||
private fun GMAlterationEditContent(
|
||||
modifier: Modifier = Modifier,
|
||||
form: GMAlterationEditPageUio,
|
||||
scope: CoroutineScope = rememberCoroutineScope(),
|
||||
tagsState: LazyListState = rememberLazyListState(),
|
||||
form: State<GMAlterationEditPageUio?>,
|
||||
paddings: PaddingValues,
|
||||
onBack: () -> Unit,
|
||||
addField: () -> Unit,
|
||||
removeField: (index: Int) -> Unit,
|
||||
onSave: () -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
onTag: (GMTagUio) -> Unit,
|
||||
) {
|
||||
val fields = form.fields.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
contentPadding = paddings,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
item(
|
||||
key = "Id",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.id,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Name",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.label,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Description",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.description,
|
||||
singleLine = false,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Tags",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.tags,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
itemsIndexed(
|
||||
items = fields.value,
|
||||
key = { _, item -> item.key },
|
||||
) { index, item ->
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.id,
|
||||
singleLine = true,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.expression,
|
||||
singleLine = true,
|
||||
)
|
||||
IconButton(
|
||||
onClick = { removeField(index) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.string.game_master__alteration__title),
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
TextButton(
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = {
|
||||
AnimatedContent(
|
||||
targetState = form.value,
|
||||
transitionSpec = {
|
||||
if (initialState?.id == targetState?.id) {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
} else {
|
||||
fadeIn() togetherWith fadeOut()
|
||||
}
|
||||
}
|
||||
) {
|
||||
when (it) {
|
||||
null -> Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
else -> {
|
||||
val fields = it.fields.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = paddings,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
item(
|
||||
key = "Id",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = it.id,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Name",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = it.label,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Description",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = it.description,
|
||||
singleLine = false,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Tags",
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
tagsState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = tagsState,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
items(
|
||||
items = it.tags,
|
||||
) { tag ->
|
||||
GMTagButton(
|
||||
modifier = Modifier.height(48.dp),
|
||||
tag = tag,
|
||||
onTag = { onTag(tag) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsIndexed(
|
||||
items = fields.value,
|
||||
key = { _, item -> item.key },
|
||||
) { index, item ->
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.id,
|
||||
singleLine = true,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.expression,
|
||||
singleLine = true,
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.size(size = 56.dp)
|
||||
.background(
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
),
|
||||
onClick = { removeField(index) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item(
|
||||
key = "Actions",
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = addField,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_add_field),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item(
|
||||
key = "Actions",
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = addField,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_add_field),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onCancel,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_cancel),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -283,4 +373,9 @@ private fun AlterationEditKeyHandler(
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavHostController.navigateBack() = popBackStack(
|
||||
route = GMAlterationEditDestination.baseRoute(),
|
||||
inclusive = true,
|
||||
)
|
||||
|
|
@ -3,9 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.network.LwaNetworkException
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse.ErrorCode
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
|
@ -15,6 +19,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class GMAlterationEditViewModel(
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val tagRepository: TagRepository,
|
||||
private val factory: GMAlterationEditFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
|
@ -29,28 +34,33 @@ class GMAlterationEditViewModel(
|
|||
init {
|
||||
viewModelScope.launch {
|
||||
_form.value = factory.createForm(
|
||||
alteration = alterationRepository.alteration(alterationId = argument.id)
|
||||
originId = argument.id,
|
||||
alteration = alterationRepository.alteration(alterationId = argument.id),
|
||||
tags = tagRepository.alterationsTagFlow().value.values,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save() {
|
||||
val edited = factory.createAlteration(form = form.value)
|
||||
val actual = alterationRepository.alterationFlow.value[edited?.id]
|
||||
|
||||
if (edited == null) return
|
||||
suspend fun save(): Boolean {
|
||||
val edited = factory.createAlteration(form = form.value) ?: return false
|
||||
|
||||
try {
|
||||
if (argument.id == null && actual?.id != null) {
|
||||
error("Id already taken by an another alteration")
|
||||
}
|
||||
alterationRepository.updateAlteration(
|
||||
alteration = edited,
|
||||
create = argument.id == null,
|
||||
)
|
||||
return true
|
||||
} catch (exception: LwaNetworkException) {
|
||||
_form.value?.id?.isError?.value = exception.code == ErrorCode.AlterationId
|
||||
_form.value?.label?.isError?.value = exception.code == ErrorCode.AlterationName
|
||||
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(message)
|
||||
return false
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,4 +79,16 @@ class GMAlterationEditViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTag(tag: GMTagUio) {
|
||||
_form.update {
|
||||
it?.copy(
|
||||
tags = it.tags.toMutableList().also { tags ->
|
||||
val index = tags.indexOf(tag)
|
||||
if (index > -1)
|
||||
tags[index] = tag.copy(highlight = tag.highlight.not())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.graphics.Shape
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
||||
|
||||
|
|
@ -61,4 +63,36 @@ fun GMTag(
|
|||
text = tag.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun GMTagButton(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = GmTagDefault.padding,
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
tag: GMTagUio,
|
||||
onTag: (() -> Unit)? = null,
|
||||
) {
|
||||
val animatedColor = animateColorAsState(
|
||||
when (tag.highlight) {
|
||||
true -> MaterialTheme.lwa.colorScheme.base.secondary
|
||||
else -> MaterialTheme.lwa.colorScheme.base.onSurface
|
||||
}
|
||||
)
|
||||
Button(
|
||||
modifier = modifier,
|
||||
colors = LwaButtonColors(contentColor = animatedColor.value),
|
||||
shape = shape,
|
||||
enabled = onTag != null,
|
||||
onClick = { onTag?.invoke() },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
color = animatedColor.value,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = tag.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,23 @@ import java.text.Collator
|
|||
|
||||
class GMTagFactory {
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
tags: Collection<Tag>,
|
||||
selectedTagIds: List<String>,
|
||||
): List<GMTagUio> {
|
||||
return tags
|
||||
.map { tag ->
|
||||
GMTagUio(
|
||||
id = tag.id,
|
||||
label = tag.label,
|
||||
highlight = selectedTagIds.contains(tag.id),
|
||||
)
|
||||
}
|
||||
.sortedWith(
|
||||
comparator = compareBy(Collator.getInstance()) { it.label }
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
tags: Collection<Tag>,
|
||||
selectedTagId: String?,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ fun LwaTheme(
|
|||
MaterialTheme(
|
||||
colors = lwaColors.base,
|
||||
typography = MaterialTheme.typography,
|
||||
shapes = MaterialTheme.shapes,
|
||||
shapes = lwaShapes.base,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import androidx.compose.material.ContentAlpha
|
|||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun LwaButtonColors(): ButtonColors = ButtonDefaults.buttonColors(
|
||||
fun LwaButtonColors(
|
||||
contentColor: Color = MaterialTheme.colors.primary,
|
||||
): ButtonColors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.surface,
|
||||
contentColor = MaterialTheme.colors.primary,
|
||||
contentColor = contentColor,
|
||||
disabledContentColor = MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled),
|
||||
)
|
||||
|
||||
)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.ui.theme.shapes
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -9,6 +10,7 @@ import androidx.compose.ui.unit.dp
|
|||
|
||||
@Stable
|
||||
data class LwaShapes(
|
||||
val base: Shapes,
|
||||
val portrait: Shape,
|
||||
val panel: Shape,
|
||||
val settings: Shape,
|
||||
|
|
@ -18,12 +20,16 @@ data class LwaShapes(
|
|||
@Stable
|
||||
@Composable
|
||||
fun lwaShapes(
|
||||
base: Shapes = Shapes(
|
||||
small = RoundedCornerShape(4.dp),
|
||||
),
|
||||
portrait: Shape = RoundedCornerShape(8.dp),
|
||||
panel: Shape = RoundedCornerShape(8.dp),
|
||||
settings: Shape = RoundedCornerShape(8.dp),
|
||||
gameMaster: Shape = RoundedCornerShape(8.dp),
|
||||
): LwaShapes = remember {
|
||||
LwaShapes(
|
||||
base = base,
|
||||
portrait = portrait,
|
||||
panel = panel,
|
||||
settings = settings,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue