Add the old ui to the new one.
This commit is contained in:
parent
d8ce46fe43
commit
f51a83cf6e
38 changed files with 635 additions and 250 deletions
|
|
@ -1,5 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="app_name">La table de Lwa</string>
|
||||
|
||||
<string name="dialog__confirm_action">Confirmer</string>
|
||||
<string name="dialog__cancel_action">Annuler</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -42,17 +42,21 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheet
|
|||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignScreen
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.app_name
|
||||
import lwacharactersheet.composeapp.generated.resources.network__connect__message
|
||||
import lwacharactersheet.composeapp.generated.resources.network__disconnect__message
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
val LocalWindowController = compositionLocalOf<WindowController> {
|
||||
error("Local Window Controller is not yet ready")
|
||||
|
|
@ -84,11 +88,7 @@ fun ApplicationScope.App() {
|
|||
) {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
// state = rememberWindowState(
|
||||
// width = 320.dp + 64.dp,
|
||||
// height = 900.dp,
|
||||
// ),
|
||||
title = "LwaCharacterSheet",
|
||||
title = runBlocking { getString(Res.string.app_name) },
|
||||
onKeyEvent = { event ->
|
||||
keyEventHandlers.reversed().any { it(event) }
|
||||
},
|
||||
|
|
@ -125,8 +125,7 @@ fun ApplicationScope.App() {
|
|||
}
|
||||
},
|
||||
content = {
|
||||
// MainNavHost()
|
||||
CampaignScreen()
|
||||
MainScreen()
|
||||
}
|
||||
)
|
||||
NetworkSnackHandler(
|
||||
|
|
@ -141,6 +140,19 @@ fun ApplicationScope.App() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainScreen(
|
||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
networkViewModel.connect()
|
||||
campaignViewModel.init()
|
||||
}
|
||||
|
||||
MainNavHost()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WindowsHandler(
|
||||
windowController: WindowController,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
|||
import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.HttpClientEngine
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
|||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -90,7 +90,7 @@ class AlterationStore(
|
|||
when (val payload = message.value) {
|
||||
is RestSynchronisation.ToggleActiveAlteration -> {
|
||||
setActiveAlteration(
|
||||
characterInstanceId = campaignJsonFactory.convertFromV1(
|
||||
characterInstanceId = campaignJsonFactory.characterInstanceIdFromJson(
|
||||
characterInstanceIdJson = payload.characterId,
|
||||
),
|
||||
alterationId = payload.alterationId,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.repository.campaign
|
|||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.character
|
||||
import com.pixelized.shared.lwa.model.campaign.npc
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
|
|
@ -63,7 +63,7 @@ class CampaignStore(
|
|||
when (payload) {
|
||||
is CampaignMessage.UpdateCharacteristic -> updateCharacteristic(
|
||||
characterInstanceId = instanceId,
|
||||
characteristic = factory.convertFromV1(characteristicJson = payload.characteristic),
|
||||
characteristic = factory.convertFromJson(json = payload.characteristic),
|
||||
value = payload.value,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
|
|||
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ fun MasteryShape(
|
|||
shape = shape,
|
||||
color = color,
|
||||
borderWidth = borderWidth,
|
||||
size = size - borderWidth * 2,
|
||||
size = size - borderWidth * 3,
|
||||
multiplier = multiplier - 1,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.desktop.lwa.ui.navigation.screen
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
|
|
@ -10,8 +9,7 @@ import androidx.navigation.compose.rememberNavController
|
|||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.MainDestination
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableMainPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableNetworkPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableOldMainPage
|
||||
|
||||
val LocalScreenController = compositionLocalOf<NavHostController> {
|
||||
error("MainNavHost controller is not yet ready")
|
||||
|
|
@ -21,12 +19,7 @@ val LocalScreenController = compositionLocalOf<NavHostController> {
|
|||
fun MainNavHost(
|
||||
controller: NavHostController = rememberNavController(),
|
||||
startDestination: String = MainDestination.navigationRoute(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
networkViewModel.connect()
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalScreenController provides controller,
|
||||
) {
|
||||
|
|
@ -35,6 +28,7 @@ fun MainNavHost(
|
|||
startDestination = startDestination,
|
||||
) {
|
||||
composableMainPage()
|
||||
composableOldMainPage()
|
||||
composableNetworkPage()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.ui.screen.main.MainPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.MainPage
|
||||
|
||||
object MainDestination {
|
||||
private const val ROUTE = "main"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
||||
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.ui.screen.main.OldMainPage
|
||||
|
||||
object OldMainDestination {
|
||||
private const val ROUTE = "old_main"
|
||||
|
||||
fun baseRoute() = ROUTE
|
||||
fun navigationRoute() = ROUTE
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableOldMainPage() {
|
||||
composable(
|
||||
route = OldMainDestination.baseRoute(),
|
||||
) {
|
||||
OldMainPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToOldMainPage() {
|
||||
val route = OldMainDestination.navigationRoute()
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -13,17 +13,17 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
|
|
@ -39,31 +39,30 @@ import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentControlle
|
|||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToOldMainPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.Toolbar
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.roll.RollPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_table_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun CampaignScreen(
|
||||
fun MainPage(
|
||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
||||
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
||||
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
|
||||
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
rollViewModel: RollViewModel = koinViewModel(),
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
networkViewModel.connect()
|
||||
campaignViewModel.init()
|
||||
}
|
||||
|
||||
KeyHandler {
|
||||
when {
|
||||
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {
|
||||
|
|
@ -75,6 +74,7 @@ fun CampaignScreen(
|
|||
}
|
||||
}
|
||||
|
||||
val screen = LocalScreenController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val blurController = rememberBlurContentController()
|
||||
|
||||
|
|
@ -88,15 +88,22 @@ fun CampaignScreen(
|
|||
CampaignScreenLayout(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
top = {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
// .height(32.dp)
|
||||
.fillMaxWidth(),
|
||||
elevation = 1.dp,
|
||||
Toolbar(
|
||||
title = campaignViewModel.title.collectAsState(initial = "").value,
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
screen.navigateToOldMainPage()
|
||||
},
|
||||
) {
|
||||
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_table_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
bottom = {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
|
|
@ -123,8 +130,7 @@ fun CampaignScreen(
|
|||
modifier = Modifier
|
||||
.width(width = 128.dp * 4)
|
||||
.fillMaxHeight()
|
||||
.padding(all = 8.dp)
|
||||
.clip(shape = remember { RoundedCornerShape(16.dp) }),
|
||||
.padding(all = 8.dp),
|
||||
blurController = blurController,
|
||||
detailViewModel = characterDetailViewModel,
|
||||
rollViewModel = rollViewModel,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CampaignViewModel(
|
||||
|
|
@ -14,8 +16,10 @@ class CampaignViewModel(
|
|||
private val campaignRepository: CampaignRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
suspend fun init() {
|
||||
val title: Flow<String> = campaignRepository.campaignFlow
|
||||
.map { it.scene.name }
|
||||
|
||||
fun init() {
|
||||
viewModelScope.launch {
|
||||
campaignRepository.campaignFlow.collectLatest {
|
||||
it.characters.keys.forEach { id ->
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
|
|
@ -22,8 +23,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
||||
|
|
@ -178,6 +181,7 @@ fun CharacterDetailAnimatedPanel(
|
|||
@Composable
|
||||
fun CharacterDetailContent(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = remember { RoundedCornerShape(16.dp) },
|
||||
header: State<CharacterDetailHeaderUio?>,
|
||||
sheet: State<CharacterDetailSheetUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
|
|
@ -191,6 +195,7 @@ fun CharacterDetailContent(
|
|||
) {
|
||||
Surface(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
shape = shape,
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
) {
|
||||
Column {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fun CharacterDetailSheet(
|
|||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
) {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
|
|
@ -15,6 +16,7 @@ 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.circle.MasteryShape
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.roll.RollActionUio
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -32,7 +34,12 @@ data class CharacterDetailSheetActionUio(
|
|||
@Composable
|
||||
fun CharacterDetailSheetAction(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(start = 28.dp, end = 9.dp, top = 6.dp, bottom = 6.dp),
|
||||
paddingValues: PaddingValues = PaddingValues(
|
||||
start = 10.dp,
|
||||
end = 9.dp,
|
||||
top = 6.dp,
|
||||
bottom = 6.dp
|
||||
),
|
||||
action: CharacterDetailSheetActionUio,
|
||||
onClick: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
|
|
@ -41,9 +48,13 @@ fun CharacterDetailSheetAction(
|
|||
.clickable(onClick = { onClick(action) })
|
||||
.padding(paddingValues = paddingValues)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
MasteryShape(
|
||||
modifier = Modifier.padding(top = 4.dp, end = 2.dp),
|
||||
multiplier = 2,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.body1,
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
|
|
@ -21,13 +23,13 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -55,11 +57,11 @@ fun PlayerPortrait(
|
|||
) {
|
||||
val colorScheme = MaterialTheme.lwa.colorScheme
|
||||
|
||||
Box (
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(size = size)
|
||||
.clip(shape = remember { RoundedCornerShape(8.dp) })
|
||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||
.background(color = colorScheme.elevated.base1dp)
|
||||
.clickable { onCharacter(character.id) },
|
||||
) {
|
||||
AsyncImage(
|
||||
|
|
@ -74,50 +76,54 @@ fun PlayerPortrait(
|
|||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.drawWithContent {
|
||||
drawRect(
|
||||
brush = Brush.verticalGradient(
|
||||
listOf(
|
||||
colorScheme.elevated.base1dp.copy(alpha = 0.0f),
|
||||
colorScheme.elevated.base1dp.copy(alpha = 0.0f),
|
||||
colorScheme.elevated.base1dp.copy(alpha = 0.0f),
|
||||
colorScheme.elevated.base1dp.copy(alpha = 0.5f),
|
||||
colorScheme.elevated.base1dp.copy(alpha = 0.8f),
|
||||
)
|
||||
)
|
||||
)
|
||||
drawRect(brush = colorScheme.portraitBackgroundBrush)
|
||||
drawContent()
|
||||
}
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp),
|
||||
verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
) {
|
||||
Row {
|
||||
Icon(
|
||||
modifier = Modifier.size(12.dp),
|
||||
modifier = Modifier.size(12.dp).offset(y = 3.dp),
|
||||
painter = painterResource(Res.drawable.ic_heart_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(width = 2.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 2.dp),
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "${character.hp}/${character.maxHp}",
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${character.hp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "/${character.maxHp}",
|
||||
)
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
) {
|
||||
Row {
|
||||
Icon(
|
||||
modifier = Modifier.size(12.dp),
|
||||
modifier = Modifier.size(12.dp).offset(y = 2.dp),
|
||||
painter = painterResource(Res.drawable.ic_water_drop_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(width = 2.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 2.dp),
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "${character.pp}/${character.maxPp}",
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${character.pp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "/${character.maxPp}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
||||
@Composable
|
||||
fun Toolbar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
actions: @Composable RowScope.() -> Unit = { },
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
elevation = 1.dp,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.minimumInteractiveComponentSize(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
style = MaterialTheme.typography.h6,
|
||||
text = title,
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides MaterialTheme.lwa.colorScheme.base.primary,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
content = actions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -27,11 +26,11 @@ 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.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToMainPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToNetwork
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheet
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory
|
||||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -39,6 +38,7 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__cr
|
|||
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_file_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_folder_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_swords_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_table_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
|
||||
import lwacharactersheet.composeapp.generated.resources.main_page__network_action
|
||||
|
|
@ -56,12 +56,13 @@ data class CharacterUio(
|
|||
)
|
||||
|
||||
@Composable
|
||||
fun MainPage(
|
||||
fun OldMainPage(
|
||||
viewModel: MainPageViewModel = koinViewModel(),
|
||||
) {
|
||||
val window = LocalWindowController.current
|
||||
val screen = LocalScreenController.current
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
val npcs = viewModel.npcs.collectAsState()
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -75,6 +76,7 @@ fun MainPage(
|
|||
) {
|
||||
MainPageContent(
|
||||
characters = characters,
|
||||
npcs = npcs,
|
||||
enableRollHistory = viewModel.enableRollHistory,
|
||||
onCharacter = {
|
||||
window.navigateToCharacterSheet(
|
||||
|
|
@ -97,6 +99,9 @@ fun MainPage(
|
|||
onNetwork = {
|
||||
screen.navigateToNetwork()
|
||||
},
|
||||
onMainPage = {
|
||||
screen.navigateToMainPage()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -106,17 +111,20 @@ fun MainPage(
|
|||
fun MainPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
characters: State<List<CharacterUio>>,
|
||||
npcs: State<List<CharacterUio>>,
|
||||
enableRollHistory: State<Boolean>,
|
||||
onCharacter: (CharacterUio) -> Unit,
|
||||
onCreateCharacter: () -> Unit,
|
||||
onRollHistory: () -> Unit,
|
||||
onOpenSaveDirectory: () -> Unit,
|
||||
onNetwork: () -> Unit,
|
||||
onMainPage: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
if (characters.value.isNotEmpty()) {
|
||||
Column {
|
||||
characters.value.forEach { sheet ->
|
||||
TextButton(
|
||||
onClick = { onCharacter(sheet) },
|
||||
|
|
@ -130,6 +138,30 @@ fun MainPageContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (characters.value.isNotEmpty() && npcs.value.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
}
|
||||
|
||||
if (npcs.value.isNotEmpty()) {
|
||||
Column {
|
||||
npcs.value.forEach { sheet ->
|
||||
TextButton(
|
||||
onClick = { onCharacter(sheet) },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
maxLines = 1,
|
||||
text = sheet.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
|
||||
|
|
@ -217,5 +249,25 @@ fun MainPageContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
onClick = onMainPage,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_swords_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
maxLines = 1,
|
||||
text = "Nouvelle interface utilisateur",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class MainPageViewModel(
|
||||
|
|
@ -27,21 +28,48 @@ class MainPageViewModel(
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val characters: StateFlow<List<CharacterUio>> = campaignRepository.campaignFlow
|
||||
.flatMapLatest { campaign ->
|
||||
.flatMapMerge { campaign ->
|
||||
combine(
|
||||
campaign.characters.map { entry ->
|
||||
characterSheetRepository.characterDetailFlow(characterId = entry.key.characterSheetId)
|
||||
.mapNotNull { sheet ->
|
||||
sheet?.let {
|
||||
CharacterUio(
|
||||
id = entry.key,
|
||||
name = it.name,
|
||||
campaign.characters
|
||||
.map { entry ->
|
||||
characterSheetRepository
|
||||
.characterDetailFlow(characterId = entry.key.characterSheetId)
|
||||
.map transform@{ sheet ->
|
||||
if (sheet == null) return@transform null
|
||||
CharacterUio(id = entry.key, name = sheet.name)
|
||||
}
|
||||
}
|
||||
.ifEmpty {
|
||||
listOf(flowOf(null))
|
||||
}
|
||||
) { data ->
|
||||
data.mapNotNull { it }
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val npcs: StateFlow<List<CharacterUio>> = campaignRepository.campaignFlow
|
||||
.flatMapMerge { campaign ->
|
||||
combine(
|
||||
campaign.npcs
|
||||
.map { entry ->
|
||||
characterSheetRepository
|
||||
.characterDetailFlow(characterId = entry.key.characterSheetId)
|
||||
.map transform@{ sheet ->
|
||||
if (sheet == null) return@transform null
|
||||
CharacterUio(id = entry.key, name = sheet.name)
|
||||
}
|
||||
}
|
||||
.ifEmpty {
|
||||
listOf(flowOf(null))
|
||||
}
|
||||
) {
|
||||
it.asList()
|
||||
) { data ->
|
||||
data.mapNotNull { it }
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
|
|
|
|||
|
|
@ -59,12 +59,14 @@ class RollViewModel(
|
|||
private val _displayOverlay = mutableStateOf(false)
|
||||
val displayOverlay: State<Boolean> get() = _displayOverlay
|
||||
|
||||
@Deprecated(message = "@See prepareRoll(RollActionUio)")
|
||||
fun prepareRoll(
|
||||
sheet: CharacterSheetPageUio,
|
||||
characteristic: CharacterSheetPageUio.Characteristic,
|
||||
) {
|
||||
val diminished =
|
||||
0 // TODO characterSheetRepository.characterDiminishedFlow(id = sheet.id).value
|
||||
// TODO characterSheetRepository.characterDiminishedFlow(id = sheet.id).value
|
||||
val diminished = 0
|
||||
|
||||
prepareRoll(
|
||||
characterSheetId = sheet.id,
|
||||
label = characteristic.label,
|
||||
|
|
@ -73,6 +75,7 @@ class RollViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
@Deprecated(message = "@See prepareRoll(RollActionUio)")
|
||||
fun prepareRoll(
|
||||
sheet: CharacterSheetPageUio,
|
||||
node: CharacterSheetPageUio.Node,
|
||||
|
|
@ -85,6 +88,7 @@ class RollViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
@Deprecated(message = "@See prepareRoll(RollActionUio)")
|
||||
fun prepareRoll(
|
||||
sheet: CharacterSheetPageUio,
|
||||
roll: CharacterSheetPageUio.Roll,
|
||||
|
|
@ -115,8 +119,8 @@ class RollViewModel(
|
|||
this.sheet = runBlocking {
|
||||
rollRotation.snapTo(0f)
|
||||
rollScale.snapTo(1f)
|
||||
characterSheetRepository.characterDetail(characterSheetId = characterSheetId)!!
|
||||
}
|
||||
characterSheetRepository.characterDetail(characterSheetId = characterSheetId)
|
||||
} ?: return
|
||||
|
||||
this.rollAction = rollAction
|
||||
this.rollSuccessValue = rollSuccessValue
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.material.darkColors
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
|
@ -15,6 +16,7 @@ import kotlin.math.ln
|
|||
data class LwaColorTheme(
|
||||
val base: Colors,
|
||||
val elevated: Elevated,
|
||||
val portraitBackgroundBrush: Brush,
|
||||
) {
|
||||
@Stable
|
||||
data class Elevated(
|
||||
|
|
@ -39,9 +41,19 @@ fun darkLwaColorTheme(
|
|||
elevation = 2.dp,
|
||||
),
|
||||
),
|
||||
portraitBackgroundBrush: Brush = Brush.verticalGradient(
|
||||
listOf(
|
||||
elevated.base1dp.copy(alpha = 0.0f),
|
||||
elevated.base1dp.copy(alpha = 0.0f),
|
||||
elevated.base1dp.copy(alpha = 0.0f),
|
||||
elevated.base1dp.copy(alpha = 0.5f),
|
||||
elevated.base1dp.copy(alpha = 0.8f),
|
||||
)
|
||||
)
|
||||
): LwaColorTheme = LwaColorTheme(
|
||||
base = base,
|
||||
elevated = elevated,
|
||||
portraitBackgroundBrush = portraitBackgroundBrush,
|
||||
)
|
||||
|
||||
@ReadOnlyComposable
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.pixelized.server.lwa.model.alteration
|
|||
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -19,7 +19,7 @@ class AlterationService(
|
|||
private val actives = store.activeFlow()
|
||||
.map { data ->
|
||||
data.mapKeys { it: Map.Entry<String, List<String>> ->
|
||||
campaignJsonFactory.convertFromV1(characterInstanceIdJson = it.key)
|
||||
campaignJsonFactory.characterInstanceIdFromJson(characterInstanceIdJson = it.key)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ package com.pixelized.server.lwa.model.alteration
|
|||
import com.pixelized.shared.lwa.alterationsPath
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.pixelized.server.lwa.model.campaign
|
|||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.character
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.npc
|
||||
import com.pixelized.shared.lwa.usecase.CampaignUseCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -47,10 +47,14 @@ class CampaignService(
|
|||
// update the corresponding character
|
||||
characters[characterInstanceId] = campaign.character(id = characterInstanceId)
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(characters = characters)
|
||||
)
|
||||
return true
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeCharacter(
|
||||
|
|
@ -63,10 +67,14 @@ class CampaignService(
|
|||
// update the corresponding character
|
||||
characters.remove(characterInstanceId)
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(characters = characters)
|
||||
)
|
||||
return true
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addNpc(
|
||||
|
|
@ -79,10 +87,14 @@ class CampaignService(
|
|||
// update the corresponding character
|
||||
npcs[npcInstanceId] = campaign.npc(id = npcInstanceId)
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(npcs = npcs)
|
||||
)
|
||||
return true
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeNpc(
|
||||
|
|
@ -95,10 +107,28 @@ class CampaignService(
|
|||
// update the corresponding character
|
||||
npcs.remove(npcInstanceId)
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(npcs = npcs)
|
||||
)
|
||||
return true
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setScene(
|
||||
scene: Campaign.Scene,
|
||||
): Boolean {
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign.copy(scene = scene)
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Data manipulation through WebSocket.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.pixelized.server.lwa.model.campaign
|
|||
import com.pixelized.shared.lwa.campaignPath
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.pixelized.server.lwa.model.alteration.AlterationService
|
|||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
|
|
@ -33,7 +33,7 @@ class Engine(
|
|||
when (data) {
|
||||
is CampaignMessage.UpdateCharacteristic -> campaignService.updateCharacteristic(
|
||||
characterInstanceId = instanceId,
|
||||
characteristic = campaignJsonFactory.convertFromV1(characteristicJson = data.characteristic),
|
||||
characteristic = campaignJsonFactory.convertFromJson(json = data.characteristic),
|
||||
value = data.value,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.pixelized.server.lwa.server.rest.campaign.deleteCampaignNpc
|
|||
import com.pixelized.server.lwa.server.rest.campaign.getCampaign
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignScene
|
||||
import com.pixelized.server.lwa.server.rest.character.deleteCharacter
|
||||
import com.pixelized.server.lwa.server.rest.character.getCharacter
|
||||
import com.pixelized.server.lwa.server.rest.character.getCharacters
|
||||
|
|
@ -146,6 +147,10 @@ class LocalServer {
|
|||
body = engine.deleteCampaignNpc(),
|
||||
)
|
||||
}
|
||||
put(
|
||||
path = "/scene",
|
||||
body = engine.putCampaignScene(),
|
||||
)
|
||||
}
|
||||
route(path = "/alterations") {
|
||||
get(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respondText
|
||||
|
||||
fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val form = call.receive<CampaignJsonV1.SceneJsonV1>()
|
||||
|
||||
val scene = campaignJsonFactory.convertFromJson(json = form)
|
||||
|
||||
val updated = campaignService.setScene(scene = scene)
|
||||
|
||||
val code = when (updated) {
|
||||
true -> HttpStatusCode.Accepted
|
||||
else -> HttpStatusCode.UnprocessableEntity
|
||||
}
|
||||
|
||||
call.respondText(
|
||||
text = "$code",
|
||||
status = code,
|
||||
)
|
||||
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ package com.pixelized.shared.lwa
|
|||
|
||||
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonV1Factory
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.parser.dice.DiceParser
|
||||
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
||||
|
|
@ -38,6 +39,7 @@ val factoryDependencies
|
|||
get() = module {
|
||||
factoryOf(::CharacterSheetJsonFactory)
|
||||
factoryOf(::CampaignJsonFactory)
|
||||
factoryOf(::CampaignJsonV1Factory)
|
||||
factoryOf(::AlteredCharacterSheetFactory)
|
||||
factoryOf(::AlterationJsonFactory)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.pixelized.shared.lwa.model.campaign
|
|||
data class Campaign(
|
||||
val characters: Map<CharacterInstance.Id, CharacterInstance>,
|
||||
val npcs: Map<CharacterInstance.Id, CharacterInstance>,
|
||||
val scene: Scene,
|
||||
) {
|
||||
data class CharacterInstance(
|
||||
val characteristic: Map<Characteristic, Int>,
|
||||
|
|
@ -26,10 +27,21 @@ data class Campaign(
|
|||
}
|
||||
}
|
||||
|
||||
data class Scene(
|
||||
val name: String,
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = Scene(
|
||||
name = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = Campaign(
|
||||
characters = emptyMap(),
|
||||
npcs = emptyMap(),
|
||||
scene = Scene.empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,15 @@ package com.pixelized.shared.lwa.model.campaign
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed interface CampaignJson
|
||||
sealed interface CampaignJson {
|
||||
|
||||
@Serializable
|
||||
sealed interface CharacterInstanceJson {
|
||||
|
||||
@Serializable
|
||||
sealed interface CharacteristicJson
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed interface SceneJson
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package com.pixelized.shared.lwa.model.campaign
|
||||
|
||||
|
||||
class CampaignJsonFactory {
|
||||
fun convertFromJson(
|
||||
json: CampaignJson,
|
||||
): Campaign {
|
||||
return when (json) {
|
||||
is CampaignJsonV1 -> convertFromV1(campaignJson = json)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertFromV1(
|
||||
campaignJson: CampaignJsonV1,
|
||||
): Campaign {
|
||||
return Campaign(
|
||||
characters = campaignJson.characters
|
||||
.map {
|
||||
convertFromV1(characterInstanceIdJson = it.key) to convertFromV1(
|
||||
characterInstanceJson = it.value
|
||||
)
|
||||
}
|
||||
.toMap(),
|
||||
npcs = campaignJson.npcs
|
||||
.map {
|
||||
convertFromV1(characterInstanceIdJson = it.key) to convertFromV1(
|
||||
characterInstanceJson = it.value
|
||||
)
|
||||
}
|
||||
.toMap(),
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
characterInstanceIdJson: String,
|
||||
): Campaign.CharacterInstance.Id {
|
||||
return Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterInstanceIdJson.drop(4), // drop first 3 number then the -
|
||||
instanceId = characterInstanceIdJson.take(3).toIntOrNull() ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
characterInstanceJson: CampaignJsonV1.CharacterInstanceJson,
|
||||
): Campaign.CharacterInstance {
|
||||
return Campaign.CharacterInstance(
|
||||
characteristic = characterInstanceJson.characteristic
|
||||
.map { char -> convertFromV1(characteristicJson = char.key) to char.value }
|
||||
.toMap(),
|
||||
diminished = characterInstanceJson.diminished ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
characteristicJson: CampaignJsonV1.CharacterInstanceJson.Characteristic,
|
||||
): Campaign.CharacterInstance.Characteristic {
|
||||
return when (characteristicJson) {
|
||||
CampaignJsonV1.CharacterInstanceJson.Characteristic.Damage -> Campaign.CharacterInstance.Characteristic.Damage
|
||||
CampaignJsonV1.CharacterInstanceJson.Characteristic.Power -> Campaign.CharacterInstance.Characteristic.Power
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
data: Campaign,
|
||||
): CampaignJson {
|
||||
return CampaignJsonV1(
|
||||
characters = data.characters
|
||||
.map { convertToJson(id = it.key) to convertToJson(data = it.value) }
|
||||
.toMap(),
|
||||
npcs = data.npcs
|
||||
.map { convertToJson(id = it.key) to convertToJson(data = it.value) }
|
||||
.toMap(),
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
id: Campaign.CharacterInstance.Id,
|
||||
): String {
|
||||
return "${String.format("%03d", id.instanceId)}-${id.characterSheetId}"
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
data: Campaign.CharacterInstance,
|
||||
): CampaignJsonV1.CharacterInstanceJson {
|
||||
return CampaignJsonV1.CharacterInstanceJson(
|
||||
characteristic = data.characteristic
|
||||
.map { char -> convertToJson(characteristic = char.key) to char.value }
|
||||
.toMap(),
|
||||
diminished = data.diminished,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
characteristic: Campaign.CharacterInstance.Characteristic,
|
||||
): CampaignJsonV1.CharacterInstanceJson.Characteristic {
|
||||
return when (characteristic) {
|
||||
Campaign.CharacterInstance.Characteristic.Damage -> CampaignJsonV1.CharacterInstanceJson.Characteristic.Damage
|
||||
Campaign.CharacterInstance.Characteristic.Power -> CampaignJsonV1.CharacterInstanceJson.Characteristic.Power
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,18 +4,24 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class CampaignJsonV1(
|
||||
val characters: Map<String, CharacterInstanceJson>,
|
||||
val npcs: Map<String, CharacterInstanceJson>,
|
||||
val characters: Map<String, CharacterInstanceJsonV1>,
|
||||
val npcs: Map<String, CharacterInstanceJsonV1>,
|
||||
val scene: SceneJsonV1?,
|
||||
) : CampaignJson {
|
||||
|
||||
@Serializable
|
||||
data class CharacterInstanceJson(
|
||||
val characteristic: Map<Characteristic, Int>,
|
||||
data class CharacterInstanceJsonV1(
|
||||
val characteristic: Map<CharacteristicV1, Int>,
|
||||
val diminished: Int?,
|
||||
) {
|
||||
enum class Characteristic {
|
||||
) : CampaignJson.CharacterInstanceJson {
|
||||
enum class CharacteristicV1 : CampaignJson.CharacterInstanceJson.CharacteristicJson {
|
||||
Damage,
|
||||
Power,
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SceneJsonV1(
|
||||
val name: String,
|
||||
) : CampaignJson.SceneJson
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.pixelized.shared.lwa.model.campaign.factory
|
||||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1
|
||||
|
||||
class CampaignJsonFactory(
|
||||
private val v1: CampaignJsonV1Factory,
|
||||
) {
|
||||
fun characterInstanceIdFromJson(
|
||||
characterInstanceIdJson: String,
|
||||
): Campaign.CharacterInstance.Id {
|
||||
return v1.convertFromV1(characterInstanceIdJson)
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
json: CampaignJson,
|
||||
): Campaign {
|
||||
return when (json) {
|
||||
is CampaignJsonV1 -> v1.convertFromV1(campaignJson = json)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
json: CampaignJson.CharacterInstanceJson,
|
||||
): Campaign.CharacterInstance {
|
||||
return when (json) {
|
||||
is CampaignJsonV1.CharacterInstanceJsonV1 -> v1.convertFromV1(characterInstanceJson = json)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
json: CampaignJson.CharacterInstanceJson.CharacteristicJson,
|
||||
): Campaign.CharacterInstance.Characteristic {
|
||||
return when (json) {
|
||||
CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage -> Campaign.CharacterInstance.Characteristic.Damage
|
||||
CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power -> Campaign.CharacterInstance.Characteristic.Power
|
||||
}
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
json: CampaignJson.SceneJson,
|
||||
): Campaign.Scene {
|
||||
return when (json) {
|
||||
is CampaignJsonV1.SceneJsonV1 -> v1.convertFromV1(sceneJson = json)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
data: Campaign,
|
||||
): CampaignJson {
|
||||
return CampaignJsonV1(
|
||||
characters = data.characters
|
||||
.map { convertToJson(id = it.key) to convertToJson(data = it.value) }
|
||||
.toMap(),
|
||||
npcs = data.npcs
|
||||
.map { convertToJson(id = it.key) to convertToJson(data = it.value) }
|
||||
.toMap(),
|
||||
scene = CampaignJsonV1.SceneJsonV1(
|
||||
name = data.scene.name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
id: Campaign.CharacterInstance.Id,
|
||||
): String {
|
||||
return "${String.format("%03d", id.instanceId)}-${id.characterSheetId}"
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
data: Campaign.CharacterInstance,
|
||||
): CampaignJsonV1.CharacterInstanceJsonV1 {
|
||||
return CampaignJsonV1.CharacterInstanceJsonV1(
|
||||
characteristic = data.characteristic
|
||||
.map { char -> convertToJson(characteristic = char.key) to char.value }
|
||||
.toMap(),
|
||||
diminished = data.diminished,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
characteristic: Campaign.CharacterInstance.Characteristic,
|
||||
): CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1 {
|
||||
return when (characteristic) {
|
||||
Campaign.CharacterInstance.Characteristic.Damage -> CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage
|
||||
Campaign.CharacterInstance.Characteristic.Power -> CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
scene: Campaign.Scene,
|
||||
): CampaignJsonV1.SceneJsonV1 {
|
||||
return CampaignJsonV1.SceneJsonV1(
|
||||
name = scene.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.pixelized.shared.lwa.model.campaign.factory
|
||||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1
|
||||
|
||||
class CampaignJsonV1Factory {
|
||||
|
||||
fun convertFromV1(
|
||||
campaignJson: CampaignJsonV1,
|
||||
): Campaign {
|
||||
return Campaign(
|
||||
characters = campaignJson.characters
|
||||
.map {
|
||||
val key = convertFromV1(characterInstanceIdJson = it.key)
|
||||
val instance = convertFromV1(characterInstanceJson = it.value)
|
||||
key to instance
|
||||
}
|
||||
.toMap(),
|
||||
npcs = campaignJson.npcs
|
||||
.map {
|
||||
val key = convertFromV1(characterInstanceIdJson = it.key)
|
||||
val instance = convertFromV1(characterInstanceJson = it.value)
|
||||
key to instance
|
||||
}
|
||||
.toMap(),
|
||||
scene = campaignJson.scene
|
||||
?.let { convertFromV1(it) }
|
||||
?: Campaign.Scene.empty(),
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
characterInstanceIdJson: String,
|
||||
): Campaign.CharacterInstance.Id {
|
||||
return Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterInstanceIdJson.drop(4), // drop first 3 number then the -
|
||||
instanceId = characterInstanceIdJson.take(3).toIntOrNull() ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
characterInstanceJson: CampaignJsonV1.CharacterInstanceJsonV1,
|
||||
): Campaign.CharacterInstance {
|
||||
return Campaign.CharacterInstance(
|
||||
characteristic = characterInstanceJson.characteristic
|
||||
.mapKeys { convertFromV1(characteristicJson = it.key) }
|
||||
.toMap(),
|
||||
diminished = characterInstanceJson.diminished ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
characteristicJson: CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1,
|
||||
): Campaign.CharacterInstance.Characteristic {
|
||||
return when (characteristicJson) {
|
||||
CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage -> Campaign.CharacterInstance.Characteristic.Damage
|
||||
CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power -> Campaign.CharacterInstance.Characteristic.Power
|
||||
}
|
||||
}
|
||||
|
||||
fun convertFromV1(
|
||||
sceneJson: CampaignJsonV1.SceneJsonV1,
|
||||
): Campaign.Scene {
|
||||
return Campaign.Scene(
|
||||
name = sceneJson.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ sealed interface CampaignMessage : MessagePayload {
|
|||
data class UpdateCharacteristic(
|
||||
override val characterSheetId: String,
|
||||
override val instanceId: Int,
|
||||
val characteristic: CampaignJsonV1.CharacterInstanceJson.Characteristic,
|
||||
val characteristic: CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1,
|
||||
val value: Int,
|
||||
) : CampaignMessage
|
||||
|
||||
|
|
|
|||
|
|
@ -60,13 +60,16 @@ class ExpressionUseCase(
|
|||
alterations: Map<String, List<FieldAlteration>>,
|
||||
expression: String,
|
||||
): Int {
|
||||
return expressionParser.parse(input = expression)?.let {
|
||||
print("Roll::$expression::")
|
||||
val roll = expressionParser.parse(input = expression)?.let {
|
||||
computeExpression(
|
||||
sheet = sheet,
|
||||
alterations = alterations,
|
||||
expression = it,
|
||||
)
|
||||
} ?: 0
|
||||
println("::$roll")
|
||||
return roll
|
||||
}
|
||||
|
||||
fun computeExpression(
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class RollUseCase {
|
|||
if (quantity > 1 && left != 1) print(",")
|
||||
}
|
||||
}.also {
|
||||
println("}")
|
||||
print("}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue