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

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

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.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)

View file

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

View file

@ -23,9 +23,6 @@ val LocalScreenController = compositionLocalOf<NavHostController> {
@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()

View file

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

View file

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

View file

@ -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 -> {

View file

@ -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<CharacterDetailHeaderUio?>,
onDismissRequest: () -> Unit,
onDiminished: () -> Unit,

View file

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

View file

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

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.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,

View file

@ -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<Map<String, RollResult>>(emptyMap())
private val selectedCharacteristicId = MutableStateFlow<String?>(null)
val header: StateFlow<LevelUpHeaderUio?> = 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),

View file

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

View file

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

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

View file

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

View file

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

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