From 35396b6069664976fccc361925a105a8404eff59 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Fri, 14 Mar 2025 15:07:48 +0100 Subject: [PATCH] Some small UI adjustment to the level UP screen & RollOverlay. --- .../composeResources/values/strings.xml | 40 +++--- .../kotlin/com/pixelized/desktop/lwa/App.kt | 40 +++--- .../desktop/lwa/DataSyncViewModel.kt | 58 +++++++++ .../com/pixelized/desktop/lwa/Module.kt | 5 +- .../lwa/ui/composable/tooltip/Tooltip.kt | 8 +- .../lwa/ui/navigation/screen/MainNavHost.kt | 9 +- .../screen/destination/MainDestination.kt | 15 +-- .../lwa/ui/overlay/roll/RollOverlay.kt | 3 +- .../lwa/ui/screen/campaign/CampaignScreen.kt | 2 - .../detail/header/CharacterDetailHeader.kt | 2 +- .../detail/sheet/CharacterDetailSheet.kt | 2 +- .../lwa/ui/screen/levelup/LevelScreen.kt | 69 ++++++++--- .../lwa/ui/screen/levelup/LevelUpFactory.kt | 115 +++++++++++++++++- .../lwa/ui/screen/levelup/LevelUpViewModel.kt | 18 +-- .../levelup/items/LevelUpCharacteristic.kt | 6 +- .../ui/screen/levelup/items/LevelUpSkill.kt | 104 +++++++++------- .../levelup/items/LevelUpSubCharacteristic.kt | 99 +++++++++++++++ .../lwa/ui/screen/network/NetworkScreen.kt | 8 +- .../lwa/ui/screen/network/NetworkViewModel.kt | 33 +++-- .../desktop/lwa/ui/theme/size/LwaSize.kt | 20 ++- .../desktop/lwa/utils/InstallCoil.kt | 28 +++++ .../kotlin/com/pixelized/desktop/lwa/main.kt | 2 +- .../shared/lwa/utils/PathProvider.kt | 10 ++ 23 files changed, 530 insertions(+), 166 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSubCharacteristic.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/InstallCoil.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 533e975..6361bbf 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1,6 +1,8 @@ + + La feuille de personnage est introuvable Ok @@ -112,36 +114,20 @@ Êtes-vous sûr de vouloir supprimer "%1$s" ? Les caractéristiques constituent les aptitudes innées d’un personnage comme son intelligence, sa force, son charisme, etc. Elles ne sont pas acquises, mais peuvent être parfois augmentées par un entraînement ou une utilisation réussie. Les caractéristiques des humains normaux varient de 2 (niveau extrêmement bas) à 20 (maximum du potentiel humain), avec une moyenne de 10 ou 11. Plus une caractéristique est élevée plus le personnage est puissant dans cette aptitude.\nÀ la création de votre personnage, répartissez les valeurs suivantes dans les différentes caractéristiques : 15, 15, 13, 11, 10, 9 et 7. - La Force représente essentiellement la puissance musculaire du personnage. Elle ne décrit pas nécessairement la masse musculaire brute, mais l’efficacité avec laquelle le personnage exerce ses muscles pour accomplir des actions physiques pénibles. - La Constitution est une mesure de la ténacité et de la résilience du personnage. Elle sert à résister aux maladies. Mais son aspect le plus important réside dans la détermination du nombre de dommages qu’un personnage peut supporter avant de succomber. - La Taille est une mesure de la masse du personnage. Elle ne représente pas forcément la taille en centimètres, mais une idée générale de sa masse physique. Un personnage à la TAI élevée peut être très grand et mince, ou petit et massif, ou de taille moyenne en surpoids. - L’Intelligence représente la capacité de discernement du personnage. Elle ne mesure pas nécessairement la somme d’informations mémorisées, mais l’aptitude au raisonnement, l’acuité intellectuelle, la capacité à résoudre des problèmes et l’intuition. - Le Pouvoir est une mesure presque intangible de la force de volonté, du dynamisme intérieur et de l’énergie spirituelle. Il représente également le potentiel magique du personnage. - La Dextérité mesure la coordination œil-main, la vitesse physique et l’agilité générale. La DEX détermine à quelle vitesse un personnage peut agir en combat. - Il représente plusieurs aspects allant de la grâce à la beauté en passant par l’attraction que le personnage exerce sur les autres. Un personnage avec un CHA élevé se remarque dans une foule en raison d’une intangible combinaison de charme et de présence. + La Force représente essentiellement la puissance musculaire du personnage. Elle ne décrit pas nécessairement la masse musculaire brute, mais l’efficacité avec laquelle le personnage exerce ses muscles pour accomplir des actions physiques pénibles.\n\n- Bonus aux dégats\n- Athlétisme\n- Lancer\n- Saisie + La Constitution est une mesure de la ténacité et de la résilience du personnage. Elle sert à résister aux maladies. Mais son aspect le plus important réside dans la détermination du nombre de dommages qu’un personnage peut supporter avant de succomber.\n\n- Point de vie maximum\n- Athlétisme\n- Acrobatie + La Taille est une mesure de la masse du personnage. Elle ne représente pas forcément la taille en centimètres, mais une idée générale de sa masse physique. Un personnage à la TAI élevée peut être très grand et mince, ou petit et massif, ou de taille moyenne en surpoids.\n\n- Bonus aux dégats\n- Point de vie maximum\n- Saisie\n- Intimidation\n- Discrétion (impact négatif) + L’Intelligence représente la capacité de discernement du personnage. Elle ne mesure pas nécessairement la somme d’informations mémorisées, mais l’aptitude au raisonnement, l’acuité intellectuelle, la capacité à résoudre des problèmes et l’intuition.\n\n- Bonus d'apprentissage\n- Perception\n- Recherche\n- Empathie\n- Baratin\n- Premiers soins + Le Pouvoir est une mesure presque intangible de la force de volonté, du dynamisme intérieur et de l’énergie spirituelle. Il représente également le potentiel magique du personnage.\n\n- Points de pouvoir maximum\n- Intimidation + La Dextérité mesure la coordination œil-main, la vitesse physique et l’agilité générale. La DEX détermine à quelle vitesse un personnage peut agir en combat.\n\n- Initiative\n- Bagarre\n- Esquive\n- Lancer\n- Acrobatie\n- Discrétion\n- Escamotage\n- Premiers soins + Il représente plusieurs aspects allant de la grâce à la beauté en passant par l’attraction que le personnage exerce sur les autres. Un personnage avec un CHA élevé se remarque dans une foule en raison d’une intangible combinaison de charme et de présence.\n\n- Empathie\n- Persuasion\n- Persuasion\n- Intimidation\n- Baratin\n- Marchandage\n- Discrétion Le Déplacement (DEP) est une valeur de jeu qui détermine la distance que peut parcourir un personnage en un round de combat. Tous les humains ont un DEP de 10. Le DEP a une valeur réelle flexible, mais généralement, chaque point de DEP équivaut à un déplacement d’un mètre. En course, un point équivaut à trois mètres. Les points de vie (PV) sont égaux à la somme CON+TAI du personnage, divisée par deux (arrondie au supérieur). Ils sont soustraits lorsque le personnage subit des dommages. Quand les points de vie tombent à 0, le personnage sombre dans l’inconscience. S'il reste inconscient trop longtemps, il meurt. Tous les points de vie régénèrent naturellement après une nuit de repos. les points de pouvoir sont égaux au POU et sont dépensés pour utiliser la magie ou d’autres pouvoirs. Tous les points de pouvoir régénèrent naturellement après une nuit de repos. - Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis en combat au corps à corps. Le modificateur s’applique aux dégâts infligés par toute attaque portée par les personnages avec des armes de mêlée (BDC). La moitié de ce bonus s'applique aux attaques de lancer (BDD). + Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis. Le modificateur s’applique aux dégâts infligés par toute attaque portée par les personnages avec des armes de mêlée (BDC). La moitié de ce bonus s'applique aux attaques de lancer (BDD).\n\nBonus aux dégâts : Force + Taille\n- 2 à 12 : -1d6\n- 12 à 17 : -1d4\n- 18 à 22 : 0\n- 23 à 29 : +1d4\n- 30 à 39 : +1d6\n- Au delà : +2d6 Une armure protège son porteur des blessures. Lorsqu’un personnage est touché en combat par une attaque non magique, soustrayez les points d’armure aux points de dégâts infligés. Les dommages au-delà de la protection de l’armure surpassent celle-ci et sont infligés au personnage, réduisant ses points de vie actuels. Plus un personnage est intelligent, plus il assimile rapidement les connaissances et plus il digère son expérience efficacement. Ce modificateur s'applique au score des compétences nouvellement acquises et aux jets d'expériences. Il est égal à (INT - 10) x 2 avec une valeur minimale de zéro. Plus un personnage est de bonne constitution, plus son corps se renforce rapidement. Ce modificateur indique le nombre de "PV" maximum que le personnage gagne à chaque progression. Il est égal à CON / 3, arrondi à l'inférieur. - Attaque en combat à mains nues. Une attaque réussie inflige 1D3 + BDGT. - Éviter une attaque, un projectile, etc. - Maitriser/immobiliser un adversaire. - Viser et lancer quelque chose à travers les airs et vers une cible. - Réaliser un mouvement nécessitant une certaine force (Enfoncer une porte, nager à contre-courant, etc.). - Réaliser un mouvement nécessitant une certaine agilité (Marcher sur une corde, courir sur la glace, etc.). - Aptitude à percevoir via ses sens ou à percevoir un détail difficile à remarquer. - Aptitude à trouver des indices et à émettre des suppositions sur la base de ceux-ci. - Évaluer les pensées et/ou les motivations cachées d’un autre personnage en se basant sur des signaux subliminaux. - Influencer quelqu'un ou un groupe de personnes avec tact, grâce, ou de bonnes manières. - Influencer quelqu'un ou un groupe de personnes par des menaces, des actions hostiles, ou de la violence physique. - Mentir ou cacher la vérité avec efficacité, soit verbalement, soit par vos actions. - Négocier les prix. - Passer inaperçu. - Réaliser une manipulation ou une fourberie, comme prendre quelque chose à quelqu'un ou dissimuler un objet sur vous-même. - Traiter les blessures légères. Une réussite rétablit 1D3 points de vie. Une réussite spéciale restaure 1D3+3 points de vie. Configuration de la table Nom du joueur @@ -156,6 +142,7 @@ Serveur Client Aucun + Un nom de joueur est requis Vous êtes connecté au serveur Vous êtes déconnecté du serveur Ok @@ -186,7 +173,10 @@ Montée de niveau Level Up ! - Passage du niveau %1$d au niveau supérieur : %2$d + niv : %1$d▸%2$d + Passage du niveau %1$d ▸ %2$d niv : %1$d - + + \ 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 1fa5ce5..f3f7ddc 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -32,9 +32,6 @@ 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 coil3.ImageLoader -import coil3.compose.setSingletonImageLoaderFactory -import coil3.request.crossfade import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent @@ -53,17 +50,18 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheet import com.pixelized.desktop.lwa.ui.navigation.window.destination.NetworkWindows 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.overlay.roll.RollHostState +import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel -import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState -import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.ui.theme.LwaTheme +import com.pixelized.desktop.lwa.utils.InstallCoil import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import lwacharactersheet.composeapp.generated.resources.Res @@ -102,7 +100,7 @@ val LocalBlurController = compositionLocalOf { @Composable @Preview -fun ApplicationScope.App() { +fun ApplicationScope.LwaApplication() { val maxWindowHeight = rememberMaxWindowHeight() val snackHostState = remember { SnackbarHostState() } val errorSnackHostState = remember { SnackbarHostState() } @@ -120,12 +118,13 @@ fun ApplicationScope.App() { ), ) - // Coil configuration - setSingletonImageLoaderFactory { context -> - ImageLoader.Builder(context) - .crossfade(enable = false) - .build() - } + // Coil configuration. + InstallCoil( + crossfade = false, + diskCachePath = { provider -> + provider.imagesStorePath() + } + ) CompositionLocalProvider( LocalApplicationScope provides this, @@ -150,16 +149,18 @@ fun ApplicationScope.App() { } } + @Composable private fun MainWindowScreen( - campaignViewModel: CampaignViewModel = koinViewModel(), + dataSyncViewModel: DataSyncViewModel = koinViewModel(), networkViewModel: NetworkViewModel = koinViewModel(), + campaignViewModel: CampaignViewModel = koinViewModel(), campaignChatViewModel: CampaignChatViewModel = koinViewModel(), rollViewModel: RollHistoryViewModel = koinViewModel(), ) { LaunchedEffect(Unit) { - networkViewModel.connect() - campaignViewModel.init() + dataSyncViewModel.autoConnect() + dataSyncViewModel.synchronise() } val snackHostState = LocalSnackHost.current @@ -205,15 +206,12 @@ private fun MainWindowScreen( content = { BlurContent( modifier = Modifier.fillMaxSize(), - controller = blurController + controller = blurController, ) { - MainNavHost( - campaignViewModel = campaignViewModel, - networkViewModel = networkViewModel, - campaignChatViewModel = campaignChatViewModel, - ) + MainNavHost() } RollOverlay( + modifier = Modifier.fillMaxSize(), hostState = rollHostState, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt new file mode 100644 index 0000000..4eaa5a8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt @@ -0,0 +1,58 @@ +package com.pixelized.desktop.lwa + +import androidx.lifecycle.ViewModel +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 com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.repository.settings.SettingsRepository +import com.pixelized.shared.lwa.model.campaign.Campaign +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class DataSyncViewModel( + private val characterRepository: CharacterSheetRepository, + private val alterationRepository: AlterationRepository, + private val campaignRepository: CampaignRepository, + private val settingsRepository: SettingsRepository, + private val networkRepository: NetworkRepository, +) : ViewModel() { + + fun autoConnect() { + val settings = settingsRepository.settings() + + if (settings.playerName.isEmpty()) return + + networkRepository.connect( + host = settings.host, + port = settings.port, + ) + } + + suspend fun synchronise() = coroutineScope { + + networkRepository.status + .filter { status -> status == NetworkRepository.Status.CONNECTED } + .onEach { campaignRepository.update() } + .launchIn(this) + + networkRepository.status + .filter { status -> status == NetworkRepository.Status.CONNECTED } + .combine(campaignRepository.campaignFlow) { _, campaign: Campaign -> campaign } + .onEach { campaign -> + campaign.characters.keys.forEach { id -> + characterRepository.characterDetail( + characterSheetId = id.characterSheetId, + forceUpdate = true, + ) + alterationRepository.updateActiveAlterations( + characterInstanceId = id, + ) + } + } + .launchIn(this) + } +} \ No newline at end of file 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 c4b528d..6cd6669 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -15,8 +15,8 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsStore import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory +import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel -import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory @@ -30,10 +30,10 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEdi import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditViewModel import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory +import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel import com.pixelized.desktop.lwa.ui.screen.main.MainPageViewModel import com.pixelized.desktop.lwa.ui.screen.network.NetworkFactory import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel -import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel import com.pixelized.desktop.lwa.usecase.SettingsUseCase @@ -116,6 +116,7 @@ val factoryDependencies val viewModelDependencies get() = module { + viewModelOf(::DataSyncViewModel) viewModelOf(::CampaignViewModel) viewModelOf(::MainPageViewModel) viewModelOf(::CharacterSheetViewModel) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/Tooltip.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/Tooltip.kt index bfe6ee5..467cfd6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/Tooltip.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/Tooltip.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -40,7 +41,12 @@ fun TooltipLayout( else -> TooltipArea( modifier = modifier, - tooltip = { Tooltip(tooltip = tooltip) }, + tooltip = { + Tooltip( + modifier = Modifier.width(width = 448.dp), + tooltip = tooltip, + ) + }, content = content, delayMillis = delayMillis, tooltipPlacement = tooltipPlacement, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt index 46d8789..2d326ad 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt @@ -23,9 +23,6 @@ val LocalScreenController = compositionLocalOf { @Composable fun MainNavHost( controller: NavHostController = rememberNavController(), - campaignViewModel: CampaignViewModel, - networkViewModel: NetworkViewModel, - campaignChatViewModel: CampaignChatViewModel, startDestination: String = MainDestination.navigationRoute(), ) { CompositionLocalProvider( @@ -35,11 +32,7 @@ fun MainNavHost( navController = controller, startDestination = startDestination, ) { - composableMainPage( - campaignViewModel = campaignViewModel, - networkViewModel = networkViewModel, - campaignChatViewModel = campaignChatViewModel, - ) + composableMainPage() composableSettingsPage() composableLevelUp() diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt index 2b7f3f8..5e33a2a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt @@ -3,10 +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.campaign.CampaignViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignScreen -import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel -import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel object MainDestination { private const val ROUTE = "main" @@ -15,19 +12,11 @@ object MainDestination { fun navigationRoute() = ROUTE } -fun NavGraphBuilder.composableMainPage( - campaignViewModel: CampaignViewModel, - networkViewModel: NetworkViewModel, - campaignChatViewModel: CampaignChatViewModel, -) { +fun NavGraphBuilder.composableMainPage() { composable( route = MainDestination.baseRoute(), ) { - CampaignScreen( - campaignViewModel = campaignViewModel, - networkViewModel = networkViewModel, - campaignChatViewModel = campaignChatViewModel, - ) + CampaignScreen() } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/roll/RollOverlay.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/roll/RollOverlay.kt index 160a91c..800e2ee 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/roll/RollOverlay.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/roll/RollOverlay.kt @@ -25,6 +25,7 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun RollOverlay( viewModel: RollViewModel = koinViewModel(), + modifier: Modifier = Modifier, hostState: RollHostState, ) { val blur = LocalBlurController.current @@ -43,7 +44,7 @@ fun RollOverlay( } AnimatedContent( - modifier = Modifier.fillMaxSize(), + modifier = modifier, targetState = hostState.rollAction.value, transitionSpec = { val enter = fadeIn() + slideInVertically { 64 } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt index 4064fe6..bb3bf52 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt @@ -61,13 +61,11 @@ fun CampaignScreen( campaignViewModel: CampaignViewModel = koinViewModel(), networkViewModel: NetworkViewModel = koinViewModel(), campaignChatViewModel: CampaignChatViewModel = koinViewModel(), - rollViewModel: RollViewModel = koinViewModel(), ) { val screen = LocalScreenController.current val blurController = rememberBlurContentController() val scope = rememberCoroutineScope() - KeyHandler { when { it.type == KeyEventType.KeyUp && it.key == Key.Escape -> { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt index 07b157f..34d59c4 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt @@ -80,7 +80,7 @@ data class CharacterDetailHeaderUio( @Composable fun CharacterDetailHeader( modifier: Modifier = Modifier, - iconSize: Dp = 14.dp, + iconSize: Dp = MaterialTheme.lwa.size.sheet.subCategory, header: State, onDismissRequest: () -> Unit, onDiminished: () -> Unit, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt index 6a273e1..fec8e53 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt @@ -52,7 +52,7 @@ fun CharacterDetailSheet( ) { sheet.value?.characteristics?.forEach { CharacterDetailSheetCharacteristic( - modifier = Modifier.size(size = MaterialTheme.lwa.size.characteristic), + modifier = Modifier.size(size = MaterialTheme.lwa.size.sheet.characteristic), characteristic = it, onClick = { onCharacteristic(it) }, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt index 21e8bc9..213f817 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt @@ -14,6 +14,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -55,6 +57,7 @@ import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import coil3.compose.AsyncImage @@ -68,6 +71,8 @@ import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristic import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkill import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkillUio +import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSubCharacteristic +import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSubCharacteristicUio import com.pixelized.desktop.lwa.ui.theme.lwa import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res @@ -75,7 +80,7 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet__skills_ import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title import lwacharactersheet.composeapp.generated.resources.level_up__action -import lwacharactersheet.composeapp.generated.resources.level_up__character_level_description +import lwacharactersheet.composeapp.generated.resources.level_up__character__label import lwacharactersheet.composeapp.generated.resources.level_up__title import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource @@ -86,6 +91,11 @@ data class LevelUpHeaderUio( val name: String, val level: Int, val portrait: String?, + val hp: LevelUpSubCharacteristicUio, + val pp: LevelUpSubCharacteristicUio, + val bonus: LevelUpSubCharacteristicUio, + val grow: LevelUpSubCharacteristicUio, + val learn: LevelUpSubCharacteristicUio, ) @Stable @@ -217,18 +227,49 @@ private fun LevelUpContent( .padding(horizontal = 16.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(space = 8.dp), ) { - Text( - style = MaterialTheme.lwa.typography.base.h5, - text = header.value?.name ?: "" - ) - Text( - style = MaterialTheme.lwa.typography.base.body1, - text = (header.value?.level ?: 0).let { - stringResource( - Res.string.level_up__character_level_description, it, it + 1 - ) - }, - ) + Row( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.h5, + text = header.value?.name ?: "", + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.lwa.typography.base.caption, + text = (header.value?.level ?: 0).let { level -> + stringResource( + resource = Res.string.level_up__character__label, + level, level + 1 + ) + }, + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(space = 16.dp), + ) { + LevelUpSubCharacteristic( + characteristic = header.value?.hp, + ) + LevelUpSubCharacteristic( + characteristic = header.value?.pp, + ) + Spacer( + modifier = Modifier.weight(weight = 1f) + ) + LevelUpSubCharacteristic( + characteristic = header.value?.bonus, + ) + LevelUpSubCharacteristic( + characteristic = header.value?.grow, + ) + LevelUpSubCharacteristic( + characteristic = header.value?.learn, + ) + } } Column( modifier = Modifier.verticalScroll(state = scrollState), @@ -251,7 +292,7 @@ private fun LevelUpContent( key = { it.characteristicId } ) { LevelUpCharacteristic( - modifier = Modifier.size(size = MaterialTheme.lwa.size.characteristic), + modifier = Modifier.size(size = MaterialTheme.lwa.size.sheet.characteristic), characteristic = it, onClick = { onCharacteristic(it) }, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpFactory.kt index 245a377..55911d4 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpFactory.kt @@ -5,10 +5,14 @@ import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction.RollActionUio import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkillUio +import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSubCharacteristicUio import com.pixelized.shared.lwa.model.AlteredCharacterSheet import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory +import com.pixelized.shared.lwa.model.alteration.Alteration +import com.pixelized.shared.lwa.model.alteration.FieldAlteration import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet +import com.pixelized.shared.lwa.parser.expression.Expression import com.pixelized.shared.lwa.usecase.ExpressionUseCase import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__cha @@ -18,6 +22,20 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet__charact import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__int import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__pow import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__str +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hp_grow +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__learning +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement +import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point +import lwacharactersheet.composeapp.generated.resources.ic_cognition_24dp +import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp +import lwacharactersheet.composeapp.generated.resources.ic_heart_plus_24dp +import lwacharactersheet.composeapp.generated.resources.ic_near_me +import lwacharactersheet.composeapp.generated.resources.ic_shield_24dp +import lwacharactersheet.composeapp.generated.resources.ic_swords_24dp +import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__charisma import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__constitution import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__dexterity @@ -25,6 +43,13 @@ import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__intelligence import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__power import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__strength +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hp_grow +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__learning +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement +import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point import org.jetbrains.compose.resources.getString class LevelUpFactory( @@ -32,14 +57,96 @@ class LevelUpFactory( private val expressionUseCase: ExpressionUseCase, ) { - fun convertToLevelUpHeaderUio( + suspend fun convertToLevelUpHeaderUio( characterSheet: CharacterSheet?, + selectedCharacteristicId: String?, ): LevelUpHeaderUio? { if (characterSheet == null) return null + + val levelAlteration = FieldAlteration( + alterationId = CharacterSheet.CharacteristicId.LVL, + metadata = Alteration.MetaData( + name = "LevelUp-${CharacterSheet.CharacteristicId.LVL}", + description = "Fake alteration for a levelUp simulation. Alter the ${CharacterSheet.CharacteristicId.LVL} stat." + ), + expression = Expression.Flat(1), + ) + + val alteredCharacterSheet = alteredCharacterSheetFactory.sheet( + characterSheet = characterSheet, + alterations = emptyMap(), + ) + val levelUpCharacterSheet = alteredCharacterSheetFactory.sheet( + characterSheet = characterSheet, + alterations = selectedCharacteristicId + ?.let { + val characteristic = FieldAlteration( + alterationId = it, + metadata = Alteration.MetaData( + name = "LevelUp-$it", + description = "Fake alteration for a levelUp simulation. Alter the $it stat." + ), + expression = Expression.Flat(1), + ) + mapOf( + CharacterSheet.CharacteristicId.LVL to listOf(levelAlteration), + it to listOf(characteristic), + ) + } + ?: mapOf( + CharacterSheet.CharacteristicId.LVL to listOf(levelAlteration), + ), + ) + return LevelUpHeaderUio( name = characterSheet.name, portrait = characterSheet.portrait, level = characterSheet.level, + hp = LevelUpSubCharacteristicUio( + icon = Res.drawable.ic_heart_24dp, + base = "${alteredCharacterSheet.maxHp}", + levelUp = "${levelUpCharacterSheet.maxHp}", + tooltip = TooltipUio( + title = getString(Res.string.character_sheet__sub_characteristics__hit_point), + description = getString(Res.string.tooltip__sub_characteristics__hit_point) + ), + ), + pp = LevelUpSubCharacteristicUio( + icon = Res.drawable.ic_water_drop_24dp, + base = "${alteredCharacterSheet.maxPp}", + levelUp = "${levelUpCharacterSheet.maxPp}", + tooltip = TooltipUio( + title = getString(Res.string.character_sheet__sub_characteristics__power_point), + description = getString(Res.string.tooltip__sub_characteristics__power_point) + ) + ), + bonus = LevelUpSubCharacteristicUio( + icon = Res.drawable.ic_swords_24dp, + base = alteredCharacterSheet.damageBonus, + levelUp = levelUpCharacterSheet.damageBonus, + tooltip = TooltipUio( + title = getString(Res.string.character_sheet__sub_characteristics__damage_bonus), + description = getString(Res.string.tooltip__sub_characteristics__bonus_damage) + ), + ), + grow = LevelUpSubCharacteristicUio( + icon = Res.drawable.ic_heart_plus_24dp, + base = "${alteredCharacterSheet.hpGrow}", + levelUp = "${levelUpCharacterSheet.hpGrow}", + tooltip = TooltipUio( + title = getString(Res.string.character_sheet__sub_characteristics__hp_grow), + description = getString(Res.string.tooltip__sub_characteristics__hp_grow) + ), + ), + learn = LevelUpSubCharacteristicUio( + icon = Res.drawable.ic_cognition_24dp, + base = "${alteredCharacterSheet.learning}", + levelUp = "${levelUpCharacterSheet.learning}", + tooltip = TooltipUio( + title = getString(Res.string.character_sheet__sub_characteristics__learning), + description = getString(Res.string.tooltip__sub_characteristics__learning) + ), + ) ) } @@ -204,6 +311,12 @@ class LevelUpFactory( level = skill.level, levelUp = results.isSkillLeveledUp(skillId = skill.id), occupation = skill.occupation, + tooltips = skill.description?.let { + TooltipUio( + title = skill.label, + description = it, + ) + }, roll = when (results[skill.id]) { null -> RollActionUio( characterInstanceId = characterInstanceId, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpViewModel.kt index 1677700..16ca8ab 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelUpViewModel.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.error__default__action @@ -35,14 +34,15 @@ class LevelUpViewModel( private val results = MutableStateFlow>(emptyMap()) private val selectedCharacteristicId = MutableStateFlow(null) - val header: StateFlow = characterSheetRepository - .characterDetailFlow(characterSheetId = argument.characterInstanceId.characterSheetId) - .map(levelUpFactory::convertToLevelUpHeaderUio) - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = null, - ) + val header = combine( + characterSheetRepository.characterDetailFlow(characterSheetId = argument.characterInstanceId.characterSheetId), + selectedCharacteristicId, + levelUpFactory::convertToLevelUpHeaderUio + ).stateIn( + scope = viewModelScope, + started = SharingStarted.Lazily, + initialValue = null, + ) val characteristics = combine( characterSheetRepository.characterDetailFlow(characterSheetId = argument.characterInstanceId.characterSheetId), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpCharacteristic.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpCharacteristic.kt index 59a9fd8..6c20682 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpCharacteristic.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpCharacteristic.kt @@ -64,11 +64,7 @@ fun LevelUpCharacteristic( .fillMaxWidth() .align(alignment = Alignment.Center), targetState = characteristic.selected, - transitionSpec = { - val enter = fadeIn() + slideInVertically { -16 } - val exit = fadeOut() + slideOutVertically { 16 } - enter togetherWith exit using SizeTransform(clip = false) - }, + transitionSpec = { fadeIn() togetherWith fadeOut() }, ) { Text( modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSkill.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSkill.kt index 23c10c5..718b642 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSkill.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSkill.kt @@ -8,6 +8,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -23,6 +24,8 @@ 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.ui.composable.shapes.MasteryShape +import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout +import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction.RollActionUio import com.pixelized.desktop.lwa.ui.theme.lwa import lwacharactersheet.composeapp.generated.resources.Res @@ -37,9 +40,11 @@ data class LevelUpSkillUio( val level: Int, val levelUp: Boolean, val occupation: Boolean, + val tooltips: TooltipUio?, val roll: RollActionUio?, ) +@OptIn(ExperimentalFoundationApi::class) @Composable fun LevelUpSkill( modifier: Modifier = Modifier, @@ -53,7 +58,7 @@ fun LevelUpSkill( else -> MaterialTheme.lwa.colorScheme.base.primary } ) - Row( + TooltipLayout( modifier = Modifier .let { when (skill.roll) { @@ -63,60 +68,65 @@ fun LevelUpSkill( } .padding(paddingValues = paddingValues) .then(other = modifier), - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, + tooltip = skill.tooltips, ) { - MasteryShape( - modifier = Modifier.padding(top = 4.dp), - multiplier = if (skill.occupation) 1 else 0, - ) Row( - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Text( - modifier = Modifier.alignByBaseline().weight(1f), - style = MaterialTheme.typography.body1, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - text = skill.label + MasteryShape( + modifier = Modifier.padding(top = 4.dp), + multiplier = if (skill.occupation) 1 else 0, ) - AnimatedContent( - targetState = skill.levelUp, - transitionSpec = { - val enter = fadeIn() + slideInVertically { -16 } - val exit = fadeOut() + slideOutVertically { 16 } - enter togetherWith exit using SizeTransform(clip = false) - } + Row( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), ) { - Row( - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + Text( + modifier = Modifier.alignByBaseline().weight(1f), + style = MaterialTheme.typography.body1, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = skill.label + ) + AnimatedContent( + targetState = skill.levelUp, + transitionSpec = { + val enter = fadeIn() + slideInVertically { -16 } + val exit = fadeOut() + slideOutVertically { 16 } + enter togetherWith exit using SizeTransform(clip = false) + } ) { - Text( - modifier = Modifier.alignByBaseline(), - style = MaterialTheme.typography.caption, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - text = stringResource( - resource = Res.string.level_up__skill_level, - when (it) { - true -> skill.level + 1 - else -> skill.level + Row( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.caption, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = stringResource( + resource = Res.string.level_up__skill_level, + when (it) { + true -> skill.level + 1 + else -> skill.level + }, + ) + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Bold, + color = when (it) { + true -> MaterialTheme.lwa.colorScheme.base.secondary + else -> valueColor.value + }, + text = when (it) { + true -> "${skill.value + 5}" + else -> "${skill.value}" }, ) - ) - Text( - modifier = Modifier.alignByBaseline(), - style = MaterialTheme.typography.body1, - fontWeight = FontWeight.Bold, - color = when (it) { - true -> MaterialTheme.lwa.colorScheme.base.secondary - else -> valueColor.value - }, - text = when (it) { - true -> "${skill.value + 5}" - else -> "${skill.value}" - }, - ) + } } } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSubCharacteristic.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSubCharacteristic.kt new file mode 100644 index 0000000..8cbb9b7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/items/LevelUpSubCharacteristic.kt @@ -0,0 +1,99 @@ +package com.pixelized.desktop.lwa.ui.screen.levelup.items + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout +import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio +import com.pixelized.desktop.lwa.ui.theme.lwa +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.generic__arrow +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + + +@Stable +data class LevelUpSubCharacteristicUio( + val icon: DrawableResource, + val base: String, + val levelUp: String, + val tooltip: TooltipUio, +) + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun LevelUpSubCharacteristic( + modifier: Modifier = Modifier, + characteristic: LevelUpSubCharacteristicUio?, +) { + TooltipLayout( + modifier = modifier, + tooltip = characteristic?.tooltip, + ) { + Row( + verticalAlignment = Alignment.Bottom, + ) { + if (characteristic?.icon != null) { + Icon( + modifier = Modifier + .padding(bottom = 4.dp, end = 2.dp) + .size(size = MaterialTheme.lwa.size.sheet.subCategory), + painter = painterResource(characteristic.icon), + contentDescription = null + ) + } else { + Box( + modifier = Modifier + .padding(bottom = 4.dp, end = 2.dp) + .size(size = MaterialTheme.lwa.size.sheet.subCategory), + ) + } + Text( + modifier = Modifier.padding(bottom = 3.dp), + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Thin, + text = characteristic?.base ?: "", + ) + Text( + modifier = Modifier.padding(bottom = 3.dp), + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Thin, + text = stringResource(Res.string.generic__arrow), + ) + AnimatedContent( + targetState = characteristic?.levelUp ?: "", + transitionSpec = { + val enter = fadeIn() + slideInVertically { -16 } + val exit = fadeOut() + slideOutVertically { 16 } + enter togetherWith exit using SizeTransform(clip = false) + }, + ) { + Text( + style = MaterialTheme.typography.h6, + color = MaterialTheme.lwa.colorScheme.base.secondary, + fontWeight = FontWeight.Bold, + text = it, + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt index b88a921..ec766bd 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt @@ -41,6 +41,7 @@ import androidx.compose.runtime.LaunchedEffect 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.text.style.TextOverflow @@ -50,6 +51,7 @@ import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnack import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController import com.pixelized.desktop.lwa.ui.theme.lwa +import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp import lwacharactersheet.composeapp.generated.resources.network__host__label @@ -103,6 +105,7 @@ fun NetworkScreen( ) { val screen = LocalScreenController.current val snack = LocalSnackHost.current + val scope = rememberCoroutineScope() Surface( modifier = Modifier.fillMaxSize(), @@ -148,7 +151,7 @@ fun NetworkScreen( onResetPortChange = viewModel::onResetPortChange, onPortChange = viewModel::onPortChange, onResetHostChange = viewModel::onResetHostChange, - onConnect = viewModel::connect, + onConnect = { scope.launch { viewModel.connect() } }, onDisconnect = viewModel::disconnect, ) } @@ -188,6 +191,7 @@ fun NetworkPage( viewModel: NetworkViewModel = koinViewModel(), ) { val snack = LocalSnackHost.current + val scope = rememberCoroutineScope() Box( modifier = modifier, @@ -205,7 +209,7 @@ fun NetworkPage( onResetHostChange = viewModel::onResetHostChange, onPortChange = viewModel::onPortChange, onResetPortChange = viewModel::onResetPortChange, - onConnect = viewModel::connect, + onConnect = { scope.launch { viewModel.connect() } }, onDisconnect = viewModel::disconnect, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt index d9d929d..0b521fa 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt @@ -1,5 +1,6 @@ package com.pixelized.desktop.lwa.ui.screen.network +import androidx.compose.material.SnackbarDuration import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel @@ -9,7 +10,6 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -20,6 +20,10 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.network__message__action +import lwacharactersheet.composeapp.generated.resources.network__require__player_name +import org.jetbrains.compose.resources.getString class NetworkViewModel( private val settingsRepository: SettingsRepository, @@ -64,11 +68,13 @@ class NetworkViewModel( ) init { - settingsRepository.settingsFlow().onEach { - nameFlow.value = it.playerName - hostFlow.value = it.host - portFlow.value = it.port - }.launchIn(viewModelScope) + settingsRepository.settingsFlow() + .onEach { + nameFlow.value = it.playerName + hostFlow.value = it.host + portFlow.value = it.port + } + .launchIn(viewModelScope) } fun onPlayerNameChange(player: String) { @@ -91,7 +97,7 @@ class NetworkViewModel( hostFlow.value = settings.host } - fun connect() { + suspend fun connect() { blurController.show() _isLoading.value = true @@ -109,6 +115,19 @@ class NetworkViewModel( ) } + if (nameFlow.value.isBlank()) { + _networkError.emit( + ErrorSnackUio( + message = getString(Res.string.network__require__player_name), + action = getString(Res.string.network__message__action), + duration = SnackbarDuration.Short, + ) + ) + blurController.hide() + _isLoading.value = false + return + } + networkRepository.connect( host = hostFlow.value, port = portFlow.value, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt index 9149318..d036772 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt @@ -3,20 +3,30 @@ package com.pixelized.desktop.lwa.ui.theme.size import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -@Stable + data class LwaSize( - val characteristic: DpSize, -) + val sheet: Sheet, +) { + @Stable + data class Sheet( + val subCategory: Dp, + val characteristic: DpSize, + ) +} @Composable @Stable fun lwaSize( - characteristic: DpSize = DpSize(width = 76.dp, height = 110.dp), + sheet: LwaSize.Sheet = LwaSize.Sheet( + subCategory = 14.dp, + characteristic = DpSize(width = 76.dp, height = 110.dp), + ), ) = remember { LwaSize( - characteristic = characteristic, + sheet = sheet, ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/InstallCoil.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/InstallCoil.kt new file mode 100644 index 0000000..6cfeb8a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/InstallCoil.kt @@ -0,0 +1,28 @@ +package com.pixelized.desktop.lwa.utils + +import androidx.compose.runtime.Composable +import coil3.ImageLoader +import coil3.compose.setSingletonImageLoaderFactory +import coil3.disk.DiskCache +import coil3.request.crossfade +import com.pixelized.shared.lwa.utils.PathProvider +import okio.Path.Companion.toPath +import org.koin.compose.koinInject + +@Composable +fun InstallCoil( + pathProvider: PathProvider = koinInject(), + crossfade: Boolean, + diskCachePath: (PathProvider) -> String, +) { + setSingletonImageLoaderFactory { context -> + ImageLoader.Builder(context) + .crossfade(enable = crossfade) + .diskCache { + DiskCache.Builder() + .directory(directory = diskCachePath(pathProvider).toPath()) + .build() + } + .build() + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt index 550220c..8528159 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt @@ -13,7 +13,7 @@ fun main() { } application { KoinContext { - App() + LwaApplication() } } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/utils/PathProvider.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/utils/PathProvider.kt index 6bd0036..a400d12 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/utils/PathProvider.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/utils/PathProvider.kt @@ -14,6 +14,16 @@ class PathProvider( } } + fun imagesStorePath( + os: OperatingSystem = this.operatingSystem, + app: String = this.appName, + ): String { + return when (os) { + OperatingSystem.Windows -> "${storePath(os = os, app = app)}_images\\" + OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}_images/" + } + } + fun characterStorePath( os: OperatingSystem = this.operatingSystem, app: String = this.appName,