Some small UI adjustment to the level UP screen & RollOverlay.

This commit is contained in:
Thomas Andres Gomez 2025-03-14 15:07:48 +01:00
parent fce085f70d
commit 35396b6069
23 changed files with 530 additions and 166 deletions

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<resources> <resources>
<string name="generic__arrow">&#x25B8;</string>
<string name="error__missing_character_sheet__label">La feuille de personnage est introuvable</string> <string name="error__missing_character_sheet__label">La feuille de personnage est introuvable</string>
<string name="error__default__action">Ok</string> <string name="error__default__action">Ok</string>
@ -112,36 +114,20 @@
<string name="character_sheet__delete_dialog__description">Êtes-vous sûr de vouloir supprimer "%1$s" ?</string> <string name="character_sheet__delete_dialog__description">Êtes-vous sûr de vouloir supprimer "%1$s" ?</string>
<string name="tooltip__characteristics__characteristics">Les caractéristiques constituent les aptitudes innées dun 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.</string> <string name="tooltip__characteristics__characteristics">Les caractéristiques constituent les aptitudes innées dun 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.</string>
<string name="tooltip__characteristics__strength">La Force représente essentiellement la puissance musculaire du personnage. Elle ne décrit pas nécessairement la masse musculaire brute, mais lefficacité avec laquelle le personnage exerce ses muscles pour accomplir des actions physiques pénibles.</string> <string name="tooltip__characteristics__strength">La Force représente essentiellement la puissance musculaire du personnage. Elle ne décrit pas nécessairement la masse musculaire brute, mais lefficacité 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</string>
<string name="tooltip__characteristics__constitution">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 quun personnage peut supporter avant de succomber.</string> <string name="tooltip__characteristics__constitution">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 quun personnage peut supporter avant de succomber.\n\n- Point de vie maximum\n- Athlétisme\n- Acrobatie</string>
<string name="tooltip__characteristics__height">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.</string> <string name="tooltip__characteristics__height">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)</string>
<string name="tooltip__characteristics__intelligence">LIntelligence représente la capacité de discernement du personnage. Elle ne mesure pas nécessairement la somme dinformations mémorisées, mais laptitude au raisonnement, lacuité intellectuelle, la capacité à résoudre des problèmes et lintuition.</string> <string name="tooltip__characteristics__intelligence">LIntelligence représente la capacité de discernement du personnage. Elle ne mesure pas nécessairement la somme dinformations mémorisées, mais laptitude au raisonnement, lacuité intellectuelle, la capacité à résoudre des problèmes et lintuition.\n\n- Bonus d'apprentissage\n- Perception\n- Recherche\n- Empathie\n- Baratin\n- Premiers soins</string>
<string name="tooltip__characteristics__power">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.</string> <string name="tooltip__characteristics__power">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</string>
<string name="tooltip__characteristics__dexterity">La Dextérité mesure la coordination œil-main, la vitesse physique et lagilité générale. La DEX détermine à quelle vitesse un personnage peut agir en combat.</string> <string name="tooltip__characteristics__dexterity">La Dextérité mesure la coordination œil-main, la vitesse physique et lagilité 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</string>
<string name="tooltip__characteristics__charisma">Il représente plusieurs aspects allant de la grâce à la beauté en passant par lattraction que le personnage exerce sur les autres. Un personnage avec un CHA élevé se remarque dans une foule en raison dune intangible combinaison de charme et de présence.</string> <string name="tooltip__characteristics__charisma">Il représente plusieurs aspects allant de la grâce à la beauté en passant par lattraction que le personnage exerce sur les autres. Un personnage avec un CHA élevé se remarque dans une foule en raison dune intangible combinaison de charme et de présence.\n\n- Empathie\n- Persuasion\n- Persuasion\n- Intimidation\n- Baratin\n- Marchandage\n- Discrétion</string>
<string name="tooltip__sub_characteristics__movement">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 dun mètre. En course, un point équivaut à trois mètres.</string> <string name="tooltip__sub_characteristics__movement">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 dun mètre. En course, un point équivaut à trois mètres.</string>
<string name="tooltip__sub_characteristics__hit_point">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 linconscience. S'il reste inconscient trop longtemps, il meurt. Tous les points de vie régénèrent naturellement après une nuit de repos.</string> <string name="tooltip__sub_characteristics__hit_point">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 linconscience. S'il reste inconscient trop longtemps, il meurt. Tous les points de vie régénèrent naturellement après une nuit de repos.</string>
<string name="tooltip__sub_characteristics__power_point">les points de pouvoir sont égaux au POU et sont dépensés pour utiliser la magie ou dautres pouvoirs. Tous les points de pouvoir régénèrent naturellement après une nuit de repos.</string> <string name="tooltip__sub_characteristics__power_point">les points de pouvoir sont égaux au POU et sont dépensés pour utiliser la magie ou dautres pouvoirs. Tous les points de pouvoir régénèrent naturellement après une nuit de repos.</string>
<string name="tooltip__sub_characteristics__bonus_damage">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 sapplique 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).</string> <string name="tooltip__sub_characteristics__bonus_damage">Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis. Le modificateur sapplique 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</string>
<string name="tooltip__sub_characteristics__armor">Une armure protège son porteur des blessures. Lorsquun personnage est touché en combat par une attaque non magique, soustrayez les points darmure aux points de dégâts infligés. Les dommages au-delà de la protection de larmure surpassent celle-ci et sont infligés au personnage, réduisant ses points de vie actuels.</string> <string name="tooltip__sub_characteristics__armor">Une armure protège son porteur des blessures. Lorsquun personnage est touché en combat par une attaque non magique, soustrayez les points darmure aux points de dégâts infligés. Les dommages au-delà de la protection de larmure surpassent celle-ci et sont infligés au personnage, réduisant ses points de vie actuels.</string>
<string name="tooltip__sub_characteristics__learning">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.</string> <string name="tooltip__sub_characteristics__learning">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.</string>
<string name="tooltip__sub_characteristics__hp_grow">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.</string> <string name="tooltip__sub_characteristics__hp_grow">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.</string>
<string name="tooltip__skills__combat">Attaque en combat à mains nues. Une attaque réussie inflige 1D3 + BDGT.</string>
<string name="tooltip__skills__dodge">Éviter une attaque, un projectile, etc.</string>
<string name="tooltip__skills__grab">Maitriser/immobiliser un adversaire.</string>
<string name="tooltip__skills__throw">Viser et lancer quelque chose à travers les airs et vers une cible.</string>
<string name="tooltip__skills__athletics">Réaliser un mouvement nécessitant une certaine force (Enfoncer une porte, nager à contre-courant, etc.).</string>
<string name="tooltip__skills__acrobatics">Réaliser un mouvement nécessitant une certaine agilité (Marcher sur une corde, courir sur la glace, etc.).</string>
<string name="tooltip__skills__perception">Aptitude à percevoir via ses sens ou à percevoir un détail difficile à remarquer.</string>
<string name="tooltip__skills__search">Aptitude à trouver des indices et à émettre des suppositions sur la base de ceux-ci.</string>
<string name="tooltip__skills__empathy">Évaluer les pensées et/ou les motivations cachées dun autre personnage en se basant sur des signaux subliminaux.</string>
<string name="tooltip__skills__persuasion">Influencer quelqu'un ou un groupe de personnes avec tact, grâce, ou de bonnes manières.</string>
<string name="tooltip__skills__intimidation">Influencer quelqu'un ou un groupe de personnes par des menaces, des actions hostiles, ou de la violence physique.</string>
<string name="tooltip__skills__spiel">Mentir ou cacher la vérité avec efficacité, soit verbalement, soit par vos actions.</string>
<string name="tooltip__skills__bargain">Négocier les prix.</string>
<string name="tooltip__skills__discretion">Passer inaperçu.</string>
<string name="tooltip__skills__sleight_of_hand">Réaliser une manipulation ou une fourberie, comme prendre quelque chose à quelqu'un ou dissimuler un objet sur vous-même.</string>
<string name="tooltip__skills__aid">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.</string>
<string name="network__title">Configuration de la table</string> <string name="network__title">Configuration de la table</string>
<string name="network__player_name__label">Nom du joueur</string> <string name="network__player_name__label">Nom du joueur</string>
@ -156,6 +142,7 @@
<string name="network__socket__type_server">Serveur</string> <string name="network__socket__type_server">Serveur</string>
<string name="network__socket__type_client">Client</string> <string name="network__socket__type_client">Client</string>
<string name="network__socket__type_none">Aucun</string> <string name="network__socket__type_none">Aucun</string>
<string name="network__require__player_name">Un nom de joueur est requis</string>
<string name="network__connect__message">Vous êtes connecté au serveur</string> <string name="network__connect__message">Vous êtes connecté au serveur</string>
<string name="network__disconnect__message">Vous êtes déconnecté du serveur</string> <string name="network__disconnect__message">Vous êtes déconnecté du serveur</string>
<string name="network__message__action">Ok</string> <string name="network__message__action">Ok</string>
@ -186,7 +173,10 @@
<string name="level_up__title">Montée de niveau</string> <string name="level_up__title">Montée de niveau</string>
<string name="level_up__action">Level Up !</string> <string name="level_up__action">Level Up !</string>
<string name="level_up__character_level_description">Passage du niveau %1$d au niveau supérieur : %2$d</string> <string name="level_up__character__label">niv : %1$d&#x25B8;%2$d</string>
<string name="level_up__character_level_description">Passage du niveau %1$d &#x25B8; %2$d</string>
<string name="level_up__skill_level">niv : %1$d -</string> <string name="level_up__skill_level">niv : %1$d -</string>
</resources> </resources>

View file

@ -32,9 +32,6 @@ import androidx.compose.ui.unit.min
import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.ApplicationScope
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.rememberWindowState 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
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent 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.NetworkWindows
import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow 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.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.CampaignViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel 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.campaign.player.ribbon.PlayerRibbon
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost 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.NetworkPage
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel 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.RollHistoryPage
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
import com.pixelized.desktop.lwa.ui.theme.LwaTheme import com.pixelized.desktop.lwa.ui.theme.LwaTheme
import com.pixelized.desktop.lwa.utils.InstallCoil
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
@ -102,7 +100,7 @@ val LocalBlurController = compositionLocalOf<BlurContentController> {
@Composable @Composable
@Preview @Preview
fun ApplicationScope.App() { fun ApplicationScope.LwaApplication() {
val maxWindowHeight = rememberMaxWindowHeight() val maxWindowHeight = rememberMaxWindowHeight()
val snackHostState = remember { SnackbarHostState() } val snackHostState = remember { SnackbarHostState() }
val errorSnackHostState = remember { SnackbarHostState() } val errorSnackHostState = remember { SnackbarHostState() }
@ -120,12 +118,13 @@ fun ApplicationScope.App() {
), ),
) )
// Coil configuration // Coil configuration.
setSingletonImageLoaderFactory { context -> InstallCoil(
ImageLoader.Builder(context) crossfade = false,
.crossfade(enable = false) diskCachePath = { provider ->
.build() provider.imagesStorePath()
} }
)
CompositionLocalProvider( CompositionLocalProvider(
LocalApplicationScope provides this, LocalApplicationScope provides this,
@ -150,16 +149,18 @@ fun ApplicationScope.App() {
} }
} }
@Composable @Composable
private fun MainWindowScreen( private fun MainWindowScreen(
campaignViewModel: CampaignViewModel = koinViewModel(), dataSyncViewModel: DataSyncViewModel = koinViewModel(),
networkViewModel: NetworkViewModel = koinViewModel(), networkViewModel: NetworkViewModel = koinViewModel(),
campaignViewModel: CampaignViewModel = koinViewModel(),
campaignChatViewModel: CampaignChatViewModel = koinViewModel(), campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
rollViewModel: RollHistoryViewModel = koinViewModel(), rollViewModel: RollHistoryViewModel = koinViewModel(),
) { ) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
networkViewModel.connect() dataSyncViewModel.autoConnect()
campaignViewModel.init() dataSyncViewModel.synchronise()
} }
val snackHostState = LocalSnackHost.current val snackHostState = LocalSnackHost.current
@ -205,15 +206,12 @@ private fun MainWindowScreen(
content = { content = {
BlurContent( BlurContent(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
controller = blurController controller = blurController,
) { ) {
MainNavHost( MainNavHost()
campaignViewModel = campaignViewModel,
networkViewModel = networkViewModel,
campaignChatViewModel = campaignChatViewModel,
)
} }
RollOverlay( RollOverlay(
modifier = Modifier.fillMaxSize(),
hostState = rollHostState, hostState = rollHostState,
) )
} }

View file

@ -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)
}
}

View file

@ -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.repository.settings.SettingsStore
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel 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.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.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.CampaignChatViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory 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.CharacterSheetEditViewModel
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory 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.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.main.MainPageViewModel
import com.pixelized.desktop.lwa.ui.screen.network.NetworkFactory import com.pixelized.desktop.lwa.ui.screen.network.NetworkFactory
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel 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.rollhistory.RollHistoryViewModel
import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsUseCase
@ -116,6 +116,7 @@ val factoryDependencies
val viewModelDependencies val viewModelDependencies
get() = module { get() = module {
viewModelOf(::DataSyncViewModel)
viewModelOf(::CampaignViewModel) viewModelOf(::CampaignViewModel)
viewModelOf(::MainPageViewModel) viewModelOf(::MainPageViewModel)
viewModelOf(::CharacterSheetViewModel) viewModelOf(::CharacterSheetViewModel)

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface import androidx.compose.material.Surface
@ -40,7 +41,12 @@ fun TooltipLayout(
else -> TooltipArea( else -> TooltipArea(
modifier = modifier, modifier = modifier,
tooltip = { Tooltip(tooltip = tooltip) }, tooltip = {
Tooltip(
modifier = Modifier.width(width = 448.dp),
tooltip = tooltip,
)
},
content = content, content = content,
delayMillis = delayMillis, delayMillis = delayMillis,
tooltipPlacement = tooltipPlacement, tooltipPlacement = tooltipPlacement,

View file

@ -23,9 +23,6 @@ val LocalScreenController = compositionLocalOf<NavHostController> {
@Composable @Composable
fun MainNavHost( fun MainNavHost(
controller: NavHostController = rememberNavController(), controller: NavHostController = rememberNavController(),
campaignViewModel: CampaignViewModel,
networkViewModel: NetworkViewModel,
campaignChatViewModel: CampaignChatViewModel,
startDestination: String = MainDestination.navigationRoute(), startDestination: String = MainDestination.navigationRoute(),
) { ) {
CompositionLocalProvider( CompositionLocalProvider(
@ -35,11 +32,7 @@ fun MainNavHost(
navController = controller, navController = controller,
startDestination = startDestination, startDestination = startDestination,
) { ) {
composableMainPage( composableMainPage()
campaignViewModel = campaignViewModel,
networkViewModel = networkViewModel,
campaignChatViewModel = campaignChatViewModel,
)
composableSettingsPage() composableSettingsPage()
composableLevelUp() composableLevelUp()

View file

@ -3,10 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.composable 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.CampaignScreen
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
object MainDestination { object MainDestination {
private const val ROUTE = "main" private const val ROUTE = "main"
@ -15,19 +12,11 @@ object MainDestination {
fun navigationRoute() = ROUTE fun navigationRoute() = ROUTE
} }
fun NavGraphBuilder.composableMainPage( fun NavGraphBuilder.composableMainPage() {
campaignViewModel: CampaignViewModel,
networkViewModel: NetworkViewModel,
campaignChatViewModel: CampaignChatViewModel,
) {
composable( composable(
route = MainDestination.baseRoute(), route = MainDestination.baseRoute(),
) { ) {
CampaignScreen( CampaignScreen()
campaignViewModel = campaignViewModel,
networkViewModel = networkViewModel,
campaignChatViewModel = campaignChatViewModel,
)
} }
} }

View file

@ -25,6 +25,7 @@ import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun RollOverlay( fun RollOverlay(
viewModel: RollViewModel = koinViewModel(), viewModel: RollViewModel = koinViewModel(),
modifier: Modifier = Modifier,
hostState: RollHostState, hostState: RollHostState,
) { ) {
val blur = LocalBlurController.current val blur = LocalBlurController.current
@ -43,7 +44,7 @@ fun RollOverlay(
} }
AnimatedContent( AnimatedContent(
modifier = Modifier.fillMaxSize(), modifier = modifier,
targetState = hostState.rollAction.value, targetState = hostState.rollAction.value,
transitionSpec = { transitionSpec = {
val enter = fadeIn() + slideInVertically { 64 } val enter = fadeIn() + slideInVertically { 64 }

View file

@ -61,13 +61,11 @@ fun CampaignScreen(
campaignViewModel: CampaignViewModel = koinViewModel(), campaignViewModel: CampaignViewModel = koinViewModel(),
networkViewModel: NetworkViewModel = koinViewModel(), networkViewModel: NetworkViewModel = koinViewModel(),
campaignChatViewModel: CampaignChatViewModel = koinViewModel(), campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
rollViewModel: RollViewModel = koinViewModel(),
) { ) {
val screen = LocalScreenController.current val screen = LocalScreenController.current
val blurController = rememberBlurContentController() val blurController = rememberBlurContentController()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
KeyHandler { KeyHandler {
when { when {
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> { it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {

View file

@ -80,7 +80,7 @@ data class CharacterDetailHeaderUio(
@Composable @Composable
fun CharacterDetailHeader( fun CharacterDetailHeader(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
iconSize: Dp = 14.dp, iconSize: Dp = MaterialTheme.lwa.size.sheet.subCategory,
header: State<CharacterDetailHeaderUio?>, header: State<CharacterDetailHeaderUio?>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onDiminished: () -> Unit, onDiminished: () -> Unit,

View file

@ -52,7 +52,7 @@ fun CharacterDetailSheet(
) { ) {
sheet.value?.characteristics?.forEach { sheet.value?.characteristics?.forEach {
CharacterDetailSheetCharacteristic( CharacterDetailSheetCharacteristic(
modifier = Modifier.size(size = MaterialTheme.lwa.size.characteristic), modifier = Modifier.size(size = MaterialTheme.lwa.size.sheet.characteristic),
characteristic = it, characteristic = it,
onClick = { onCharacteristic(it) }, onClick = { onCharacteristic(it) },
) )

View file

@ -14,6 +14,8 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues 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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth 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.input.key.type
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import coil3.compose.AsyncImage 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.LevelUpCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkill 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.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 com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res 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__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_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__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 lwacharactersheet.composeapp.generated.resources.level_up__title
import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@ -86,6 +91,11 @@ data class LevelUpHeaderUio(
val name: String, val name: String,
val level: Int, val level: Int,
val portrait: String?, val portrait: String?,
val hp: LevelUpSubCharacteristicUio,
val pp: LevelUpSubCharacteristicUio,
val bonus: LevelUpSubCharacteristicUio,
val grow: LevelUpSubCharacteristicUio,
val learn: LevelUpSubCharacteristicUio,
) )
@Stable @Stable
@ -216,20 +226,51 @@ private fun LevelUpContent(
.background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp) .background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(space = 8.dp), verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) { ) {
Text( Text(
style = MaterialTheme.lwa.typography.base.h5, modifier = Modifier.alignByBaseline(),
text = header.value?.name ?: "" style = MaterialTheme.typography.h5,
text = header.value?.name ?: "",
overflow = TextOverflow.Ellipsis,
maxLines = 1,
) )
Text( Text(
style = MaterialTheme.lwa.typography.base.body1, modifier = Modifier.alignByBaseline(),
text = (header.value?.level ?: 0).let { style = MaterialTheme.lwa.typography.base.caption,
text = (header.value?.level ?: 0).let { level ->
stringResource( stringResource(
Res.string.level_up__character_level_description, it, it + 1 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( Column(
modifier = Modifier.verticalScroll(state = scrollState), modifier = Modifier.verticalScroll(state = scrollState),
) { ) {
@ -251,7 +292,7 @@ private fun LevelUpContent(
key = { it.characteristicId } key = { it.characteristicId }
) { ) {
LevelUpCharacteristic( LevelUpCharacteristic(
modifier = Modifier.size(size = MaterialTheme.lwa.size.characteristic), modifier = Modifier.size(size = MaterialTheme.lwa.size.sheet.characteristic),
characteristic = it, characteristic = it,
onClick = { onCharacteristic(it) }, onClick = { onCharacteristic(it) },
) )

View file

@ -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.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio 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.LevelUpSkillUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSubCharacteristicUio
import com.pixelized.shared.lwa.model.AlteredCharacterSheet import com.pixelized.shared.lwa.model.AlteredCharacterSheet
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory 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.campaign.Campaign
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.parser.expression.Expression
import com.pixelized.shared.lwa.usecase.ExpressionUseCase import com.pixelized.shared.lwa.usecase.ExpressionUseCase
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__cha 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__int
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__pow 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__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__charisma
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__constitution import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__constitution
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__dexterity 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__intelligence
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__power import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__power
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__strength 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 import org.jetbrains.compose.resources.getString
class LevelUpFactory( class LevelUpFactory(
@ -32,14 +57,96 @@ class LevelUpFactory(
private val expressionUseCase: ExpressionUseCase, private val expressionUseCase: ExpressionUseCase,
) { ) {
fun convertToLevelUpHeaderUio( suspend fun convertToLevelUpHeaderUio(
characterSheet: CharacterSheet?, characterSheet: CharacterSheet?,
selectedCharacteristicId: String?,
): LevelUpHeaderUio? { ): LevelUpHeaderUio? {
if (characterSheet == null) return null 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( return LevelUpHeaderUio(
name = characterSheet.name, name = characterSheet.name,
portrait = characterSheet.portrait, portrait = characterSheet.portrait,
level = characterSheet.level, 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, level = skill.level,
levelUp = results.isSkillLeveledUp(skillId = skill.id), levelUp = results.isSkillLeveledUp(skillId = skill.id),
occupation = skill.occupation, occupation = skill.occupation,
tooltips = skill.description?.let {
TooltipUio(
title = skill.label,
description = it,
)
},
roll = when (results[skill.id]) { roll = when (results[skill.id]) {
null -> RollActionUio( null -> RollActionUio(
characterInstanceId = characterInstanceId, characterInstanceId = characterInstanceId,

View file

@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.error__default__action import lwacharactersheet.composeapp.generated.resources.error__default__action
@ -35,10 +34,11 @@ class LevelUpViewModel(
private val results = MutableStateFlow<Map<String, RollResult>>(emptyMap()) private val results = MutableStateFlow<Map<String, RollResult>>(emptyMap())
private val selectedCharacteristicId = MutableStateFlow<String?>(null) private val selectedCharacteristicId = MutableStateFlow<String?>(null)
val header: StateFlow<LevelUpHeaderUio?> = characterSheetRepository val header = combine(
.characterDetailFlow(characterSheetId = argument.characterInstanceId.characterSheetId) characterSheetRepository.characterDetailFlow(characterSheetId = argument.characterInstanceId.characterSheetId),
.map(levelUpFactory::convertToLevelUpHeaderUio) selectedCharacteristicId,
.stateIn( levelUpFactory::convertToLevelUpHeaderUio
).stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Lazily, started = SharingStarted.Lazily,
initialValue = null, initialValue = null,

View file

@ -64,11 +64,7 @@ fun LevelUpCharacteristic(
.fillMaxWidth() .fillMaxWidth()
.align(alignment = Alignment.Center), .align(alignment = Alignment.Center),
targetState = characteristic.selected, targetState = characteristic.selected,
transitionSpec = { transitionSpec = { fadeIn() togetherWith fadeOut() },
val enter = fadeIn() + slideInVertically { -16 }
val exit = fadeOut() + slideOutVertically { 16 }
enter togetherWith exit using SizeTransform(clip = false)
},
) { ) {
Text( Text(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),

View file

@ -8,6 +8,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.shapes.MasteryShape 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.overlay.roll.RollAction.RollActionUio
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
@ -37,9 +40,11 @@ data class LevelUpSkillUio(
val level: Int, val level: Int,
val levelUp: Boolean, val levelUp: Boolean,
val occupation: Boolean, val occupation: Boolean,
val tooltips: TooltipUio?,
val roll: RollActionUio?, val roll: RollActionUio?,
) )
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun LevelUpSkill( fun LevelUpSkill(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -53,7 +58,7 @@ fun LevelUpSkill(
else -> MaterialTheme.lwa.colorScheme.base.primary else -> MaterialTheme.lwa.colorScheme.base.primary
} }
) )
Row( TooltipLayout(
modifier = Modifier modifier = Modifier
.let { .let {
when (skill.roll) { when (skill.roll) {
@ -63,6 +68,10 @@ fun LevelUpSkill(
} }
.padding(paddingValues = paddingValues) .padding(paddingValues = paddingValues)
.then(other = modifier), .then(other = modifier),
tooltip = skill.tooltips,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(space = 8.dp), horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -122,3 +131,4 @@ fun LevelUpSkill(
} }
} }
} }
}

View file

@ -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,
)
}
}
}
}

View file

@ -41,6 +41,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow 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.composable.error.ErrorSnack
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
import lwacharactersheet.composeapp.generated.resources.network__host__label import lwacharactersheet.composeapp.generated.resources.network__host__label
@ -103,6 +105,7 @@ fun NetworkScreen(
) { ) {
val screen = LocalScreenController.current val screen = LocalScreenController.current
val snack = LocalSnackHost.current val snack = LocalSnackHost.current
val scope = rememberCoroutineScope()
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -148,7 +151,7 @@ fun NetworkScreen(
onResetPortChange = viewModel::onResetPortChange, onResetPortChange = viewModel::onResetPortChange,
onPortChange = viewModel::onPortChange, onPortChange = viewModel::onPortChange,
onResetHostChange = viewModel::onResetHostChange, onResetHostChange = viewModel::onResetHostChange,
onConnect = viewModel::connect, onConnect = { scope.launch { viewModel.connect() } },
onDisconnect = viewModel::disconnect, onDisconnect = viewModel::disconnect,
) )
} }
@ -188,6 +191,7 @@ fun NetworkPage(
viewModel: NetworkViewModel = koinViewModel(), viewModel: NetworkViewModel = koinViewModel(),
) { ) {
val snack = LocalSnackHost.current val snack = LocalSnackHost.current
val scope = rememberCoroutineScope()
Box( Box(
modifier = modifier, modifier = modifier,
@ -205,7 +209,7 @@ fun NetworkPage(
onResetHostChange = viewModel::onResetHostChange, onResetHostChange = viewModel::onResetHostChange,
onPortChange = viewModel::onPortChange, onPortChange = viewModel::onPortChange,
onResetPortChange = viewModel::onResetPortChange, onResetPortChange = viewModel::onResetPortChange,
onConnect = viewModel::connect, onConnect = { scope.launch { viewModel.connect() } },
onDisconnect = viewModel::disconnect, onDisconnect = viewModel::disconnect,
) )
} }

View file

@ -1,5 +1,6 @@
package com.pixelized.desktop.lwa.ui.screen.network package com.pixelized.desktop.lwa.ui.screen.network
import androidx.compose.material.SnackbarDuration
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel 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.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
@ -20,6 +20,10 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch 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( class NetworkViewModel(
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
@ -64,11 +68,13 @@ class NetworkViewModel(
) )
init { init {
settingsRepository.settingsFlow().onEach { settingsRepository.settingsFlow()
.onEach {
nameFlow.value = it.playerName nameFlow.value = it.playerName
hostFlow.value = it.host hostFlow.value = it.host
portFlow.value = it.port portFlow.value = it.port
}.launchIn(viewModelScope) }
.launchIn(viewModelScope)
} }
fun onPlayerNameChange(player: String) { fun onPlayerNameChange(player: String) {
@ -91,7 +97,7 @@ class NetworkViewModel(
hostFlow.value = settings.host hostFlow.value = settings.host
} }
fun connect() { suspend fun connect() {
blurController.show() blurController.show()
_isLoading.value = true _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( networkRepository.connect(
host = hostFlow.value, host = hostFlow.value,
port = portFlow.value, port = portFlow.value,

View file

@ -3,20 +3,30 @@ package com.pixelized.desktop.lwa.ui.theme.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Stable
data class LwaSize( data class LwaSize(
val sheet: Sheet,
) {
@Stable
data class Sheet(
val subCategory: Dp,
val characteristic: DpSize, val characteristic: DpSize,
) )
}
@Composable @Composable
@Stable @Stable
fun lwaSize( 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 { ) = remember {
LwaSize( LwaSize(
characteristic = characteristic, sheet = sheet,
) )
} }

View file

@ -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()
}
}

View file

@ -13,7 +13,7 @@ fun main() {
} }
application { application {
KoinContext { KoinContext {
App() LwaApplication()
} }
} }
} }

View file

@ -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( fun characterStorePath(
os: OperatingSystem = this.operatingSystem, os: OperatingSystem = this.operatingSystem,
app: String = this.appName, app: String = this.appName,