Refactor: clean unused legacy character sheet stuff.

This commit is contained in:
Thomas Andres Gomez 2025-05-09 17:38:32 +02:00
parent 09ed7d2d90
commit 763f575be4
28 changed files with 0 additions and 4051 deletions

View file

@ -35,17 +35,12 @@ import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler
import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers
import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination
import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
import com.pixelized.desktop.lwa.ui.navigation.window.WindowsNavHost
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetEditWindow
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow
import com.pixelized.desktop.lwa.ui.navigation.window.destination.GameMasterWindow
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterNavHost
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
import com.pixelized.desktop.lwa.utils.InstallCoil
@ -162,18 +157,6 @@ private fun WindowsHandler(
controller = windowController,
content = { window ->
when (window) {
is CharacterSheetWindow -> CharacterSheetMainNavHost(
startDestination = CharacterSheetDestination.navigationRoute(
characterSheetId = window.characterSheetId,
),
)
is CharacterSheetEditWindow -> CharacterSheetMainNavHost(
startDestination = CharacterSheetEditDestination.navigationRoute(
characterSheetId = window.characterSheetId,
),
)
is GameMasterWindow -> LwaScaffold {
GameMasterNavHost()
}

View file

@ -46,11 +46,6 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.links.ResourcesViewModel
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetFactory
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetViewModel
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditFactory
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditViewModel
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory
@ -144,10 +139,7 @@ val repositoryDependencies
val factoryDependencies
get() = module {
factoryOf(::CharacterSheetFactory)
factoryOf(::CharacterSheetEditFactory)
factoryOf(::NetworkFactory)
factoryOf(::SkillFieldFactory)
factoryOf(::SettingsFactory)
factoryOf(::CampaignJsonFactory)
factoryOf(::CharacterRibbonFactory)
@ -177,8 +169,6 @@ val viewModelDependencies
viewModelOf(::DataSyncViewModel)
viewModelOf(::CampaignToolbarViewModel)
viewModelOf(::ResourcesViewModel)
viewModelOf(::CharacterSheetViewModel)
viewModelOf(::CharacterSheetEditViewModel)
viewModelOf(::RollViewModel)
viewModelOf(::NetworkViewModel)
viewModelOf(::PlayerRibbonViewModel)

View file

@ -1,52 +0,0 @@
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPage
import com.pixelized.desktop.lwa.utils.extention.ARG
object CharacterSheetDestination {
private const val ROUTE = "characterSheet"
private const val CHARACTER_SHEET_ID = "characterSheetId"
fun baseRoute() = "$ROUTE?${CHARACTER_SHEET_ID.ARG}"
fun navigationRoute(characterSheetId: String) = "$ROUTE?$CHARACTER_SHEET_ID=${characterSheetId}"
fun arguments() = listOf(
navArgument(CHARACTER_SHEET_ID) {
nullable = false
type = NavType.StringType
},
)
data class Argument(
val characterSheetId: String,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
characterSheetId = savedStateHandle
.get<String>(CHARACTER_SHEET_ID)
?: error("missing characterSheetId parameter"),
)
}
}
fun NavGraphBuilder.composableCharacterSheetPage() {
composable(
route = CharacterSheetDestination.baseRoute(),
arguments = CharacterSheetDestination.arguments(),
) {
CharacterSheetPage()
}
}
fun NavHostController.navigateToCharacterSheet(
characterSheetId: String,
) {
val route = CharacterSheetDestination.navigationRoute(characterSheetId = characterSheetId)
navigate(route = route)
}

View file

@ -1,52 +0,0 @@
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
import androidx.compose.runtime.Stable
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditPage
import com.pixelized.desktop.lwa.utils.extention.ARG
object CharacterSheetEditDestination {
private const val ROUTE = "character.sheet.edit"
private const val CHARACTER_ID = "id"
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
fun navigationRoute(characterSheetId: String?) = "$ROUTE?$CHARACTER_ID=$characterSheetId"
fun arguments() = listOf(
navArgument(CHARACTER_ID) {
nullable = true
type = NavType.StringType
},
)
@Stable
data class Argument(
val id: String?,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
id = savedStateHandle.get<String>(CHARACTER_ID),
)
}
}
fun NavGraphBuilder.composableCharacterSheetEditPage() {
composable(
route = CharacterSheetEditDestination.baseRoute(),
arguments = CharacterSheetEditDestination.arguments(),
) {
CharacterSheetEditPage()
}
}
fun NavHostController.navigateToCharacterSheetEdit(
id: String? = null,
) {
val route = CharacterSheetEditDestination.navigationRoute(characterSheetId = id)
navigate(route = route)
}

View file

@ -1,32 +0,0 @@
package com.pixelized.desktop.lwa.ui.navigation.window.destination
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
@Stable
class CharacterSheetEditWindow(
val characterSheetId: String?,
title: String,
size: DpSize,
) : Window(
title = title,
size = size,
)
fun WindowController.navigateToCharacterSheetEdit(
characterId: String?,
title: String = "",
) {
showWindow(
window = CharacterSheetEditWindow(
characterSheetId = characterId,
title = title,
size = DpSize(
width = 600.dp,
height = maxWindowHeight - 32.dp,
),
)
)
}

View file

@ -1,32 +0,0 @@
package com.pixelized.desktop.lwa.ui.navigation.window.destination
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
@Stable
class CharacterSheetWindow(
val characterSheetId: String,
title: String,
size: DpSize,
) : Window(
title = title,
size = size,
)
fun WindowController.navigateToCharacterSheet(
characterSheetId: String,
title: String = "Feuille de personnage",
) {
showWindow(
window = CharacterSheetWindow(
characterSheetId = characterSheetId,
title = title,
size = DpSize(
width = 400.dp + 64.dp,
height = maxWindowHeight - 32.dp,
),
)
)
}

View file

@ -6,8 +6,6 @@ import androidx.navigation.NavHostController
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterEditPage
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Difficulty
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Result
@ -23,9 +21,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title
import org.jetbrains.compose.resources.getString
class CharacterDetailPanelViewModel(
private val characterSheetRepository: CharacterSheetRepository,

View file

@ -1,35 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableCharacterSheetEditPage
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableCharacterSheetPage
@Composable
fun CharacterSheetMainNavHost(
controller: NavHostController = rememberNavController(),
startDestination: String,
) {
CompositionLocalProvider(
LocalScreenController provides controller,
) {
Surface(
modifier = Modifier.fillMaxSize(),
) {
NavHost(
navController = controller,
startDestination = startDestination,
) {
composableCharacterSheetPage()
composableCharacterSheetEditPage()
}
}
}
}

View file

@ -1,183 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.copy
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__copy__error
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
import org.jetbrains.compose.resources.stringResource
@Stable
data class CharacterSheetCopyDialogUio(
val label: String,
val value: LwaTextFieldUio,
val validate: () -> Boolean,
)
@Composable
fun CharacterSheetCopyDialog(
dialog: State<CharacterSheetCopyDialogUio?>,
onConfirm: (CharacterSheetCopyDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
AnimatedContent(
modifier = Modifier.fillMaxSize(),
targetState = dialog.value,
transitionSpec = {
val enter = fadeIn() + slideInVertically { 32 }
val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
Box(
modifier = Modifier.fillMaxSize()
) {
when (it) {
null -> Box(
modifier = Modifier,
)
else -> {
val focusRequester = remember {
FocusRequester()
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Dialog(
dialog = it,
focusRequester = focusRequester,
onConfirm = onConfirm,
onDismissRequest = onDismissRequest,
)
}
}
}
}
}
@Composable
private fun Dialog(
dialog: CharacterSheetCopyDialogUio,
focusRequester: FocusRequester = remember { FocusRequester() },
onConfirm: (CharacterSheetCopyDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
Box(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onDismissRequest,
)
.onPreviewEscape(
escape = onDismissRequest,
enter = { onConfirm(dialog) },
)
.fillMaxSize()
.padding(all = 32.dp),
contentAlignment = Alignment.Center,
) {
DecoratedBox {
Surface {
Column(
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(top = 16.dp, start = 24.dp, end = 24.dp),
style = MaterialTheme.typography.caption,
text = dialog.label,
)
LwaTextField(
modifier = Modifier
.focusRequester(focusRequester = focusRequester)
.width(512.dp),
field = dialog.value,
)
AnimatedVisibility(
visible = dialog.value.errorFlow.collectAsState().value,
enter = fadeIn(),
exit = fadeOut(),
) {
Text(
style = MaterialTheme.lwa.typography.system.caption,
color = MaterialTheme.lwa.colorScheme.base.error,
text = stringResource(Res.string.character_sheet_edit__copy__error)
)
}
Row(
modifier = Modifier
.padding(bottom = 4.dp)
.padding(horizontal = 16.dp)
.align(alignment = Alignment.End),
horizontalArrangement = Arrangement.spacedBy(
space = 4.dp,
alignment = Alignment.End
)
) {
TextButton(
onClick = onDismissRequest,
) {
Text(
color = MaterialTheme.colors.primary.copy(alpha = .7f),
text = stringResource(Res.string.dialog__cancel_action)
)
}
TextButton(
onClick = {
if (dialog.validate()) {
onConfirm(dialog)
}
},
) {
Text(
text = stringResource(Res.string.dialog__confirm_action)
)
}
}
}
}
}
}
}

View file

@ -1,151 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.delete
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__description
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__title
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
import org.jetbrains.compose.resources.stringResource
@Stable
data class CharacterSheetDeleteDialogUio(
val characterSheetId: String,
val name: String,
)
@Composable
fun CharacterSheetDeleteDialog(
dialog: State<CharacterSheetDeleteDialogUio?>,
onConfirm: (CharacterSheetDeleteDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
AnimatedContent(
modifier = Modifier.fillMaxSize(),
targetState = dialog.value,
transitionSpec = {
val enter = fadeIn() + slideInVertically { 32 }
val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
Box(
modifier = Modifier.fillMaxSize()
) {
when (it) {
null -> Box(
modifier = Modifier,
)
else -> {
Dialog(
dialog = it,
onConfirm = onConfirm,
onDismissRequest = onDismissRequest,
)
}
}
}
}
}
@Composable
private fun Dialog(
dialog: CharacterSheetDeleteDialogUio,
onConfirm: (CharacterSheetDeleteDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
Box(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onDismissRequest,
)
.onPreviewEscape(
escape = onDismissRequest,
enter = { onConfirm(dialog) },
)
.fillMaxSize()
.padding(all = 32.dp),
contentAlignment = Alignment.Center,
) {
DecoratedBox {
Surface {
Column(
verticalArrangement = Arrangement.spacedBy(space = 24.dp),
) {
Text(
modifier = Modifier
.padding(horizontal = 24.dp)
.padding(top = 24.dp),
style = MaterialTheme.typography.h6,
text = stringResource(Res.string.character_sheet__delete_dialog__title),
)
Text(
modifier = Modifier
.padding(horizontal = 24.dp),
style = MaterialTheme.typography.body1,
text = stringResource(
Res.string.character_sheet__delete_dialog__description,
dialog.name
),
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 4.dp),
horizontalArrangement = Arrangement.spacedBy(
space = 4.dp,
alignment = Alignment.End
)
) {
TextButton(
onClick = onDismissRequest,
) {
Text(
text = stringResource(Res.string.dialog__cancel_action)
)
}
TextButton(
onClick = { onConfirm(dialog) },
) {
Text(
text = stringResource(Res.string.dialog__confirm_action)
)
}
}
}
}
}
}
}

View file

@ -1,352 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Node
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__cha
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__con
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__dex
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__hei
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__int
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__pow
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__str
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hp_grow
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__learning
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__charisma
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__constitution
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__dexterity
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__height
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__intelligence
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__power
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__strength
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hp_grow
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__learning
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point
import org.jetbrains.compose.resources.getString
class CharacterSheetFactory(
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
private val skillUseCase: ExpressionUseCase,
) {
suspend fun convertToUio(
characterSheetId: String,
characterSheet: CharacterSheet?,
alterations: Map<String, List<FieldAlteration>>,
): CharacterSheetPageUio? {
if (characterSheet == null) return null
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
characterSheet = characterSheet,
alterations = alterations,
)
return CharacterSheetPageUio(
id = alteredCharacterSheet.id,
name = alteredCharacterSheet.name,
diminished = alteredCharacterSheet.diminished,
characteristics = listOf(
Characteristic(
id = CharacteristicId.STR,
label = getString(Res.string.character_sheet__characteristics__str),
value = "${alteredCharacterSheet.strength}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__str),
description = getString(Res.string.tooltip__characteristics__strength),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__str),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.strength * 5 - alteredCharacterSheet.diminished,
),
),
Characteristic(
id = CharacteristicId.DEX,
label = getString(Res.string.character_sheet__characteristics__dex),
value = "${alteredCharacterSheet.dexterity}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__dex),
description = getString(Res.string.tooltip__characteristics__dexterity),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__dex),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.dexterity * 5 - alteredCharacterSheet.diminished,
),
),
Characteristic(
id = CharacteristicId.CON,
label = getString(Res.string.character_sheet__characteristics__con),
value = "${alteredCharacterSheet.constitution}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__con),
description = getString(Res.string.tooltip__characteristics__constitution),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__con),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.constitution * 5 - alteredCharacterSheet.diminished,
),
),
Characteristic(
id = CharacteristicId.HEI,
label = getString(Res.string.character_sheet__characteristics__hei),
value = "${alteredCharacterSheet.height}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__hei),
description = getString(Res.string.tooltip__characteristics__height),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__hei),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.height * 5 - alteredCharacterSheet.diminished,
),
),
Characteristic(
id = CharacteristicId.INT,
label = getString(Res.string.character_sheet__characteristics__int),
value = "${alteredCharacterSheet.intelligence}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__int),
description = getString(Res.string.tooltip__characteristics__intelligence),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__int),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.intelligence * 5 - alteredCharacterSheet.diminished,
),
),
Characteristic(
id = CharacteristicId.POW,
label = getString(Res.string.character_sheet__characteristics__pow),
value = "${alteredCharacterSheet.power}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__pow),
description = getString(Res.string.tooltip__characteristics__power),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__pow),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.power * 5 - alteredCharacterSheet.diminished,
),
),
Characteristic(
id = CharacteristicId.CHA,
label = getString(Res.string.character_sheet__characteristics__cha),
value = "${alteredCharacterSheet.charisma}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__characteristics__cha),
description = getString(Res.string.tooltip__characteristics__charisma),
),
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = getString(Res.string.character_sheet__characteristics__cha),
rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.charisma * 5 - alteredCharacterSheet.diminished,
),
),
),
subCharacteristics = listOf(
Characteristic(
id = CharacteristicId.MOV,
label = getString(Res.string.character_sheet__sub_characteristics__movement),
value = "${alteredCharacterSheet.movement}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__movement),
description = getString(Res.string.tooltip__sub_characteristics__movement),
),
roll = null,
),
Characteristic(
id = CharacteristicId.HP,
label = getString(Res.string.character_sheet__sub_characteristics__hit_point),
value = alteredCharacterSheet.maxHp.let { maxHp -> "${maxHp - alteredCharacterSheet.damage}/${maxHp}" },
editable = true,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__hit_point),
description = getString(Res.string.tooltip__sub_characteristics__hit_point),
),
roll = null,
),
Characteristic(
id = CharacteristicId.PP,
label = getString(Res.string.character_sheet__sub_characteristics__power_point),
value = alteredCharacterSheet.maxPp.let { maxPp -> "${maxPp - alteredCharacterSheet.power}/${maxPp}" },
editable = true,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__power_point),
description = getString(Res.string.tooltip__sub_characteristics__power_point),
),
roll = null,
),
Characteristic(
id = CharacteristicId.DMG,
label = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
value = alteredCharacterSheet.damageBonus,
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
description = getString(Res.string.tooltip__sub_characteristics__bonus_damage),
),
roll = null,
),
Characteristic(
id = CharacteristicId.ARMOR,
label = getString(Res.string.character_sheet__sub_characteristics__armor),
value = "${alteredCharacterSheet.armor}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__armor),
description = getString(Res.string.tooltip__sub_characteristics__armor),
),
roll = null,
),
Characteristic(
id = CharacteristicId.LB,
label = getString(Res.string.character_sheet__sub_characteristics__learning),
value = "${alteredCharacterSheet.learning}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__learning),
description = getString(Res.string.tooltip__sub_characteristics__learning),
),
roll = null,
),
Characteristic(
id = CharacteristicId.GHP,
label = getString(Res.string.character_sheet__sub_characteristics__hp_grow),
value = "${alteredCharacterSheet.hpGrow}",
editable = false,
tooltips = BasicTooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__hp_grow),
description = getString(Res.string.tooltip__sub_characteristics__hp_grow),
),
roll = null,
),
),
commonSkills = characterSheet.commonSkills.map { skill ->
val value = skillUseCase.computeSkillValue(
sheet = alteredCharacterSheet,
skill = skill,
diminished = alteredCharacterSheet.diminished,
alterations = alterations,
)
Node(
id = skill.id,
label = skill.label,
value = value,
used = skill.used,
tooltips = skill.description?.let {
BasicTooltipUio(
title = skill.label,
description = it,
)
},
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = skill.label,
rollAction = "1d100",
rollSuccessValue = value,
),
)
},
specialSKills = characterSheet.specialSkills.map { skill ->
val value = skillUseCase.computeSkillValue(
sheet = alteredCharacterSheet,
skill = skill,
diminished = alteredCharacterSheet.diminished,
alterations = alterations,
)
Node(
id = skill.id,
label = skill.label,
value = value,
used = skill.used,
tooltips = skill.description?.let {
BasicTooltipUio(
title = skill.label,
description = it,
)
},
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = skill.label,
rollAction = "1d100",
rollSuccessValue = value,
),
)
},
magicsSkills = characterSheet.magicSkills.map { skill ->
val value = skillUseCase.computeSkillValue(
sheet = alteredCharacterSheet,
skill = skill,
diminished = alteredCharacterSheet.diminished,
alterations = alterations,
)
Node(
id = skill.id,
label = skill.label,
value = value,
used = skill.used,
tooltips = skill.description?.let {
BasicTooltipUio(
title = skill.label,
description = it,
)
},
roll = RollAction.Uio.BoundedRollActionUio(
characterSheetId = characterSheetId,
label = skill.label,
rollAction = "1d100",
rollSuccessValue = value,
),
)
},
actions = characterSheet.actions.mapNotNull { action ->
if (action.default.isEmpty()) return@mapNotNull null
CharacterSheetPageUio.Roll(
label = action.label,
value = action.default,
roll = RollAction.Uio.BoundlessRollActionUio(
characterSheetId = characterSheetId,
label = action.label,
canBeCritical = action.canBeCritical,
rollDefaultAction = action.default,
rollSpecialAction = action.special,
rollCriticalAction = action.critical,
)
)
}
)
}
}

View file

@ -1,690 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ScrollState
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.PaddingValues
import androidx.compose.foundation.layout.Row
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.heightIn
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.verticalScroll
import androidx.compose.material.Checkbox
import androidx.compose.material.CheckboxDefaults
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipLayout
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindow
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
import com.pixelized.desktop.lwa.ui.overlay.roll.RollPage
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialog
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialog
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.preview.rememberCharacterSheetPreview
import com.pixelized.desktop.lwa.utils.preview.ContentPreview
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete__label
import lwacharactersheet.composeapp.generated.resources.character_sheet__edit__label
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
import lwacharactersheet.composeapp.generated.resources.ic_skull_24dp
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
@Stable
data class CharacterSheetPageUio(
val id: String,
val name: String,
val diminished: Int,
val characteristics: List<Characteristic>,
val subCharacteristics: List<Characteristic>,
val commonSkills: List<Node>,
val specialSKills: List<Node>,
val magicsSkills: List<Node>,
val actions: List<Roll>,
) {
@Stable
data class Characteristic(
val id: String,
val label: String,
val value: String,
val editable: Boolean,
val tooltips: BasicTooltipUio?,
val roll: RollAction.Uio?,
)
@Stable
data class Node(
val id: String,
val label: String,
val value: Int,
val used: Boolean,
val tooltips: BasicTooltipUio? = null,
val roll: RollAction.Uio,
)
@Stable
data class Roll(
val label: String,
val value: String,
val roll: RollAction.Uio,
)
}
@Composable
fun CharacterSheetPage(
viewModel: CharacterSheetViewModel = koinViewModel(),
diminishedDialogViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(),
rollViewModel: RollViewModel = koinViewModel(),
) {
val windowController = LocalWindowController.current
val screen = LocalScreenController.current
val window = LocalWindow.current
val scope = rememberCoroutineScope()
val blurController = remember { BlurContentController() }
val sheet = viewModel.sheetFlow.collectAsState()
Surface(
modifier = Modifier.fillMaxSize(),
) {
BlurContent(
controller = blurController,
content = {
sheet.value?.let { sheet ->
CharacterSheetPageContent(
modifier = Modifier.fillMaxSize(),
characterSheet = sheet,
onDiminished = {
blurController.show()
scope.launch {
diminishedDialogViewModel.showDiminishedDialog(
characterSheetId = it
)
}
},
onEdit = {
windowController.navigateToCharacterSheetEdit(
title = runBlocking { getString(Res.string.character_sheet_edit__edit__title) },
characterId = sheet.id,
)
},
onDelete = {
blurController.show()
viewModel.showConfirmCharacterDeletionDialog()
},
onCharacteristic = { characteristic ->
if (characteristic.roll == null) return@CharacterSheetPageContent
scope.launch {
rollViewModel.prepareRoll(characteristic.roll)
blurController.show()
viewModel.showRollOverlay()
}
},
onSubCharacteristic = {
// blurController.show()
// scope.launch {
// viewModel.showSubCharacteristicDialog(id = it.id)
// }
},
onSkill = { node ->
scope.launch {
blurController.show()
rollViewModel.prepareRoll(node.roll)
viewModel.showRollOverlay()
}
},
onUseSkill = viewModel::onUseSkill,
onRoll = { roll ->
scope.launch {
blurController.show()
rollViewModel.prepareRoll(roll.roll)
viewModel.showRollOverlay()
}
},
)
}
},
)
AnimatedContent(
targetState = viewModel.displayRollOverlay.value,
transitionSpec = {
val enter = fadeIn() + slideInVertically { 64 }
val exit = fadeOut() + slideOutVertically { 64 }
enter togetherWith exit
},
) { roll ->
when (roll) {
true -> RollPage(
viewModel = rollViewModel,
onDismissRequest = {
blurController.hide()
viewModel.hideRollOverlay()
},
onRoll = {
scope.launch {
rollViewModel.roll()
}
}
)
else -> Box(
modifier = Modifier.fillMaxSize()
)
}
}
CharacterSheetDeleteConfirmationDialog(
dialog = viewModel.displayDeleteConfirmationDialog,
onConfirm = {
scope.launch {
viewModel.deleteCharacter(id = it.id)
if (screen.popBackStack().not()) {
windowController.hideWindow(window = window)
}
}
},
onDismissRequest = {
blurController.hide()
viewModel.hideConfirmCharacterDeletionDialog()
},
)
CharacterSheetDiminishedDialog(
dialog = viewModel.diminishedDialog,
onConfirm = {
scope.launch {
diminishedDialogViewModel.changeDiminished(dialog = it)
diminishedDialogViewModel.hideDiminishedDialog()
blurController.hide()
}
},
onDismissRequest = {
diminishedDialogViewModel.hideDiminishedDialog()
blurController.hide()
},
)
}
}
@Composable
fun CharacterSheetPageContent(
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
characterSheet: CharacterSheetPageUio,
onDiminished: (characterSheetId: String) -> Unit,
onEdit: () -> Unit,
onDelete: () -> Unit,
onCharacteristic: (characteristic: Characteristic) -> Unit,
onSubCharacteristic: (characteristic: Characteristic) -> Unit,
onSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
onUseSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
onRoll: (roll: CharacterSheetPageUio.Roll) -> Unit,
) {
Scaffold(
modifier = modifier,
topBar = {
TopAppBar(
title = {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = characterSheet.name,
)
},
actions = {
val showMenu = remember { mutableStateOf(false) }
Box {
IconButton(
onClick = { onDiminished(characterSheet.id) },
) {
Icon(
modifier = Modifier.size(size = 24.dp),
painter = painterResource(Res.drawable.ic_skull_24dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
AnimatedContent(
modifier = Modifier.align(alignment = Alignment.BottomCenter),
targetState = characterSheet.diminished,
transitionSpec = {
val sign = if (targetState > initialState) 1 else -1
val enter = fadeIn() + slideInVertically { 16 * sign }
val exit = fadeOut() + slideOutVertically { -16 * sign }
enter togetherWith exit using SizeTransform(clip = false)
}
) {
Text(
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSurface,
text = "${it}",
)
}
}
IconButton(
onClick = { showMenu.value = showMenu.value.not() },
) {
Icon(
imageVector = Icons.Default.MoreVert,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false }
) {
DropdownMenuItem(
onClick = {
showMenu.value = false
onEdit()
},
) {
Icon(
imageVector = Icons.Default.Edit,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
Text(
modifier = Modifier.padding(start = 8.dp),
color = MaterialTheme.colors.primary,
text = stringResource(Res.string.character_sheet__edit__label),
)
}
DropdownMenuItem(
onClick = {
showMenu.value = false
onDelete()
},
) {
Icon(
imageVector = Icons.Default.Delete,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
Text(
modifier = Modifier.padding(start = 8.dp),
color = MaterialTheme.colors.primary,
text = stringResource(Res.string.character_sheet__delete__label),
)
}
}
},
)
},
content = { paddingValues ->
Row(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(paddingValues)
.padding(all = 16.dp),
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
Column(
modifier = Modifier
.fillMaxHeight()
.width(100.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
characterSheet.characteristics.forEach {
Stat(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 120.dp),
characteristic = it,
onClick = { onCharacteristic(it) },
)
}
}
Column(
modifier = Modifier
.fillMaxHeight()
.weight(2f / 3f),
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
) {
Column {
Text(
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__sub_characteristics__title),
)
characterSheet.subCharacteristics.forEach {
SubCharacteristics(
modifier = Modifier.cell(),
characteristic = it,
onCharacteristic = when {
it.editable -> onSubCharacteristic
else -> null
}
)
}
}
}
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
) {
Column {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__skills__common_title),
)
characterSheet.commonSkills.forEach { skill ->
Skill(
modifier = Modifier.cell(),
node = skill,
onClick = { onSkill(skill) },
onUse = { onUseSkill(skill) },
)
}
}
}
AnimatedVisibility(
visible = characterSheet.specialSKills.isNotEmpty()
) {
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
) {
Column {
Text(
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__skills__special_title),
)
characterSheet.specialSKills.forEach { occupation ->
Skill(
modifier = Modifier.cell(),
node = occupation,
onClick = { onSkill(occupation) },
onUse = { onUseSkill(occupation) },
)
}
}
}
}
AnimatedVisibility(
visible = characterSheet.magicsSkills.isNotEmpty()
) {
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
) {
Column {
Text(
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__skills__magic_title),
)
characterSheet.magicsSkills.forEach { magic ->
Skill(
modifier = Modifier.cell(),
node = magic,
onClick = { onSkill(magic) },
onUse = { onUseSkill(magic) },
)
}
}
}
}
characterSheet.actions.forEach { roll ->
Roll(
modifier = Modifier.cell(),
label = roll.label,
onClick = { onRoll(roll) },
)
}
}
}
}
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Stat(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(all = 8.dp),
characteristic: Characteristic,
onClick: () -> Unit,
) {
BasicTooltipLayout(
tooltip = characteristic.tooltips,
content = {
DecoratedBox(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
) {
Text(
modifier = Modifier.align(alignment = Alignment.TopCenter),
style = MaterialTheme.typography.caption,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = characteristic.label,
)
Text(
modifier = Modifier.align(alignment = Alignment.Center),
style = MaterialTheme.typography.h3,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = MaterialTheme.colors.primary,
text = characteristic.value
)
}
},
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun SubCharacteristics(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
characteristic: Characteristic,
onCharacteristic: ((characteristic: Characteristic) -> Unit)?,
) {
BasicTooltipLayout(
tooltip = characteristic.tooltips,
content = {
Row(
modifier = Modifier
.clickable(enabled = characteristic.editable && onCharacteristic != null) {
onCharacteristic?.invoke(characteristic)
}
.padding(paddingValues = paddingValues).then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
style = MaterialTheme.typography.body1,
text = characteristic.label
)
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = when (characteristic.editable && onCharacteristic != null) {
true -> MaterialTheme.colors.primary
else -> MaterialTheme.colors.onSurface
},
text = characteristic.value,
)
}
},
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Skill(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
node: CharacterSheetPageUio.Node,
onClick: () -> Unit,
onUse: () -> Unit,
) {
BasicTooltipLayout(
tooltip = node.tooltips,
content = {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = node.label
)
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary,
text = "${node.value}",
)
Checkbox(
modifier = Modifier.size(size = 32.dp),
checked = node.used,
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colors.primary,
),
onCheckedChange = { onUse() },
)
}
},
)
}
@Composable
private fun Roll(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(start = 10.dp, end = 14.dp),
label: String,
onClick: () -> Unit,
) {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = label
)
Icon(
modifier = Modifier.size(size = 24.dp),
painter = painterResource(Res.drawable.ic_d20_24dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
}
private fun Modifier.cell(): Modifier = this.fillMaxWidth().height(height = 32.dp)
@Composable
@Preview
private fun DecoratedBoxPreview() {
ContentPreview {
CharacterSheetPageContent(
modifier = Modifier.fillMaxSize(),
scrollState = rememberScrollState(),
characterSheet = rememberCharacterSheetPreview(),
onDiminished = { },
onEdit = { },
onDelete = { },
onCharacteristic = { },
onSubCharacteristic = { },
onSkill = { },
onUseSkill = { },
onRoll = { },
)
}
}

View file

@ -1,95 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialogUio
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogUio
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
private typealias CSDCDialogUio = CharacterSheetDeleteConfirmationDialogUio
class CharacterSheetViewModel(
private val characterRepository: CharacterSheetRepository,
private val networkRepository: NetworkRepository,
alteration: AlterationRepository,
factory: CharacterSheetFactory,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val argument = CharacterSheetDestination.Argument(savedStateHandle)
private val _displayDeleteConfirmationDialog = mutableStateOf<CSDCDialogUio?>(null)
val displayDeleteConfirmationDialog: State<CSDCDialogUio?> get() = _displayDeleteConfirmationDialog
private val _displayRollOverlay = mutableStateOf(false)
val displayRollOverlay: State<Boolean> get() = _displayRollOverlay
private val _diminishedDialog = mutableStateOf<CharacterSheetDiminishedDialogUio?>(null)
val diminishedDialog: State<CharacterSheetDiminishedDialogUio?> get() = _diminishedDialog
val sheetFlow = combine(
characterRepository.characterDetailFlow(characterSheetId = argument.characterSheetId),
alteration.activeFieldAlterationsFlow(characterSheetId = argument.characterSheetId),
transform = { characterSheet, alterations ->
factory.convertToUio(
characterSheetId = argument.characterSheetId,
characterSheet = characterSheet,
alterations = alterations,
)
},
).stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = null,
)
suspend fun deleteCharacter(id: String) {
characterRepository.deleteCharacter(characterSheetId = id)
}
fun onUseSkill(skill: CharacterSheetPageUio.Node) {
viewModelScope.launch {
networkRepository.share(
message = CharacterSheetEvent.UpdateSkillUsageEvent(
timestamp = System.currentTimeMillis(),
characterSheetId = argument.characterSheetId,
skillId = skill.id,
used = skill.used.not(),
)
)
}
}
fun showConfirmCharacterDeletionDialog() {
val preview = characterRepository.characterPreview(
characterId = argument.characterSheetId
) ?: return
_displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio(
id = preview.characterSheetId,
name = preview.name,
)
}
fun hideConfirmCharacterDeletionDialog() {
_displayDeleteConfirmationDialog.value = null
}
fun showRollOverlay() {
_displayRollOverlay.value = true
}
fun hideRollOverlay() {
_displayRollOverlay.value = false
}
}

View file

@ -1,130 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__description
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__title
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
import org.jetbrains.compose.resources.stringResource
@Stable
data class CharacterSheetDeleteConfirmationDialogUio(
val id: String,
val name: String,
)
@Composable
fun CharacterSheetDeleteConfirmationDialog(
dialog: State<CharacterSheetDeleteConfirmationDialogUio?>,
onConfirm: (CharacterSheetDeleteConfirmationDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
dialog.value?.let {
Dialog(
onDismissRequest = onDismissRequest,
content = {
CharacterSheetDeleteConfirmationContent(
dialog = it,
onConfirm = onConfirm,
onDismissRequest = onDismissRequest,
)
}
)
}
}
@Composable
private fun CharacterSheetDeleteConfirmationContent(
dialog: CharacterSheetDeleteConfirmationDialogUio,
onConfirm: (CharacterSheetDeleteConfirmationDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
Box(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onDismissRequest,
)
.onPreviewEscape(
escape = onDismissRequest,
enter = { onConfirm(dialog) },
)
.fillMaxSize()
.padding(all = 32.dp),
contentAlignment = Alignment.Center,
) {
DecoratedBox {
Surface {
Column(
verticalArrangement = Arrangement.spacedBy(space = 24.dp),
) {
Text(
modifier = Modifier
.padding(horizontal = 24.dp)
.padding(top = 24.dp),
style = MaterialTheme.typography.h6,
text = stringResource(Res.string.character_sheet__delete_dialog__title),
)
Text(
modifier = Modifier
.padding(horizontal = 24.dp),
style = MaterialTheme.typography.body1,
text = stringResource(
Res.string.character_sheet__delete_dialog__description,
dialog.name
),
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 4.dp),
horizontalArrangement = Arrangement.spacedBy(
space = 4.dp,
alignment = Alignment.End
)
) {
TextButton(
onClick = onDismissRequest,
) {
Text(
text = stringResource(Res.string.dialog__cancel_action)
)
}
TextButton(
onClick = { onConfirm(dialog) },
) {
Text(
text = stringResource(Res.string.dialog__confirm_action)
)
}
}
}
}
}
}
}

View file

@ -1,141 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.preview
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
@Composable
@Stable
fun rememberCharacterSheetPreview(): CharacterSheetPageUio {
return remember {
CharacterSheetPageUio(
id = "Koryas_Id",
name = "Koryas",
diminished = 0,
characteristics = listOf(
Characteristic(
id = CharacteristicId.STR,
label = "Force",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.DEX,
label = "Dexterité",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.CON,
label = "Constitution",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.HEI,
label = "Taille",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.INT,
label = "Intelligence",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.POW,
label = "Pouvoir",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.CHA,
label = "Charisme",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
),
subCharacteristics = listOf(
Characteristic(
id = CharacteristicId.MOV,
label = "Mouvement",
value = "10",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.HP,
label = "Point de vie",
value = "20/20",
tooltips = null,
editable = true,
roll = null,
),
Characteristic(
id = CharacteristicId.PP,
label = "Point de pouvoir",
value = "15/15",
tooltips = null,
editable = true,
roll = null,
),
Characteristic(
id = CharacteristicId.DMG,
label = "Domage bonus",
value = "1d4",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.ARMOR,
label = "Armure",
value = "0",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.LB,
label = "Apprentissage",
value = "0",
tooltips = null,
editable = false,
roll = null,
),
Characteristic(
id = CharacteristicId.GHP,
label = "Bonus de PV",
value = "5",
tooltips = null,
editable = false,
roll = null,
),
),
commonSkills = emptyList(),
specialSKills = emptyList(),
magicsSkills = emptyList(),
actions = emptyList(),
)
}
}

View file

@ -1,623 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import com.pixelized.desktop.lwa.ui.composable.checkbox.LwaCheckBoxUio
import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextFieldFlow
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.occupation
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.BaseSkillFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.LevelUpWrapperUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SimpleFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
import com.pixelized.desktop.lwa.utils.extention.unAccent
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__critical_action_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__default_action_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__description_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__name_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__spacial_action_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__cha
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__con
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__dex
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__hei
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__int
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__level
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__portrait
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__pow
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__str
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__thumbnail
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__level_up
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__name_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__acrobatics_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__acrobatics_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__acrobatics_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__aid_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__aid_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__aid_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__athletics_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__athletics_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__athletics_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bargain_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bargain_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bargain_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bonus_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__combat_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__combat_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__combat_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__discretion_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__discretion_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__discretion_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__dodge_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__dodge_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__dodge_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__empathy_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__empathy_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__empathy_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__grab_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__grab_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__grab_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__intimidation_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__intimidation_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__intimidation_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__level_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__perception_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__perception_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__perception_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__persuasion_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__persuasion_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__persuasion_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__search_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__search_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__search_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__sleight_of_hand_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__sleight_of_hand_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__sleight_of_hand_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__spiel_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__spiel_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__spiel_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw_base
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw_description
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw_label
import org.jetbrains.compose.resources.getString
import kotlin.math.max
class CharacterSheetEditFactory(
private val skillFieldFactory: SkillFieldFactory,
private val characterSheetUseCase: CharacterSheetUseCase,
) {
suspend fun updateCharacterSheet(
currentSheet: CharacterSheet?,
editedSheet: CharacterSheetEditPageUio,
): CharacterSheet {
val level = editedSheet.level.unpack()?.toIntOrNull()
?: currentSheet?.level
?: 1
val strength = editedSheet.strength.unpack()?.toIntOrNull()
?: currentSheet?.strength
?: 0
val dexterity = editedSheet.dexterity.unpack()?.toIntOrNull()
?: currentSheet?.dexterity
?: 0
val constitution = editedSheet.constitution.unpack()?.toIntOrNull()
?: currentSheet?.constitution
?: 0
val height = editedSheet.height.unpack()?.toIntOrNull()
?: currentSheet?.height
?: 0
val intelligence = editedSheet.intelligence.unpack()?.toIntOrNull()
?: currentSheet?.intelligence
?: 0
val power = editedSheet.power.unpack()?.toIntOrNull()
?: currentSheet?.power
?: 0
val charisma = editedSheet.charisma.unpack()?.toIntOrNull()
?: currentSheet?.charisma
?: 0
return CharacterSheet(
id = editedSheet.id.value,
name = editedSheet.name.value.value,
job = "",
portrait = editedSheet.portrait.value.value,
thumbnail = editedSheet.thumbnail.value.value,
externalLink = "",
level = level,
shouldLevelUp = editedSheet.levelUp.checked.value,
strength = strength,
dexterity = dexterity,
constitution = constitution,
height = height,
intelligence = intelligence,
power = power,
charisma = charisma,
damage = currentSheet?.damage ?: 0,
fatigue = currentSheet?.fatigue ?: 0,
diminished = currentSheet?.diminished ?: 0,
alterations = currentSheet?.alterations ?: emptyList(),
commonSkills = editedSheet.commonSkills.map { editedSkill ->
val currentSkill = currentSheet?.commonSkills?.firstOrNull {
it.id == editedSkill.id
}
CharacterSheet.Skill(
id = editedSkill.id,
label = editedSkill.label,
description = currentSkill?.description
?: commonSkillDescription(skillId = editedSkill.id),
base = currentSkill?.base
?: commonSkillBase(skillId = editedSkill.id),
bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() },
level = editedSkill.level.value.value.toIntOrNull() ?: 0,
occupation = editedSkill.option.checked.value,
used = currentSkill?.used ?: false,
)
},
specialSkills = editedSheet.specialSkills.map { editedSkill ->
val currentSkill = currentSheet?.specialSkills?.firstOrNull {
it.id == editedSkill.id
}
CharacterSheet.Skill(
id = editedSkill.id,
label = editedSkill.label.value.value,
description = editedSkill.description.value.value,
base = editedSkill.base.value.value,
bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() },
level = editedSkill.level.value.value.toIntOrNull() ?: 0,
occupation = editedSkill.options.occupation,
used = currentSkill?.used ?: false,
)
},
magicSkills = editedSheet.magicSkills.map { editedSkill ->
val currentSkill = currentSheet?.magicSkills?.firstOrNull {
it.id == editedSkill.id
}
CharacterSheet.Skill(
id = editedSkill.id,
label = editedSkill.label.value.value,
description = editedSkill.description.value.value,
base = editedSkill.base.value.value,
bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() },
level = editedSkill.level.value.value.toIntOrNull() ?: 0,
occupation = editedSkill.options.occupation,
used = currentSkill?.used ?: false,
)
},
actions = editedSheet.actions.map {
CharacterSheet.Roll(
id = it.id,
label = it.label.valueFlow.value,
description = it.description.valueFlow.value,
canBeCritical = it.canBeCritical.checked.value,
default = it.default.valueFlow.value,
special = it.special?.valueFlow?.value,
critical = it.critical?.valueFlow?.value,
)
},
tags = emptyList(),
)
}
suspend fun convertToUio(
sheet: CharacterSheet?,
onDeleteSkill: (id: String) -> Unit,
): CharacterSheetEditPageUio {
val str = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__str),
value = skillFieldFactory.createWrapper(
value = sheet?.strength?.toString() ?: "",
placeholder = mutableStateOf("10")
),
)
val dex = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__dex),
value = skillFieldFactory.createWrapper(
value = sheet?.dexterity?.toString() ?: "",
placeholder = mutableStateOf("10"),
),
)
val con = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__con),
value = skillFieldFactory.createWrapper(
value = sheet?.constitution?.toString() ?: "",
placeholder = mutableStateOf("10"),
)
)
val hei = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__hei),
value = skillFieldFactory.createWrapper(
value = sheet?.height?.toString() ?: "",
placeholder = mutableStateOf("10"),
)
)
val int = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__int),
value = skillFieldFactory.createWrapper(
value = sheet?.intelligence?.toString() ?: "",
placeholder = mutableStateOf("10"),
)
)
val pow = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__pow),
value = skillFieldFactory.createWrapper(
value = sheet?.power?.toString() ?: "",
placeholder = mutableStateOf("10"),
)
)
val cha = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__cha),
value = skillFieldFactory.createWrapper(
value = sheet?.charisma?.toString() ?: "",
placeholder = mutableStateOf("10"),
)
)
fun str(): Int = str.unpack()?.toIntOrNull() ?: 0
fun dex(): Int = dex.unpack()?.toIntOrNull() ?: 0
fun con(): Int = con.unpack()?.toIntOrNull() ?: 0
fun hei(): Int = hei.unpack()?.toIntOrNull() ?: 0
fun int(): Int = int.unpack()?.toIntOrNull() ?: 0
fun pow(): Int = pow.unpack()?.toIntOrNull() ?: 0
fun cha(): Int = cha.unpack()?.toIntOrNull() ?: 0
val specialSkillsLabel = getString(Res.string.character_sheet_edit__skills__special_title)
val magicSkillsLabel = getString(Res.string.character_sheet_edit__skills__magic_title)
val portrait = mutableStateOf(sheet?.portrait ?: "")
val thumbnail = mutableStateOf(sheet?.thumbnail ?: "")
return with(characterSheetUseCase) {
val name = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__name_label)),
value = sheet?.name ?: ""
)
CharacterSheetEditPageUio(
id = derivedStateOf {
sheet?.id ?: name.value.value.unAccent().replace(" ", "_")
},
levelUp = createLevelUpWrapper(shouldLevelUp = sheet?.shouldLevelUp ?: false),
name = name,
level = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__characteristics__level),
value = skillFieldFactory.createWrapper(
value = sheet?.level?.toString() ?: "",
placeholder = mutableStateOf("1"),
)
),
portrait = TextFieldWrapperUio(
enable = true,
label = mutableStateOf(getString(Res.string.character_sheet_edit__characteristics__portrait)),
value = portrait,
placeholder = mutableStateOf(null),
onValueChange = { portrait.value = it },
),
thumbnail = TextFieldWrapperUio(
enable = true,
label = mutableStateOf(getString(Res.string.character_sheet_edit__characteristics__thumbnail)),
value = thumbnail,
placeholder = mutableStateOf(null),
onValueChange = { thumbnail.value = it },
),
strength = str,
dexterity = dex,
constitution = con,
height = hei,
intelligence = int,
power = pow,
charisma = cha,
commonSkills = listOf(
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.COMBAT_ID,
label = getString(Res.string.character_sheet_edit__skills__combat_label),
description = getString(Res.string.character_sheet_edit__skills__combat_description),
base = derivedStateOf { normalize(dex() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.DODGE_ID,
label = getString(Res.string.character_sheet_edit__skills__dodge_label),
description = getString(Res.string.character_sheet_edit__skills__dodge_description),
base = derivedStateOf { normalize(dex() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.GRAB_ID,
label = getString(Res.string.character_sheet_edit__skills__grab_label),
description = getString(Res.string.character_sheet_edit__skills__grab_description),
base = derivedStateOf { normalize(str() + hei()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.THROW_ID,
label = getString(Res.string.character_sheet_edit__skills__throw_label),
description = getString(Res.string.character_sheet_edit__skills__throw_description),
base = derivedStateOf { normalize(str() + dex()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.ATHLETICS_ID,
label = getString(Res.string.character_sheet_edit__skills__athletics_label),
description = getString(Res.string.character_sheet_edit__skills__athletics_description),
base = derivedStateOf { normalize(str() + con() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.ACROBATICS_ID,
label = getString(Res.string.character_sheet_edit__skills__acrobatics_label),
description = getString(Res.string.character_sheet_edit__skills__acrobatics_description),
base = derivedStateOf { normalize(dex() + con() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.PERCEPTION_ID,
label = getString(Res.string.character_sheet_edit__skills__perception_label),
description = getString(Res.string.character_sheet_edit__skills__perception_description),
base = derivedStateOf { normalize(10 + int() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SEARCH_ID,
label = getString(Res.string.character_sheet_edit__skills__search_label),
description = getString(Res.string.character_sheet_edit__skills__search_description),
base = derivedStateOf { normalize(10 + int() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.EMPATHY_ID,
label = getString(Res.string.character_sheet_edit__skills__empathy_label),
description = getString(Res.string.character_sheet_edit__skills__empathy_description),
base = derivedStateOf { normalize(cha() + int()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.PERSUASION_ID,
label = getString(Res.string.character_sheet_edit__skills__persuasion_label),
description = getString(Res.string.character_sheet_edit__skills__persuasion_description),
base = derivedStateOf { normalize(cha() * 3) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.INTIMIDATION_ID,
label = getString(Res.string.character_sheet_edit__skills__intimidation_label),
description = getString(Res.string.character_sheet_edit__skills__intimidation_description),
base = derivedStateOf { normalize(cha() + max(pow(), hei()) * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SPIEL_ID,
label = getString(Res.string.character_sheet_edit__skills__spiel_label),
description = getString(Res.string.character_sheet_edit__skills__spiel_description),
base = derivedStateOf { normalize(cha() * 2 + int()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.BARGAIN_ID,
label = getString(Res.string.character_sheet_edit__skills__bargain_label),
description = getString(Res.string.character_sheet_edit__skills__bargain_description),
base = derivedStateOf { normalize(cha() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.DISCRETION_ID,
label = getString(Res.string.character_sheet_edit__skills__discretion_label),
description = getString(Res.string.character_sheet_edit__skills__discretion_description),
base = derivedStateOf { normalize(cha() + dex() * 2 - hei()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID,
label = getString(Res.string.character_sheet_edit__skills__sleight_of_hand_label),
description = getString(Res.string.character_sheet_edit__skills__sleight_of_hand_description),
base = derivedStateOf { normalize(dex() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.AID_ID,
label = getString(Res.string.character_sheet_edit__skills__aid_label),
description = getString(Res.string.character_sheet_edit__skills__aid_description),
base = derivedStateOf { normalize(int() + dex()) },
),
),
specialSkills = sheet?.specialSkills?.map { skill ->
skillFieldFactory.createSkill(
id = skill.id,
label = specialSkillsLabel,
descriptionValue = skill.description ?: "",
labelValue = skill.label,
baseValue = skill.base,
bonusValue = skill.bonus ?: "",
levelValue = skill.level.takeIf { it > 0 }?.toString() ?: "",
options = run {
val current = sheet.specialSkills.firstOrNull { it.id == skill.id }
listOf(
skillFieldFactory.occupationOption(current?.occupation ?: false),
skillFieldFactory.deleteOption { onDeleteSkill(skill.id) },
)
},
)
} ?: emptyList(),
magicSkills = sheet?.magicSkills?.map { skill ->
skillFieldFactory.createSkill(
id = skill.id,
label = magicSkillsLabel,
descriptionValue = skill.description ?: "",
labelValue = skill.label,
baseValue = skill.base,
bonusValue = skill.bonus ?: "",
levelValue = skill.level.takeIf { it > 0 }?.toString() ?: "",
options = run {
val current = sheet.magicSkills.firstOrNull { it.id == skill.id }
listOf(
skillFieldFactory.occupationOption(
checked = current?.occupation ?: false
),
skillFieldFactory.deleteOption { onDeleteSkill(skill.id) },
)
},
)
} ?: emptyList(),
actions = sheet?.actions?.map { action ->
ActionFieldUio(
id = action.id,
label = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__name_label),
value = action.label,
).createLwaTextField(),
description = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__description_label),
value = action.description ?: "",
).createLwaTextField(),
canBeCritical = createLwaBox(
checked = action.canBeCritical,
),
default = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__default_action_label),
value = action.default,
).createLwaTextField(),
special = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__spacial_action_label),
value = action.special ?: "",
).createLwaTextField(),
critical = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__critical_action_label),
value = action.critical ?: "",
).createLwaTextField(),
option = skillFieldFactory.deleteOption { onDeleteSkill(action.id) },
)
} ?: emptyList(),
)
}
}
fun createLwaBox(
enable: Boolean = true,
checked: Boolean,
): LwaCheckBoxUio {
val checkedFlow = MutableStateFlow(checked)
return LwaCheckBoxUio(
enable = enable,
checked = checkedFlow,
onCheckedChange = { checkedFlow.value = it },
)
}
private suspend fun createLevelUpWrapper(
shouldLevelUp: Boolean,
): LevelUpWrapperUio {
val field = mutableStateOf(shouldLevelUp)
return LevelUpWrapperUio(
label = getString(Res.string.character_sheet_edit__level_up),
checked = field,
onCheck = { field.value = it },
)
}
private suspend fun createBaseSkill(
sheet: CharacterSheet?,
id: String,
label: String,
description: String,
base: State<Int>,
): BaseSkillFieldUio {
val skill = sheet?.commonSkills?.firstOrNull { it.id == id }
return BaseSkillFieldUio(
id = id,
label = label,
base = base,
bonus = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__skills__bonus_label)),
value = skill?.bonus ?: "",
),
level = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__skills__level_label)),
value = skill?.level?.takeIf { it > 0 }?.toString() ?: "",
),
option = skillFieldFactory.occupationOption(skill?.occupation ?: false),
tooltip = BasicTooltipUio(
title = label,
description = description,
)
)
}
private fun SimpleFieldUio.unpack(): String? {
return value.value.value.ifBlank { value.placeholder.value }
}
private suspend fun commonSkillBase(skillId: String): String {
return when (skillId) {
CharacterSheet.CommonSkillId.COMBAT_ID -> getString(Res.string.character_sheet_edit__skills__combat_base)
CharacterSheet.CommonSkillId.DODGE_ID -> getString(Res.string.character_sheet_edit__skills__dodge_base)
CharacterSheet.CommonSkillId.GRAB_ID -> getString(Res.string.character_sheet_edit__skills__grab_base)
CharacterSheet.CommonSkillId.THROW_ID -> getString(Res.string.character_sheet_edit__skills__throw_base)
CharacterSheet.CommonSkillId.ATHLETICS_ID -> getString(Res.string.character_sheet_edit__skills__athletics_base)
CharacterSheet.CommonSkillId.ACROBATICS_ID -> getString(Res.string.character_sheet_edit__skills__acrobatics_base)
CharacterSheet.CommonSkillId.PERCEPTION_ID -> getString(Res.string.character_sheet_edit__skills__perception_base)
CharacterSheet.CommonSkillId.SEARCH_ID -> getString(Res.string.character_sheet_edit__skills__search_base)
CharacterSheet.CommonSkillId.EMPATHY_ID -> getString(Res.string.character_sheet_edit__skills__empathy_base)
CharacterSheet.CommonSkillId.PERSUASION_ID -> getString(Res.string.character_sheet_edit__skills__persuasion_base)
CharacterSheet.CommonSkillId.INTIMIDATION_ID -> getString(Res.string.character_sheet_edit__skills__intimidation_base)
CharacterSheet.CommonSkillId.SPIEL_ID -> getString(Res.string.character_sheet_edit__skills__spiel_base)
CharacterSheet.CommonSkillId.BARGAIN_ID -> getString(Res.string.character_sheet_edit__skills__bargain_base)
CharacterSheet.CommonSkillId.DISCRETION_ID -> getString(Res.string.character_sheet_edit__skills__discretion_base)
CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID -> getString(Res.string.character_sheet_edit__skills__sleight_of_hand_base)
CharacterSheet.CommonSkillId.AID_ID -> getString(Res.string.character_sheet_edit__skills__aid_base)
else -> ""
}
}
private suspend fun commonSkillLabel(skillId: String): String {
return when (skillId) {
CharacterSheet.CommonSkillId.COMBAT_ID -> getString(Res.string.character_sheet_edit__skills__combat_label)
CharacterSheet.CommonSkillId.DODGE_ID -> getString(Res.string.character_sheet_edit__skills__dodge_label)
CharacterSheet.CommonSkillId.GRAB_ID -> getString(Res.string.character_sheet_edit__skills__grab_label)
CharacterSheet.CommonSkillId.THROW_ID -> getString(Res.string.character_sheet_edit__skills__throw_label)
CharacterSheet.CommonSkillId.ATHLETICS_ID -> getString(Res.string.character_sheet_edit__skills__athletics_label)
CharacterSheet.CommonSkillId.ACROBATICS_ID -> getString(Res.string.character_sheet_edit__skills__acrobatics_label)
CharacterSheet.CommonSkillId.PERCEPTION_ID -> getString(Res.string.character_sheet_edit__skills__perception_label)
CharacterSheet.CommonSkillId.SEARCH_ID -> getString(Res.string.character_sheet_edit__skills__search_label)
CharacterSheet.CommonSkillId.EMPATHY_ID -> getString(Res.string.character_sheet_edit__skills__empathy_label)
CharacterSheet.CommonSkillId.PERSUASION_ID -> getString(Res.string.character_sheet_edit__skills__persuasion_label)
CharacterSheet.CommonSkillId.INTIMIDATION_ID -> getString(Res.string.character_sheet_edit__skills__intimidation_label)
CharacterSheet.CommonSkillId.SPIEL_ID -> getString(Res.string.character_sheet_edit__skills__spiel_label)
CharacterSheet.CommonSkillId.BARGAIN_ID -> getString(Res.string.character_sheet_edit__skills__bargain_label)
CharacterSheet.CommonSkillId.DISCRETION_ID -> getString(Res.string.character_sheet_edit__skills__discretion_label)
CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID -> getString(Res.string.character_sheet_edit__skills__sleight_of_hand_label)
CharacterSheet.CommonSkillId.AID_ID -> getString(Res.string.character_sheet_edit__skills__aid_label)
else -> ""
}
}
private suspend fun commonSkillDescription(skillId: String): String {
return when (skillId) {
CharacterSheet.CommonSkillId.COMBAT_ID -> getString(Res.string.character_sheet_edit__skills__combat_description)
CharacterSheet.CommonSkillId.DODGE_ID -> getString(Res.string.character_sheet_edit__skills__dodge_description)
CharacterSheet.CommonSkillId.GRAB_ID -> getString(Res.string.character_sheet_edit__skills__grab_description)
CharacterSheet.CommonSkillId.THROW_ID -> getString(Res.string.character_sheet_edit__skills__throw_description)
CharacterSheet.CommonSkillId.ATHLETICS_ID -> getString(Res.string.character_sheet_edit__skills__athletics_description)
CharacterSheet.CommonSkillId.ACROBATICS_ID -> getString(Res.string.character_sheet_edit__skills__acrobatics_description)
CharacterSheet.CommonSkillId.PERCEPTION_ID -> getString(Res.string.character_sheet_edit__skills__perception_description)
CharacterSheet.CommonSkillId.SEARCH_ID -> getString(Res.string.character_sheet_edit__skills__search_description)
CharacterSheet.CommonSkillId.EMPATHY_ID -> getString(Res.string.character_sheet_edit__skills__empathy_description)
CharacterSheet.CommonSkillId.PERSUASION_ID -> getString(Res.string.character_sheet_edit__skills__persuasion_description)
CharacterSheet.CommonSkillId.INTIMIDATION_ID -> getString(Res.string.character_sheet_edit__skills__intimidation_description)
CharacterSheet.CommonSkillId.SPIEL_ID -> getString(Res.string.character_sheet_edit__skills__spiel_description)
CharacterSheet.CommonSkillId.BARGAIN_ID -> getString(Res.string.character_sheet_edit__skills__bargain_description)
CharacterSheet.CommonSkillId.DISCRETION_ID -> getString(Res.string.character_sheet_edit__skills__discretion_description)
CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID -> getString(Res.string.character_sheet_edit__skills__sleight_of_hand_description)
CharacterSheet.CommonSkillId.AID_ID -> getString(Res.string.character_sheet_edit__skills__aid_description)
else -> ""
}
}
}

View file

@ -1,477 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
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.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindow
import com.pixelized.desktop.lwa.ui.screen.characterSheet.copy.CharacterSheetCopyDialog
import com.pixelized.desktop.lwa.ui.screen.characterSheet.delete.CharacterSheetDeleteDialog
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionField
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.BaseSkillFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.BaseSkillForm
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.LevelUpField
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.LevelUpWrapperUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SimpleField
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SimpleFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SkillFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SkillForm
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapper
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__add_roll_action
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__save_action
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__magic_action
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_action
import lwacharactersheet.composeapp.generated.resources.ic_delete_forever_24dp
import lwacharactersheet.composeapp.generated.resources.ic_save_24dp
import lwacharactersheet.composeapp.generated.resources.ic_save_as_24dp
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
@Stable
data class CharacterSheetEditPageUio(
val id: State<String>,
val levelUp: LevelUpWrapperUio,
val name: TextFieldWrapperUio,
val level: SimpleFieldUio,
val portrait: TextFieldWrapperUio,
val thumbnail: TextFieldWrapperUio,
val strength: SimpleFieldUio,
val dexterity: SimpleFieldUio,
val constitution: SimpleFieldUio,
val height: SimpleFieldUio,
val intelligence: SimpleFieldUio,
val power: SimpleFieldUio,
val charisma: SimpleFieldUio,
val commonSkills: List<BaseSkillFieldUio>,
val specialSkills: List<SkillFieldUio>,
val magicSkills: List<SkillFieldUio>,
val actions: List<ActionFieldUio>,
) {
val characteristics
get() = listOf(
strength,
dexterity,
constitution,
height,
intelligence,
power,
charisma,
)
}
@Composable
fun CharacterSheetEditPage(
viewModel: CharacterSheetEditViewModel = koinViewModel(),
) {
val windowController = LocalWindowController.current
val window = LocalWindow.current
val screen = LocalScreenController.current
val scope = rememberCoroutineScope()
Surface(
modifier = Modifier.fillMaxSize(),
) {
CharacterSheetEdit(
title = window.title,
form = viewModel.characterSheet.value,
onNewSpecialSkill = {
scope.launch {
viewModel.onNewSpecialSkill()
}
},
onNewMagicSkill = {
scope.launch {
viewModel.onNewMagicSkill()
}
},
onNewAction = {
scope.launch {
viewModel.onNewAction()
}
},
onDelete = {
scope.launch {
viewModel.showDeleteCharacterSheetDialog()
}
},
onCopy = {
scope.launch {
viewModel.showCopyCharacterSheetDialog()
}
},
onSave = {
scope.launch {
viewModel.save()
if (screen.popBackStack().not()) {
windowController.hideWindow(window = window)
}
}
},
)
}
CharacterSheetCopyDialog(
dialog = viewModel.copyCharacterSheetDialog,
onConfirm = { dialog ->
scope.launch {
val characterSheetId = dialog.value.valueFlow.value
viewModel.saveAs(characterSheetId = characterSheetId)
if (screen.popBackStack().not()) {
windowController.hideWindow(window = window)
}
}
},
onDismissRequest = {
viewModel.hideCopyCharacterSheetDialog()
}
)
CharacterSheetDeleteDialog(
dialog = viewModel.deleteCharacterSheetDialog,
onConfirm = { dialog ->
scope.launch {
viewModel.delete(characterSheetId = dialog.characterSheetId)
if (screen.popBackStack().not()) {
windowController.hideWindow(window = window)
}
}
},
onDismissRequest = {
viewModel.hideDeleteCharacterSheetDialog()
}
)
}
@Composable
fun CharacterSheetEdit(
modifier: Modifier = Modifier,
title: String,
form: CharacterSheetEditPageUio,
onNewSpecialSkill: () -> Unit,
onNewMagicSkill: () -> Unit,
onNewAction: () -> Unit,
onDelete: () -> Unit,
onCopy: () -> Unit,
onSave: () -> Unit,
) {
Scaffold(
modifier = modifier,
topBar = {
TopAppBar(
title = {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = title,
)
},
actions = {
IconButton(
onClick = onDelete,
) {
Icon(
painter = painterResource(Res.drawable.ic_delete_forever_24dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
IconButton(
onClick = onCopy,
) {
Icon(
painter = painterResource(Res.drawable.ic_save_as_24dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
IconButton(
onClick = onSave,
) {
Icon(
painter = painterResource(Res.drawable.ic_save_24dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
},
)
},
content = { paddingValues ->
Column(
modifier = Modifier
.verticalScroll(state = rememberScrollState())
.padding(paddingValues = paddingValues)
.padding(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
) {
Text(
style = MaterialTheme.lwa.typography.system.caption,
text = "id : "
)
SelectionContainer {
Text(
style = MaterialTheme.lwa.typography.system.caption,
text = form.id.value,
)
}
}
TextFieldWrapper(
modifier = Modifier.fillMaxWidth(),
wrapper = form.name,
)
TextFieldWrapper(
modifier = Modifier.fillMaxWidth(),
wrapper = form.portrait,
)
TextFieldWrapper(
modifier = Modifier.fillMaxWidth(),
wrapper = form.thumbnail,
)
Row(
verticalAlignment = Alignment.CenterVertically,
) {
LevelUpField(
modifier = Modifier.weight(1f).offset(y = 4.dp),
field = form.levelUp,
)
SimpleField(
modifier = Modifier.weight(2f),
field = form.level,
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
DecoratedBox(
modifier = Modifier.weight(weight = 1f),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.caption,
text = stringResource(Res.string.character_sheet_edit__characteristics__title),
)
form.characteristics.forEach {
SimpleField(
modifier = Modifier.fillMaxWidth(),
field = it,
)
}
}
}
}
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.caption,
text = stringResource(Res.string.character_sheet__skills__common_title),
)
form.commonSkills.forEach {
BaseSkillForm(
modifier = Modifier
.fillMaxWidth()
.padding(end = 4.dp),
field = it,
)
}
}
}
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.caption,
text = stringResource(Res.string.character_sheet__skills__special_title),
)
form.specialSkills.forEach {
SkillForm(
modifier = Modifier
.fillMaxWidth()
.padding(end = 4.dp),
field = it,
)
}
TextButton(
modifier = Modifier.align(alignment = Alignment.End),
onClick = onNewSpecialSkill,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(Res.string.character_sheet_edit__skills__special_action),
)
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
)
}
}
}
}
DecoratedBox(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.caption,
text = stringResource(Res.string.character_sheet__skills__magic_title),
)
form.magicSkills.forEach {
SkillForm(
modifier = Modifier
.fillMaxWidth()
.padding(end = 4.dp),
field = it,
)
}
TextButton(
modifier = Modifier.align(alignment = Alignment.End),
onClick = onNewMagicSkill,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(Res.string.character_sheet_edit__skills__magic_action),
)
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
)
}
}
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
) {
form.actions.forEach {
ActionField(
modifier = Modifier
.fillMaxWidth()
.padding(end = 6.dp, bottom = 16.dp),
action = it,
)
}
TextButton(
modifier = Modifier.align(alignment = Alignment.End),
onClick = onNewAction,
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(Res.string.character_sheet_edit__add_roll_action)
)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
TextButton(
onClick = onSave,
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(Res.string.character_sheet_edit__save_action),
)
}
}
}
}
)
}

View file

@ -1,205 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextFieldFlow
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination
import com.pixelized.desktop.lwa.ui.screen.characterSheet.copy.CharacterSheetCopyDialogUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.delete.CharacterSheetDeleteDialogUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionFieldUio
import kotlinx.coroutines.runBlocking
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__critical_action_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__default_action_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__description_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__name_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__spacial_action_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__copy__label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__copy__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_title
import org.jetbrains.compose.resources.getString
import java.util.UUID
class CharacterSheetEditViewModel(
private val characterSheetRepository: CharacterSheetRepository,
private val sheetFactory: CharacterSheetEditFactory,
private val skillFactory: SkillFieldFactory,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val argument = CharacterSheetEditDestination.Argument(savedStateHandle)
private val copyDialog = mutableStateOf<CharacterSheetCopyDialogUio?>(null)
val copyCharacterSheetDialog: State<CharacterSheetCopyDialogUio?> = copyDialog
private val deleteDialog = mutableStateOf<CharacterSheetDeleteDialogUio?>(null)
val deleteCharacterSheetDialog: State<CharacterSheetDeleteDialogUio?> = deleteDialog
private val _characterSheet = mutableStateOf(
runBlocking {
sheetFactory.convertToUio(
sheet = characterSheetRepository.characterDetail(characterSheetId = argument.id),
onDeleteSkill = ::deleteSkill,
)
}
)
val characterSheet: State<CharacterSheetEditPageUio> get() = _characterSheet
suspend fun showCopyCharacterSheetDialog() {
val idFieldFlow = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__copy__label),
value = argument.id ?: "",
)
copyDialog.value = CharacterSheetCopyDialogUio(
label = getString(Res.string.character_sheet_edit__copy__title),
value = idFieldFlow.createLwaTextField(),
validate = {
characterSheetRepository.checkCharacterSheetIdValidity(
characterSheetId = idFieldFlow.valueFlow.value
).also {
idFieldFlow.errorFlow.value = it.not()
}
}
)
}
fun hideCopyCharacterSheetDialog() {
copyDialog.value = null
}
fun showDeleteCharacterSheetDialog() {
deleteDialog.value = CharacterSheetDeleteDialogUio(
characterSheetId = argument.id ?: "",
name = characterSheet.value.name.value.value,
)
}
fun hideDeleteCharacterSheetDialog() {
deleteDialog.value = null
}
suspend fun onNewSpecialSkill() {
val id = UUID.randomUUID().toString()
val skill = skillFactory.createSkill(
id = id,
label = getString(Res.string.character_sheet_edit__skills__special_title),
options = listOf(
skillFactory.occupationOption(checked = true),
skillFactory.deleteOption { deleteSkill(skillId = id) }
),
)
val skills = _characterSheet.value.specialSkills
.toMutableList()
.also { it.add(skill) }
_characterSheet.value = characterSheet.value.copy(
specialSkills = skills,
)
}
suspend fun onNewMagicSkill() {
val id = UUID.randomUUID().toString()
val skill = skillFactory.createSkill(
id = id,
label = getString(Res.string.character_sheet_edit__skills__magic_title),
options = listOf(
skillFactory.occupationOption(checked = false),
skillFactory.deleteOption { deleteSkill(skillId = id) }
),
)
val skills = _characterSheet.value.magicSkills
.toMutableList()
.also { it.add(skill) }
_characterSheet.value = characterSheet.value.copy(
magicSkills = skills,
)
}
suspend fun onNewAction() {
val id = UUID.randomUUID().toString()
val field = ActionFieldUio(
id = id,
label = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__name_label),
value = "",
).createLwaTextField(),
description = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__description_label),
value = "",
).createLwaTextField(),
canBeCritical = sheetFactory.createLwaBox(
checked = false,
),
default = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__default_action_label),
value = "",
).createLwaTextField(),
special = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__spacial_action_label),
value = "",
).createLwaTextField(),
critical = createLwaTextFieldFlow(
label = getString(Res.string.character_sheet_edit__actions__critical_action_label),
value = "",
).createLwaTextField(),
option = skillFactory.deleteOption { deleteSkill(id) },
)
val actions = _characterSheet.value.actions.toMutableList().also {
it.add(field)
}
_characterSheet.value = _characterSheet.value.copy(
actions = actions,
)
}
private fun deleteSkill(skillId: String) {
_characterSheet.value = _characterSheet.value.copy(
specialSkills = _characterSheet.value.specialSkills.toMutableList().also { skills ->
skills.removeIf { it.id == skillId }
},
magicSkills = _characterSheet.value.magicSkills.toMutableList().also { skills ->
skills.removeIf { it.id == skillId }
},
actions = _characterSheet.value.actions.toMutableList().also { actions ->
actions.removeIf { it.id == skillId }
},
)
}
suspend fun save() {
val updatedSheet = sheetFactory.updateCharacterSheet(
currentSheet = characterSheetRepository.characterDetail(characterSheetId = _characterSheet.value.id.value),
editedSheet = _characterSheet.value,
)
characterSheetRepository.updateCharacter(
sheet = updatedSheet,
create = argument.id == null,
)
}
suspend fun saveAs(
characterSheetId: String,
) {
val updatedSheet = sheetFactory.updateCharacterSheet(
currentSheet = characterSheetRepository.characterDetail(characterSheetId = _characterSheet.value.id.value),
editedSheet = _characterSheet.value,
)
characterSheetRepository.updateCharacter(
sheet = updatedSheet.copy(id = characterSheetId),
create = true,
)
}
suspend fun delete(
characterSheetId: String,
) {
characterSheetRepository.deleteCharacter(
characterSheetId = characterSheetId
)
}
}

View file

@ -1,101 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SkillFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.ActionOption
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.CheckedOption
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.OptionUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__delete__label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__occupation__label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__base_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bonus_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__description_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__level_label
import org.jetbrains.compose.resources.getString
import java.util.UUID
class SkillFieldFactory {
suspend fun createSkill(
id: String = UUID.randomUUID().toString(),
label: String,
labelValue: String = "",
descriptionValue: String = "",
baseValue: String = "",
bonusValue: String = "",
levelValue: String = "",
options: List<OptionUio> = emptyList(),
): SkillFieldUio {
val baseLabel = getString(Res.string.character_sheet_edit__skills__base_label)
return SkillFieldUio(
id = id,
label = createWrapper(
label = mutableStateOf(label),
value = labelValue,
),
description = createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__skills__description_label)),
value = descriptionValue,
),
base = createWrapper(
label = derivedStateOf {
options
.firstOrNull { it is CheckedOption && it.checked.value }
?.let { "$baseLabel *" }
?: baseLabel
},
value = baseValue,
),
bonus = createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__skills__bonus_label)),
value = bonusValue,
),
level = createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__skills__level_label)),
value = levelValue,
),
options = options,
)
}
fun createWrapper(
enable: Boolean = true,
label: State<String?> = mutableStateOf(null),
placeholder: State<String?> = mutableStateOf(null),
value: String = "",
): TextFieldWrapperUio {
val state = mutableStateOf(value)
return TextFieldWrapperUio(
enable = enable,
label = label,
value = state,
placeholder = placeholder,
onValueChange = { state.value = it },
)
}
suspend fun deleteOption(onDelete: () -> Unit) = ActionOption.DeleteOptionUio(
icon = Icons.Default.Delete,
label = getString(Res.string.character_sheet_edit__delete__label),
onOption = onDelete,
)
suspend fun occupationOption(checked: Boolean) = mutableStateOf(checked).let { state ->
CheckedOption.OccupationOption(
checked = state,
label = getString(Res.string.character_sheet_edit__occupation__label),
onOption = { state.value = state.value.not() },
)
}
}
val List<OptionUio>.occupation: Boolean
get() = this.firstNotNullOfOrNull {
if (it is CheckedOption.OccupationOption) it.checked.value else null
} ?: false

View file

@ -1,118 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.DropdownMenu
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.checkbox.LwaCheckBox
import com.pixelized.desktop.lwa.ui.composable.checkbox.LwaCheckBoxUio
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.ActionOption
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.DropDownActionMenuItem
@Stable
data class ActionFieldUio(
val id: String,
val label: LwaTextFieldUio,
val description: LwaTextFieldUio,
val canBeCritical: LwaCheckBoxUio,
val default: LwaTextFieldUio,
val special: LwaTextFieldUio?,
val critical: LwaTextFieldUio?,
val option: ActionOption.DeleteOptionUio,
)
@Composable
fun ActionField(
modifier: Modifier = Modifier,
space: Dp = 4.dp,
action: ActionFieldUio,
) {
val showMenu = remember { mutableStateOf(false) }
val canBeCritical = action.canBeCritical.checked.collectAsState()
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(space = space),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(space = space),
verticalAlignment = Alignment.CenterVertically,
) {
LwaTextField(
modifier = Modifier.weight(1f),
field = action.label,
)
LwaCheckBox(
field = action.canBeCritical,
)
IconButton(
onClick = { showMenu.value = showMenu.value.not() },
) {
Icon(
imageVector = Icons.Default.MoreVert,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false }
) {
DropDownActionMenuItem(
wrapper = action.option,
onClick = {
showMenu.value = false
action.option.onOption()
}
)
}
}
}
Row(
modifier = Modifier.fillMaxWidth().animateContentSize(),
horizontalArrangement = Arrangement.spacedBy(space = space),
) {
LwaTextField(
modifier = Modifier.weight(1f),
field = action.default,
)
if (canBeCritical.value) {
action.special?.let { field ->
LwaTextField(
modifier = Modifier.weight(1f),
field = field,
)
}
action.critical?.let { field ->
LwaTextField(
modifier = Modifier.weight(1f),
field = field,
)
}
}
}
LwaTextField(
modifier = Modifier.fillMaxWidth(),
singleLine = false,
field = action.description,
)
}
}

View file

@ -1,137 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable
import androidx.compose.foundation.ExperimentalFoundationApi
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.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.ContentAlpha
import androidx.compose.material.DropdownMenu
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipLayout
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.CheckedOption
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.DropDownCheckedMenuItem
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapper
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__base_label
import org.jetbrains.compose.resources.stringResource
@Stable
class BaseSkillFieldUio(
val id: String,
val label: String,
val base: State<Int>,
val bonus: TextFieldWrapperUio,
val level: TextFieldWrapperUio,
val option: CheckedOption,
val tooltip: BasicTooltipUio,
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BaseSkillForm(
modifier: Modifier = Modifier,
field: BaseSkillFieldUio,
) {
val showMenu = remember { mutableStateOf(false) }
val baseLabel = stringResource(Res.string.character_sheet_edit__skills__base_label)
.let { label ->
remember {
derivedStateOf {
when (field.option.checked.value) {
true -> "$label *"
else -> label
}
}
}
}
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.End),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp, bottom = 12.dp)
.align(alignment = Alignment.Bottom),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = field.label,
)
BasicTooltipLayout(
modifier = Modifier
.width(width = 96.dp)
.padding(start = 16.dp),
tooltip = field.tooltip,
) {
Column {
Text(
style = MaterialTheme.typography.caption,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
text = baseLabel.value,
)
Text(
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "+${field.base.value}",
)
}
}
TextFieldWrapper(
modifier = Modifier.width(width = 96.dp),
wrapper = field.bonus,
)
TextFieldWrapper(
modifier = Modifier.width(width = 96.dp),
wrapper = field.level,
)
Box {
IconButton(
onClick = { showMenu.value = showMenu.value.not() },
) {
Icon(
imageVector = Icons.Default.MoreVert,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false }
) {
DropDownCheckedMenuItem(
wrapper = field.option,
onClick = {
showMenu.value = false
field.option.onOption()
}
)
}
}
}
}

View file

@ -1,41 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaCheckboxColors
import com.pixelized.desktop.lwa.ui.theme.lwa
@Stable
class LevelUpWrapperUio(
val label: String,
val checked: State<Boolean>,
val onCheck: (Boolean) -> Unit,
)
@Composable
fun LevelUpField(
modifier: Modifier = Modifier,
field: LevelUpWrapperUio,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
style = MaterialTheme.typography.body1,
text = field.label,
)
Checkbox(
colors = LwaCheckboxColors(),
checked = field.checked.value,
onCheckedChange = field.onCheck,
)
}
}

View file

@ -1,49 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapper
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
@Stable
class SimpleFieldUio(
val label: String,
val value: TextFieldWrapperUio,
)
@Composable
fun SimpleField(
modifier: Modifier = Modifier,
field: SimpleFieldUio,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.End),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp, bottom = 12.dp)
.align(alignment = Alignment.Bottom),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = field.label,
)
TextFieldWrapper(
modifier = Modifier.width(width = 96.dp),
wrapper = field.value,
)
}
}

View file

@ -1,106 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable
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.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material.DropdownMenu
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.DropDownMenuItemWrapper
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option.OptionUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapper
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
@Stable
class SkillFieldUio(
val id: String,
val label: TextFieldWrapperUio,
val description: TextFieldWrapperUio,
val base: TextFieldWrapperUio,
val bonus: TextFieldWrapperUio,
val level: TextFieldWrapperUio,
val options: List<OptionUio>,
)
@Composable
fun SkillForm(
modifier: Modifier = Modifier,
field: SkillFieldUio,
) {
val showMenu = remember { mutableStateOf(false) }
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(space = 4.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
TextFieldWrapper(
modifier = Modifier.weight(1f),
wrapper = field.label,
)
TextFieldWrapper(
modifier = Modifier.width(width = 96.dp),
wrapper = field.base,
)
TextFieldWrapper(
modifier = Modifier.width(width = 96.dp),
wrapper = field.bonus,
)
TextFieldWrapper(
modifier = Modifier.width(width = 96.dp),
wrapper = field.level,
)
}
TextFieldWrapper(
modifier = Modifier.fillMaxWidth(),
singleLine = false,
wrapper = field.description,
)
}
if (field.options.isNotEmpty()) {
Box {
IconButton(
onClick = { showMenu.value = showMenu.value.not() },
) {
Icon(
imageVector = Icons.Default.MoreVert,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
DropdownMenu(
expanded = showMenu.value,
onDismissRequest = { showMenu.value = false }
) {
field.options.forEach {
DropDownMenuItemWrapper(
wrapper = it,
onClick = {
showMenu.value = false
it.onOption()
}
)
}
}
}
}
}
}

View file

@ -1,51 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option
import androidx.compose.foundation.layout.padding
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
@Stable
sealed class ActionOption(
val icon: ImageVector,
val label: String,
onOption: () -> Unit,
) : OptionUio(onOption = onOption) {
class DeleteOptionUio(
icon: ImageVector,
label: String,
onOption: () -> Unit,
) : ActionOption(
icon = icon,
label = label,
onOption = onOption,
)
}
@Composable
fun DropDownActionMenuItem(
modifier: Modifier = Modifier,
wrapper: ActionOption,
onClick: () -> Unit,
) {
DropdownMenuItem(
modifier = modifier,
onClick = onClick,
) {
Icon(
tint = MaterialTheme.colors.primary,
imageVector = wrapper.icon,
contentDescription = null,
)
Text(
modifier = Modifier.padding(start = 8.dp),
text = wrapper.label,
)
}
}

View file

@ -1,56 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Checkbox
import androidx.compose.material.CheckboxDefaults
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Stable
sealed class CheckedOption(
val label: String,
val checked: State<Boolean>,
onOption: () -> Unit,
) : OptionUio(onOption = onOption) {
class OccupationOption(
label: String,
checked: State<Boolean>,
onOption: () -> Unit,
) : CheckedOption(
label = label,
checked = checked,
onOption = onOption,
)
}
@Composable
fun DropDownCheckedMenuItem(
modifier: Modifier = Modifier,
wrapper: CheckedOption,
onClick: () -> Unit,
) {
DropdownMenuItem(
modifier = modifier,
onClick = onClick,
) {
Checkbox(
modifier = Modifier.size(size = 24.dp),
checked = wrapper.checked.value,
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colors.primary,
),
onCheckedChange = null
)
Text(
modifier = Modifier.padding(start = 8.dp),
text = wrapper.label,
)
}
}

View file

@ -1,31 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.option
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
@Stable
sealed class OptionUio(
val onOption: () -> Unit,
)
@Composable
fun DropDownMenuItemWrapper(
modifier: Modifier = Modifier,
wrapper: OptionUio,
onClick: () -> Unit,
) {
when (wrapper) {
is ActionOption -> DropDownActionMenuItem(
modifier = modifier,
wrapper = wrapper,
onClick = onClick,
)
is CheckedOption -> DropDownCheckedMenuItem(
modifier = modifier,
wrapper = wrapper,
onClick = onClick,
)
}
}

View file

@ -1,79 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.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.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.utils.rememberKeyboardActions
import kotlin.math.sin
@Stable
data class TextFieldWrapperUio(
val enable: Boolean,
val label: State<String?>,
val value: State<String>,
val placeholder: State<String?>,
val onValueChange: (String) -> Unit,
)
@Composable
fun TextFieldWrapper(
modifier: Modifier = Modifier,
singleLine: Boolean = true,
wrapper: TextFieldWrapperUio,
) {
val colorScheme = MaterialTheme.colors
val focus = LocalFocusManager.current
val localModifier = if (singleLine) {
Modifier.height(height = 56.dp)
} else {
Modifier
}
TextField(
modifier = localModifier.then(other = modifier),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = remember(wrapper.enable) {
when (wrapper.enable) {
true -> colorScheme.onSurface.copy(alpha = 0.03f)
else -> colorScheme.surface
}
},
),
keyboardActions = rememberKeyboardActions {
focus.moveFocus(FocusDirection.Next)
},
enabled = wrapper.enable,
singleLine = singleLine,
placeholder = wrapper.placeholder.value?.let {
{
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = it
)
}
},
label = wrapper.label.value?.let {
{
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = it
)
}
},
onValueChange = { wrapper.onValueChange(it) },
value = wrapper.value.value,
)
}