From 0c613c5b72598525155f4ff67371167fcf944417 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 18 Feb 2025 20:49:38 +0100 Subject: [PATCH] New UI. --- composeApp/build.gradle.kts | 3 + .../drawable/ic_close_24dp.xml | 13 + .../drawable/ic_heart_24dp.xml | 13 + .../composeResources/drawable/ic_near_me.xml | 13 + .../drawable/ic_water_drop_24dp.xml | 13 + .../kotlin/com/pixelized/desktop/lwa/App.kt | 17 +- .../com/pixelized/desktop/lwa/Module.kt | 6 + .../lwa/composable/blur/BlurContent.kt | 2 +- .../CharacterSheetJsonFactory.kt | 4 + .../characterSheet/model/CharacterSheet.kt | 2 + .../model/CharacterSheetJsonV1.kt | 2 + .../lwa/screen/campaign/CampaignScreen.kt | 177 +++++++++++ .../campaign/player/detail/CharacterDetail.kt | 279 ++++++++++++++++++ .../player/detail/CharacterDetailViewModel.kt | 76 +++++ .../detail/CharacterDiminishedViewModel.kt | 49 +++ .../campaign/player/ribbon/PlayerPortrait.kt | 125 ++++++++ .../campaign/player/ribbon/PlayerRibbon.kt | 34 +++ .../player/ribbon/PlayerRibbonViewModel.kt | 31 ++ .../detail/CharacterSheetViewModel.kt | 3 +- .../detail/dialog/DiminishedStatDialog.kt | 1 + .../edit/CharacterSheetEditFactory.kt | 2 + .../pixelized/desktop/lwa/theme/LwaTheme.kt | 46 ++- .../lwa/theme/{ => color}/LwaColorPalette.kt | 2 +- .../desktop/lwa/theme/color/LwaColorTheme.kt | 50 ++++ gradle/libs.versions.toml | 7 +- 25 files changed, 947 insertions(+), 23 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_close_24dp.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_heart_24dp.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_near_me.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_water_drop_24dp.xml create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/CampaignScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetail.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetailViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDiminishedViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerPortrait.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbon.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt rename composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/{ => color}/LwaColorPalette.kt (73%) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorTheme.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 2dc5255..3ba22e2 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -29,6 +29,9 @@ kotlin { api(libs.koin.core) implementation(libs.koin.compose) implementation(libs.koin.compose.viewmodel) + // composable component. + implementation(libs.coil.compose) + implementation(libs.coil.network) // common implementation(projects.shared) // network diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_close_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_close_24dp.xml new file mode 100644 index 0000000..dbc48c7 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_close_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_heart_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_heart_24dp.xml new file mode 100644 index 0000000..140a570 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_heart_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_near_me.xml b/composeApp/src/commonMain/composeResources/drawable/ic_near_me.xml new file mode 100644 index 0000000..0897cf4 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_near_me.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_water_drop_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_water_drop_24dp.xml new file mode 100644 index 0000000..e560f93 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_water_drop_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index 0f54cc1..3287d4d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -25,16 +25,12 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.min import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.Window -import androidx.compose.ui.window.rememberWindowState import com.pixelized.desktop.lwa.composable.key.KeyEventHandler import com.pixelized.desktop.lwa.composable.key.LocalKeyEventHandlers -import com.pixelized.desktop.lwa.navigation.screen.MainNavHost import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetDestination import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetEditDestination import com.pixelized.desktop.lwa.navigation.window.WindowController @@ -45,6 +41,7 @@ import com.pixelized.desktop.lwa.navigation.window.destination.RollHistoryWindow import com.pixelized.desktop.lwa.navigation.window.rememberMaxWindowHeight import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status +import com.pixelized.desktop.lwa.screen.campaign.CampaignScreen import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryPage import com.pixelized.desktop.lwa.theme.LwaTheme @@ -55,7 +52,6 @@ import lwacharactersheet.composeapp.generated.resources.network__disconnect__mes import org.jetbrains.compose.resources.getString import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.koinInject -import java.awt.Toolkit val LocalWindowController = compositionLocalOf { error("Local Window Controller is not yet ready") @@ -87,10 +83,10 @@ fun ApplicationScope.App() { ) { Window( onCloseRequest = ::exitApplication, - state = rememberWindowState( - width = 320.dp + 64.dp, - height = 900.dp, - ), +// state = rememberWindowState( +// width = 320.dp + 64.dp, +// height = 900.dp, +// ), title = "LwaCharacterSheet", onKeyEvent = { event -> keyEventHandlers.reversed().any { it(event) } @@ -128,7 +124,8 @@ fun ApplicationScope.App() { } }, content = { - MainNavHost() +// MainNavHost() + CampaignScreen() } ) NetworkSnackHandler( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt index 6e4165d..4ef1473 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -29,6 +29,9 @@ import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.parser.dice.DiceParser import com.pixelized.desktop.lwa.parser.word.WordParser import com.pixelized.desktop.lwa.parser.expression.ExpressionParser +import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDetailViewModel +import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDiminishedViewModel +import com.pixelized.desktop.lwa.screen.campaign.player.ribbon.PlayerRibbonViewModel import kotlinx.serialization.json.Json import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf @@ -86,6 +89,9 @@ val viewModelDependencies viewModelOf(::RollViewModel) viewModelOf(::RollHistoryViewModel) viewModelOf(::NetworkViewModel) + viewModelOf(::PlayerRibbonViewModel) + viewModelOf(::CharacterDetailViewModel) + viewModelOf(::CharacterDiminishedViewModel) } val parserDependencies diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/blur/BlurContent.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/blur/BlurContent.kt index c743d14..19a4c08 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/blur/BlurContent.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/blur/BlurContent.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.pixelized.desktop.lwa.theme.LwaColorPalette +import com.pixelized.desktop.lwa.theme.color.LwaColorPalette @Stable class BlurContentController( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt index 40a7576..2fd178c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt @@ -16,6 +16,8 @@ class CharacterSheetJsonFactory( val json = CharacterSheetJsonV1( id = sheet.id, name = sheet.name, + thumbnail = sheet.thumbnail, + portrait = sheet.portrait, strength = sheet.strength, dexterity = sheet.dexterity, constitution = sheet.constitution, @@ -93,6 +95,8 @@ class CharacterSheetJsonFactory( CharacterSheet( id = json.id, name = json.name, + portrait = json.portrait, + thumbnail = json.thumbnail, strength = json.strength, dexterity = json.dexterity, constitution = json.constitution, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt index 254c58c..21bc30b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt @@ -3,6 +3,8 @@ package com.pixelized.desktop.lwa.repository.characterSheet.model data class CharacterSheet( val id: String, val name: String, + val portrait: String?, + val thumbnail: String?, // characteristics val strength: Int, val dexterity: Int, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt index d2e4550..c50034e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt @@ -6,6 +6,8 @@ import kotlinx.serialization.Serializable data class CharacterSheetJsonV1( val id: String, val name: String, + val portrait: String?, + val thumbnail: String?, // characteristics val strength: Int, val dexterity: Int, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/CampaignScreen.kt new file mode 100644 index 0000000..68bb10b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/CampaignScreen.kt @@ -0,0 +1,177 @@ +package com.pixelized.desktop.lwa.screen.campaign + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.type +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.composable.key.KeyHandler +import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDetail +import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDetailViewModel +import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDiminishedViewModel +import com.pixelized.desktop.lwa.screen.campaign.player.ribbon.PlayerRibbon +import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.DiminishedStatDialog +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun CampaignScreen( + characterDetailViewModel: CharacterDetailViewModel = koinViewModel(), + dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(), +) { + KeyHandler { + when { + it.type == KeyEventType.KeyUp && it.key == Key.Escape -> { + characterDetailViewModel.hideCharacter() + true + } + + else -> false + } + } + + Surface { + CampaignScreenLayout( + modifier = Modifier.fillMaxSize(), + top = { + Surface( + modifier = Modifier + .height(32.dp) + .fillMaxWidth(), + elevation = 1.dp, + ) { + + } + }, + bottom = { + Surface( + modifier = Modifier + .height(48.dp) + .fillMaxWidth(), + elevation = 1.dp, + ) { + + } + }, + main = { + + }, + leftOverlay = { + PlayerRibbon( + modifier = Modifier + .padding(all = 8.dp) + .fillMaxHeight(), + onCharacter = { + characterDetailViewModel.showCharacter(id = it) + }, + ) + }, + rightOverlay = { + CharacterDetail( + modifier = Modifier + .padding(all = 8.dp) + .fillMaxHeight(), + viewModel = characterDetailViewModel, + dismissedViewModel = dismissedViewModel, + ) + }, + ) + + DiminishedStatDialog( + dialog = dismissedViewModel.diminishedDialog, + onConfirm = { diminished -> + dismissedViewModel.changeDiminished( + dialog = diminished + ) + dismissedViewModel.hideDiminishedDialog() + }, + onDismissRequest = { + dismissedViewModel.hideDiminishedDialog() + }, + ) + } +} + +@Composable +private fun CampaignScreenLayout( + modifier: Modifier = Modifier, + top: @Composable CampaignScreenScope.() -> Unit, + bottom: @Composable CampaignScreenScope.() -> Unit, + main: @Composable CampaignScreenScope.() -> Unit, + leftOverlay: @Composable CampaignScreenScope.() -> Unit, + rightOverlay: @Composable CampaignScreenScope.() -> Unit, +) { + val density = LocalDensity.current + val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) } + val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) } + val scope = remember { + CampaignScreenScope( + leftOverlay = leftOverlayState, + rightOverlay = rightOverlayState, + ) + } + with(scope) { + Column( + modifier = modifier, + ) { + top() + Box( + modifier = Modifier.weight(1f, fill = true), + ) { + Box( + modifier = Modifier + .align(alignment = Alignment.Center) + .fillMaxSize(), + ) { + main() + } + Box( + modifier = Modifier + .align(alignment = Alignment.CenterStart) + .onSizeChanged { leftOverlayState.value = it.toDp(density) }, + ) { + leftOverlay() + } + Box( + modifier = Modifier + .align(alignment = Alignment.CenterEnd) + .onSizeChanged { rightOverlayState.value = it.toDp(density) }, + ) { + rightOverlay() + } + } + bottom() + } + } +} + +private class CampaignScreenScope( + val leftOverlay: State, + val rightOverlay: State, +) + +private fun IntSize.toDp(density: Density) = with(density) { + DpSize( + width = width.toDp(), + height = height.toDp(), + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetail.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetail.kt new file mode 100644 index 0000000..f2de025 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetail.kt @@ -0,0 +1,279 @@ +package com.pixelized.desktop.lwa.screen.campaign.player.detail + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.togetherWith +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.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +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 +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +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.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.DiminishedStatDialog +import com.pixelized.desktop.lwa.theme.lwa +import kotlinx.coroutines.launch +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.ic_close_24dp +import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp +import lwacharactersheet.composeapp.generated.resources.ic_near_me +import lwacharactersheet.composeapp.generated.resources.ic_skull_24dp +import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp +import org.jetbrains.compose.resources.painterResource +import org.koin.compose.viewmodel.koinViewModel + +@Stable +data class CharacterDetailUio( + val id: String, + val portrait: String?, + val name: String, + val hp: String, + val pp: String, +) + +@Stable +data class CharacterDynDetailUio( + val hp: String, + val pp: String, + val mov: String, +) + +@Composable +fun CharacterDetail( + modifier: Modifier = Modifier, + dismissedViewModel: CharacterDiminishedViewModel, + viewModel: CharacterDetailViewModel = koinViewModel(), +) { + val scope = rememberCoroutineScope() + val detail = viewModel.detail.collectAsState() + + AnimatedContent( + modifier = modifier, + targetState = detail.value, + transitionSpec = { + (fadeIn() + slideInHorizontally { it / 2 }).togetherWith(fadeOut()) + } + ) { + when (it) { + null -> Box( + modifier = Modifier.fillMaxHeight(), + ) + + else -> { + val dynDetail = viewModel.collectDynamicDetailAsState(id = it.id) + + CharacterDetailContent( + modifier = Modifier + .fillMaxHeight() + .width(width = 128.dp * 4), + character = it, + dynDetail = dynDetail, + onDismissRequest = { viewModel.hideCharacter() }, + onDiminished = { + scope.launch { + dismissedViewModel.showDiminishedDialog(id = it.id) + } + }, + onHp = { }, + onPp = { }, + ) + } + } + } +} + +@Composable +fun CharacterDetailContent( + modifier: Modifier = Modifier, + character: CharacterDetailUio, + dynDetail: State, + onDismissRequest: () -> Unit, + onDiminished: () -> Unit, + onHp: () -> Unit, + onPp: () -> Unit, +) { + Box( + modifier = modifier, + ) { + Background( + character = character, + ) + Column { + CharacterHeader( + modifier = Modifier.padding(start = 16.dp).fillMaxWidth(), + character = character, + onDismissRequest = onDismissRequest, + dynDetail = dynDetail, + onDiminished = onDiminished, + onHp = onHp, + onPp = onPp, + ) + } + } +} + +@Composable +private fun Background( + modifier: Modifier = Modifier, + character: CharacterDetailUio, +) { + Surface( + modifier = modifier.fillMaxSize(), + color = MaterialTheme.lwa.color.elevatedSurface, + ) { +// Image( +// modifier = Modifier.fillMaxSize().drawWithContent { +// drawContent() +// drawRect( +// brush = Brush.verticalGradient( +// listOfNotNull( +// color?.copy(alpha = 0.7f), +// color, +// ) +// ) +// ) +// }, +// painter = rememberAsyncImagePainter(model = character.portrait), +// contentDescription = null, +// contentScale = ContentScale.Crop, +// alignment = Alignment.TopCenter, +// ) + } +} + +@Composable +private fun CharacterHeader( + modifier: Modifier = Modifier, + character: CharacterDetailUio, + dynDetail: State, + onDismissRequest: () -> Unit, + onDiminished: () -> Unit, + onHp: () -> Unit, + onPp: () -> Unit, +) { + Column( + modifier = modifier, + ) { + Row { + Text( + modifier = Modifier.weight(1f) + .align(alignment = Alignment.CenterVertically), + style = MaterialTheme.typography.h5, + text = character.name, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + IconButton( + onClick = onDiminished, + ) { + Icon( + modifier = Modifier.size(size = 24.dp), + painter = painterResource(Res.drawable.ic_skull_24dp), + tint = MaterialTheme.colors.primary, + contentDescription = null, + ) + } + IconButton( + onClick = onDismissRequest, + ) { + Icon( + painter = painterResource(Res.drawable.ic_close_24dp), + tint = MaterialTheme.lwa.color.base.primary, + contentDescription = null, + ) + } + } + Row( + horizontalArrangement = Arrangement.spacedBy(space = 12.dp), + ) { + Row( + modifier = Modifier.clip(shape = CircleShape).clickable { onHp() }, + verticalAlignment = Alignment.Bottom, + ) { + Icon( + modifier = Modifier.padding(bottom = 4.dp, end = 2.dp).size(12.dp), + painter = painterResource(Res.drawable.ic_heart_24dp), + contentDescription = null + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.h6, + color = MaterialTheme.lwa.color.base.primary, + fontWeight = FontWeight.Bold, + text = dynDetail.value.hp, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Thin, + text = "/${character.hp}", + ) + } + Row( + modifier = Modifier.clip(shape = CircleShape).clickable { onPp() }, + verticalAlignment = Alignment.Bottom, + ) { + Icon( + modifier = Modifier.padding(bottom = 4.dp, end = 2.dp).size(12.dp), + painter = painterResource(Res.drawable.ic_water_drop_24dp), + contentDescription = null + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.h6, + color = MaterialTheme.lwa.color.base.primary, + fontWeight = FontWeight.Bold, + text = dynDetail.value.pp, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Thin, + text = "/${character.pp}", + ) + } + Row( + verticalAlignment = Alignment.Bottom, + ) { + Icon( + modifier = Modifier.padding(bottom = 4.dp, end = 2.dp).size(12.dp), + painter = painterResource(Res.drawable.ic_near_me), + contentDescription = null, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.h6, + text = dynDetail.value.mov, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.caption, + text = "m", + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetailViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetailViewModel.kt new file mode 100644 index 0000000..5fd3a29 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDetailViewModel.kt @@ -0,0 +1,76 @@ +package com.pixelized.desktop.lwa.screen.campaign.player.detail + +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.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn + +class CharacterDetailViewModel( + private val repository: CharacterSheetRepository, + private val alteration: AlterationRepository, +) : ViewModel() { + + private val displayedCharacterId = MutableStateFlow(null) + + val detail: StateFlow = combine( + displayedCharacterId, + repository.characterSheetFlow(), + ) { id, sheets -> + val sheet = sheets.firstOrNull { it.id == id } + if (sheet == null) return@combine null + CharacterDetailUio( + id = sheet.id, + portrait = sheet.portrait, + name = sheet.name, + hp = "${sheet.maxHp}", + pp = "${sheet.maxPp}", + ) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + + @Composable + @Stable + fun collectDynamicDetailAsState(id: String): State { + val flow = remember(id) { + repository.characterSheetFlow(id = id) + } + return remember(id) { + flow.mapNotNull { sheet -> + if (sheet == null) return@mapNotNull null + CharacterDynDetailUio( + hp = sheet.currentHp.toString(), + pp = sheet.currentPp.toString(), + mov = sheet.movement.toString(), + ) + } + }.collectAsState( + initial = CharacterDynDetailUio( + hp = flow.value?.maxHp?.toString() ?: "", + pp = flow.value?.maxPp?.toString() ?: "", + mov = flow.value?.movement?.toString() ?: "", + ) + ) + } + + fun showCharacter(id: String) { + displayedCharacterId.value = id + } + + fun hideCharacter() { + displayedCharacterId.value = null + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDiminishedViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDiminishedViewModel.kt new file mode 100644 index 0000000..2e85a85 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/detail/CharacterDiminishedViewModel.kt @@ -0,0 +1,49 @@ +package com.pixelized.desktop.lwa.screen.campaign.player.detail + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue +import androidx.lifecycle.ViewModel +import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.DiminishedStatDialogUio +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label +import org.jetbrains.compose.resources.getString + +class CharacterDiminishedViewModel( + private val repository: CharacterSheetRepository, +) : ViewModel() { + + private val _diminishedDialog = mutableStateOf(null) + val diminishedDialog: State get() = _diminishedDialog + + suspend fun showDiminishedDialog(id: String) { + val diminished = repository.characterDiminishedFlow(id = id).value + val textFieldValue = + mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0))) + _diminishedDialog.value = DiminishedStatDialogUio( + id = id, + label = getString(resource = Res.string.character_sheet__diminished__label), + value = { textFieldValue.value }, + onValueChange = { value -> + textFieldValue.value = when (value.text.toIntOrNull()?.takeIf { it >= 0 }) { + null -> TextFieldValue("0", selection = TextRange(index = 0)) + else -> value + } + }, + ) + } + + fun hideDiminishedDialog() { + _diminishedDialog.value = null + } + + fun changeDiminished(dialog: DiminishedStatDialogUio) { + val value = dialog.value().text.toIntOrNull() ?: 0 + repository.setDiminishedForCharacter( + id = dialog.id, + diminished = value, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerPortrait.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerPortrait.kt new file mode 100644 index 0000000..da65d18 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerPortrait.kt @@ -0,0 +1,125 @@ +package com.pixelized.desktop.lwa.screen.campaign.player.ribbon + +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.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +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.Color +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp +import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp +import org.jetbrains.compose.resources.painterResource + +@Stable +data class PlayerPortraitUio( + val id: String, + val portrait: String?, + val hp: Int, + val maxHp: Int, + val pp: Int, + val maxPp: Int, +) + +object PlayerPortrait { + object Default { + val size = DpSize(96.dp, 128.dp) + } +} + +@Composable +fun PlayerPortrait( + modifier: Modifier = Modifier, + size: DpSize = PlayerPortrait.Default.size, + character: PlayerPortraitUio, + onCharacter: (id: String) -> Unit, +) { + Box( + modifier = modifier + .size(size = size) + .clip(shape = remember { RoundedCornerShape(8.dp) }) + .clickable { onCharacter(character.id) }, + ) { + AsyncImage( + modifier = Modifier.fillMaxSize(), + model = character.portrait, + contentScale = ContentScale.Crop, + alignment = Alignment.TopCenter, + filterQuality = FilterQuality.High, + contentDescription = null, + ) + Column( + modifier = Modifier + .fillMaxSize() + .drawWithContent { + drawRect( + brush = Brush.verticalGradient( + listOf( + Color.Black.copy(alpha = 0.0f), + Color.Black.copy(alpha = 0.0f), + Color.Black.copy(alpha = 0.0f), + Color.Black.copy(alpha = 0.5f), + Color.Black.copy(alpha = 0.8f), + ) + ) + ) + drawContent() + } + .padding(all = 2.dp), + verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 2.dp), + ) { + Icon( + modifier = Modifier.size(12.dp), + painter = painterResource(Res.drawable.ic_heart_24dp), + contentDescription = null + ) + Text( + modifier = Modifier.padding(bottom = 2.dp), + style = MaterialTheme.typography.caption, + text = "${character.hp}/${character.maxHp}", + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 2.dp), + ) { + Icon( + modifier = Modifier.size(12.dp), + painter = painterResource(Res.drawable.ic_water_drop_24dp), + contentDescription = null + ) + Text( + modifier = Modifier.padding(bottom = 2.dp), + style = MaterialTheme.typography.caption, + text = "${character.pp}/${character.maxPp}", + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbon.kt new file mode 100644 index 0000000..ee10f26 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbon.kt @@ -0,0 +1,34 @@ +package com.pixelized.desktop.lwa.screen.campaign.player.ribbon + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun PlayerRibbon( + modifier: Modifier = Modifier, + playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(), + onCharacter: (id: String) -> Unit, +) { + val characters = playerRibbonViewModel.characters.collectAsState() + + LazyColumn( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(space = 8.dp) + ) { + items( + items = characters.value, + key = { it.id }, + ) { + PlayerPortrait( + character = it, + onCharacter = onCharacter, + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt new file mode 100644 index 0000000..d1a9cc6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt @@ -0,0 +1,31 @@ +package com.pixelized.desktop.lwa.screen.campaign.player.ribbon + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class PlayerRibbonViewModel( + repository: CharacterSheetRepository, +) : ViewModel() { + + val characters = repository.characterSheetFlow() + .map { sheets -> + sheets.map { sheet -> + PlayerPortraitUio( + id = sheet.id, + portrait = sheet.thumbnail, + hp = sheet.currentHp, + maxHp = sheet.maxHp, + pp = sheet.currentPp, + maxPp = sheet.maxPp, + ) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt index a8a3252..289c6a6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt @@ -197,6 +197,7 @@ class CharacterSheetViewModel( val textFieldValue = mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0))) _diminishedDialog.value = DiminishedStatDialogUio( + id = argument.id, label = getString(resource = Res.string.character_sheet__diminished__label), value = { textFieldValue.value }, onValueChange = { value -> @@ -215,7 +216,7 @@ class CharacterSheetViewModel( fun changeDiminished(dialog: DiminishedStatDialogUio) { val value = dialog.value().text.toIntOrNull() ?: 0 repository.setDiminishedForCharacter( - id = argument.id, + id = dialog.id, diminished = value, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt index 91b1e69..2063d84 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt @@ -49,6 +49,7 @@ import org.jetbrains.compose.resources.stringResource @Stable data class DiminishedStatDialogUio( + val id: String, val label: String, val value: () -> TextFieldValue, val onValueChange: (TextFieldValue) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt index eb94cc0..608e65e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt @@ -67,6 +67,8 @@ class CharacterSheetEditFactory( return CharacterSheet( id = editedSheet.id, name = editedSheet.name.value.value, + portrait = currentSheet?.portrait, + thumbnail = currentSheet?.thumbnail, strength = editedSheet.strength.unpack()?.toIntOrNull() ?: currentSheet?.strength ?: 0, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaTheme.kt index a67158b..ab00f0a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaTheme.kt @@ -1,17 +1,47 @@ package com.pixelized.desktop.lwa.theme import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Stable +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.remember +import com.pixelized.desktop.lwa.theme.color.LwaColorTheme +import com.pixelized.desktop.lwa.theme.color.darkLwaColorTheme + +val LocalLwaTheme = compositionLocalOf { + error("Local Snack Controller is not yet ready") +} + +val MaterialTheme.lwa: LwaTheme + @Composable + @Stable + get() = LocalLwaTheme.current + +@Stable +data class LwaTheme( + val color: LwaColorTheme, +) @Composable fun LwaTheme( - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { - MaterialTheme( - colors = darkColors(), - typography = MaterialTheme.typography, - shapes = MaterialTheme.shapes, - content = content, - ) + val lwaColorTheme = darkLwaColorTheme() + val theme = remember { + LwaTheme( + color = lwaColorTheme, + ) + } + + CompositionLocalProvider( + LocalLwaTheme provides theme + ) { + MaterialTheme( + colors = lwaColorTheme.base, + typography = MaterialTheme.typography, + shapes = MaterialTheme.shapes, + content = content, + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaColorPalette.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorPalette.kt similarity index 73% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaColorPalette.kt rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorPalette.kt index ca2525e..691f062 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/LwaColorPalette.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorPalette.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.theme +package com.pixelized.desktop.lwa.theme.color import androidx.compose.ui.graphics.Color diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorTheme.kt new file mode 100644 index 0000000..ff93249 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/theme/color/LwaColorTheme.kt @@ -0,0 +1,50 @@ +package com.pixelized.desktop.lwa.theme.color + +import androidx.compose.material.Colors +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.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.ln + +@Stable +data class LwaColorTheme( + val base: Colors, + val elevatedSurface: Color, +) + +@Composable +@Stable +fun darkLwaColorTheme( + base: Colors = darkColors(), + elevatedSurface: Color = base.calculateElevatedColor( + color = base.surface, + onColor = base.onSurface, + elevation = 1.dp, + ), +): LwaColorTheme = LwaColorTheme( + base = base, + elevatedSurface = elevatedSurface, +) + +@ReadOnlyComposable +@Composable +private fun Colors.calculateElevatedColor(color: Color, onColor: Color, elevation: Dp): Color { + return if (elevation > 0.dp && !isLight) { + val foregroundColor = calculateForegroundColor(onColor, elevation) + foregroundColor.compositeOver(color) + } else { + color + } +} + +@ReadOnlyComposable +@Composable +private fun calculateForegroundColor(color: Color, elevation: Dp): Color { + val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f + return color.copy(alpha = alpha) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b76ae76..60b1b57 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ ktor = "3.0.1" koin = "4.0.0" turtle = "0.5.0" logback = "1.5.11" - +coil = "3.1.0" [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } @@ -42,4 +42,7 @@ ktor-server-netty = { group = 'io.ktor', name = "ktor-server-netty", version.ref ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor" } turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" } -logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } \ No newline at end of file +logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } + +coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } \ No newline at end of file