Add some chat log messages (diminished + hp/pp changes)

This commit is contained in:
Thomas Andres Gomez 2025-03-03 22:49:29 +01:00
parent 7a9dd97123
commit f60a58f71e
22 changed files with 503 additions and 38 deletions

View file

@ -157,4 +157,9 @@
<string name="roll_history__title">Historique des lancers</string> <string name="roll_history__title">Historique des lancers</string>
<string name="roll_history__item__throw">lance</string> <string name="roll_history__item__throw">lance</string>
<string name="roll_history__item__difficulty">Difficulté</string> <string name="roll_history__item__difficulty">Difficulté</string>
<string name="chat__diminished_change">%1$s passe à %2$d d\'état diminuée</string>
<string name="chat__characteristic_change">%1$s passe à %2$d %3$s</string>
<string name="chat__characteristic__hp">Hp</string>
<string name="chat__characteristic__pp">Pp</string>
</resources> </resources>

View file

@ -28,7 +28,6 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp 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
import androidx.compose.ui.unit.max
import androidx.compose.ui.unit.min 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

View file

@ -33,6 +33,7 @@ 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.screen.roll.RollViewModel import com.pixelized.desktop.lwa.ui.screen.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.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsUseCase
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider import com.pixelized.shared.lwa.utils.PathProvider
@ -124,6 +125,7 @@ val viewModelDependencies
viewModelOf(::CharacterDiminishedViewModel) viewModelOf(::CharacterDiminishedViewModel)
viewModelOf(::CharacterDetailCharacteristicDialogViewModel) viewModelOf(::CharacterDetailCharacteristicDialogViewModel)
viewModelOf(::CampaignChatViewModel) viewModelOf(::CampaignChatViewModel)
viewModelOf(::SettingsViewModel)
} }
val useCaseDependencies val useCaseDependencies

View file

@ -17,6 +17,10 @@ class CampaignRepository(
val campaignFlow get() = store.campaignFlow val campaignFlow get() = store.campaignFlow
suspend fun update() {
store.update()
}
fun characterInstanceFlow( fun characterInstanceFlow(
id: Campaign.CharacterInstance.Id, id: Campaign.CharacterInstance.Id,
): StateFlow<Campaign.CharacterInstance> { ): StateFlow<Campaign.CharacterInstance> {

View file

@ -37,7 +37,7 @@ class CampaignStore(
} }
} }
private suspend fun update() { suspend fun update() {
_campaignFlow.value = load() _campaignFlow.value = load()
} }

View file

@ -16,6 +16,10 @@ class SettingsFactory(
host = settings.host, host = settings.host,
port = settings.port, port = settings.port,
playerName = settings.playerName, playerName = settings.playerName,
dynamicDice = settings.dynamicDice,
autoHideChat = settings.autoHideChat,
autoShowChat = settings.autoShowChat,
autoScrollChat = settings.autoScrollChat,
) )
} }
@ -34,7 +38,11 @@ class SettingsFactory(
return Settings( return Settings(
host = json.host ?: default.host, host = json.host ?: default.host,
port = json.port ?: default.port, port = json.port ?: default.port,
playerName = json.playerName ?: default.playerName playerName = json.playerName ?: default.playerName,
dynamicDice = json.dynamicDice ?: default.dynamicDice,
autoHideChat = json.autoHideChat ?: default.autoHideChat,
autoShowChat = json.autoShowChat ?: default.autoShowChat,
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
) )
} }
} }

View file

@ -4,6 +4,10 @@ data class Settings(
val host: String, val host: String,
val port: Int, val port: Int,
val playerName: String, val playerName: String,
val dynamicDice: Boolean,
val autoHideChat: Boolean,
val autoShowChat: Boolean,
val autoScrollChat: Boolean,
) { ) {
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}" val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
} }

View file

@ -7,4 +7,8 @@ data class SettingsJsonV1(
val host: String?, val host: String?,
val port: Int?, val port: Int?,
val playerName: String?, val playerName: String?,
val dynamicDice: Boolean?,
val autoHideChat: Boolean?,
val autoShowChat: Boolean?,
val autoScrollChat: Boolean?,
) : SettingsJson ) : SettingsJson

View file

@ -92,6 +92,7 @@ fun MainPage(
top = { top = {
CampaignToolbar( CampaignToolbar(
campaignViewModel = campaignViewModel, campaignViewModel = campaignViewModel,
networkViewModel = networkViewModel,
) )
}, },
bottom = { bottom = {
@ -103,7 +104,7 @@ fun MainPage(
chat = { chat = {
CampaignChat( CampaignChat(
modifier = Modifier.padding(all = 8.dp), modifier = Modifier.padding(all = 8.dp),
campaignChatViewModel = campaignChatViewModel, chatViewModel = campaignChatViewModel,
) )
}, },
leftOverlay = { leftOverlay = {

View file

@ -26,21 +26,30 @@ class CampaignViewModel(
fun init() { fun init() {
viewModelScope.launch { viewModelScope.launch {
combine( launch {
network.status, network.status.collect { status ->
campaignRepository.campaignFlow, if (status == NetworkRepository.Status.CONNECTED) {
) { status, campaign -> campaignRepository.update()
status to campaign }
}.collectLatest { (status, campaign) -> }
if (status == NetworkRepository.Status.CONNECTED) { }
campaign.characters.keys.forEach { id -> launch {
characterRepository.characterDetail( combine(
characterSheetId = id.characterSheetId, network.status,
forceUpdate = true, campaignRepository.campaignFlow,
) ) { status, campaign ->
alterationRepository.updateActiveAlterations( status to campaign
characterInstanceId = id, }.collectLatest { (status, campaign) ->
) if (status == NetworkRepository.Status.CONNECTED) {
campaign.characters.keys.forEach { id ->
characterRepository.characterDetail(
characterSheetId = id.characterSheetId,
forceUpdate = true,
)
alterationRepository.updateActiveAlterations(
characterInstanceId = id,
)
}
} }
} }
} }

View file

@ -32,9 +32,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max import androidx.compose.ui.unit.max
import androidx.compose.ui.unit.min import androidx.compose.ui.unit.min
import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.WindowState
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignLayoutScope import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignLayoutScope
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.CharacteristicTextMessage
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.CharacteristicTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.DiminishedTextMessage
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.DiminishedTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessage import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessage
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
@ -47,19 +52,21 @@ import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun CampaignChat( fun CampaignChat(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
campaignChatViewModel: CampaignChatViewModel = koinViewModel(), chatViewModel: CampaignChatViewModel = koinViewModel(),
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val lazyState = rememberLazyListState() val lazyState = rememberLazyListState()
val animatedChatWidth = rememberAnimatedChatWidth() val animatedChatWidth = rememberAnimatedChatWidth()
val colorScheme = MaterialTheme.lwa.colorScheme val colorScheme = MaterialTheme.lwa.colorScheme
val messages = campaignChatViewModel.messages.collectAsState() val messages = chatViewModel.messages.collectAsState()
val settings = chatViewModel.settings.collectAsState()
ChatScrollDownEffect( ChatScrollDownEffect(
lazyState = lazyState, lazyState = lazyState,
messages = messages, messages = messages,
displayChat = campaignChatViewModel::displayChat, settings = settings,
hideChat = campaignChatViewModel::hideChat, displayChat = chatViewModel::displayChat,
hideChat = chatViewModel::hideChat,
) )
Box( Box(
@ -69,17 +76,19 @@ fun CampaignChat(
height = PlayerRibbon.Default.size.height * 2 + 8.dp, height = PlayerRibbon.Default.size.height * 2 + 8.dp,
) )
.graphicsLayer { .graphicsLayer {
alpha = campaignChatViewModel.chatAnimatedVisibility.value alpha = chatViewModel.chatAnimatedVisibility.value
} }
.background( .background(
shape = remember { RoundedCornerShape(8.dp) }, shape = remember { RoundedCornerShape(8.dp) },
color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) }, color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) },
) )
.onPointerEvent(eventType = PointerEventType.Enter) { .onPointerEvent(eventType = PointerEventType.Enter) {
scope.launch { campaignChatViewModel.displayChat() } scope.launch { chatViewModel.displayChat() }
} }
.onPointerEvent(eventType = PointerEventType.Exit) { .onPointerEvent(eventType = PointerEventType.Exit) {
scope.launch { campaignChatViewModel.hideChat() } if (settings.value.autoHideChat) {
scope.launch { chatViewModel.hideChat() }
}
}, },
) { ) {
LazyColumn( LazyColumn(
@ -98,6 +107,8 @@ fun CampaignChat(
) { ) {
when (it) { when (it) {
is RollTextMessageUio -> RollTextMessage(message = it) is RollTextMessageUio -> RollTextMessage(message = it)
is DiminishedTextMessageUio -> DiminishedTextMessage(message = it)
is CharacteristicTextMessageUio -> CharacteristicTextMessage(message = it)
} }
} }
} }
@ -108,6 +119,7 @@ fun CampaignChat(
private fun ChatScrollDownEffect( private fun ChatScrollDownEffect(
lazyState: LazyListState, lazyState: LazyListState,
messages: State<List<TextMessage>>, messages: State<List<TextMessage>>,
settings: State<Settings>,
displayChat: suspend () -> Unit, displayChat: suspend () -> Unit,
hideChat: suspend () -> Unit, hideChat: suspend () -> Unit,
) { ) {
@ -115,11 +127,17 @@ private fun ChatScrollDownEffect(
key1 = messages.value.lastOrNull()?.id, key1 = messages.value.lastOrNull()?.id,
) { ) {
if (messages.value.isNotEmpty()) { if (messages.value.isNotEmpty()) {
displayChat() if (settings.value.autoShowChat) {
lazyState.animateScrollToItem( displayChat()
index = messages.value.lastIndex + 1, }
) if (settings.value.autoScrollChat) {
hideChat() lazyState.animateScrollToItem(
index = messages.value.lastIndex + 1,
)
}
if (settings.value.autoHideChat) {
hideChat()
}
} }
} }
} }

View file

@ -5,6 +5,7 @@ import androidx.compose.animation.core.tween
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -15,8 +16,11 @@ import kotlinx.coroutines.flow.stateIn
class CampaignChatViewModel( class CampaignChatViewModel(
networkRepository: NetworkRepository, networkRepository: NetworkRepository,
textMessageFactory: TextMessageFactory, textMessageFactory: TextMessageFactory,
settingsRepository: SettingsRepository,
) : ViewModel() { ) : ViewModel() {
val settings = settingsRepository.settingsFlow()
val chatAnimatedVisibility = Animatable(0f) val chatAnimatedVisibility = Animatable(0f)
private var _messages = emptyList<TextMessage>() private var _messages = emptyList<TextMessage>()

View file

@ -1,26 +1,40 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.chat package com.pixelized.desktop.lwa.ui.screen.campaign.chat
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.CharacteristicTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.DiminishedTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power
import com.pixelized.shared.lwa.protocol.websocket.Message import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.chat__characteristic__hp
import lwacharactersheet.composeapp.generated.resources.chat__characteristic__pp
import org.jetbrains.compose.resources.getString
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
class TextMessageFactory( class TextMessageFactory(
private val characterSheetRepository: CharacterSheetRepository, private val characterSheetRepository: CharacterSheetRepository,
private val alterationRepository: AlterationRepository,
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
) { ) {
private val formatId = SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS") private val formatId = SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS")
private val formatTime = SimpleDateFormat("HH:mm:ss") private val formatTime = SimpleDateFormat("HH:mm:ss")
fun convertToTextMessage( suspend fun convertToTextMessage(
message: Message, message: Message,
): TextMessage? { ): TextMessage? {
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
val id = formatId.format(time) val id = formatId.format(time)
return when (val payload = message.value) { return when (val payload = message.value) {
is RollMessage -> { is RollMessage -> {
val sheetPreview = characterSheetRepository val sheetPreview = characterSheetRepository
@ -47,8 +61,54 @@ class TextMessageFactory(
) )
} }
is CampaignMessage.UpdateCharacteristic -> null is CampaignMessage.UpdateDiminished -> {
is CampaignMessage.UpdateDiminished -> null val characterInstanceId = Campaign.CharacterInstance.Id(
characterSheetId = payload.characterSheetId,
instanceId = payload.instanceId,
)
val sheetPreview = characterSheetRepository
.characterPreview(characterId = payload.characterSheetId)
?: return null
DiminishedTextMessageUio(
id = id,
timestamp = formatTime.format(time),
character = sheetPreview.name,
diminished = payload.diminished,
)
}
is CampaignMessage.UpdateCharacteristic -> {
val sheet = characterSheetRepository.characterDetail(
characterSheetId = payload.characterSheetId,
) ?: return null
val characterInstanceId = Campaign.CharacterInstance.Id(
characterSheetId = payload.characterSheetId,
instanceId = payload.instanceId,
)
val alterations = alterationRepository.alterations(
characterInstanceId = characterInstanceId,
)
val alteredSheet = alteredCharacterSheetFactory.sheet(
characterSheet = sheet,
alterations = alterations,
)
CharacteristicTextMessageUio(
id = id,
timestamp = formatTime.format(time),
character = sheet.name,
value = when (payload.characteristic) {
Damage -> alteredSheet.maxHp - payload.value
Power -> alteredSheet.maxPp - payload.value
},
characteristic = when (payload.characteristic) {
Damage -> getString(Res.string.chat__characteristic__hp)
Power -> getString(Res.string.chat__characteristic__pp)
},
)
}
RestSynchronisation.Campaign -> null RestSynchronisation.Campaign -> null
is RestSynchronisation.CharacterDelete -> null is RestSynchronisation.CharacterDelete -> null
is RestSynchronisation.CharacterUpdate -> null is RestSynchronisation.CharacterUpdate -> null

View file

@ -0,0 +1,64 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
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.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.chat__characteristic_change
import org.jetbrains.compose.resources.stringResource
@Stable
data class CharacteristicTextMessageUio(
override val id: String,
override val timestamp: String,
val character: String,
val value: Int,
val characteristic: String,
) : TextMessage
@Composable
fun CharacteristicTextMessage(
modifier: Modifier = Modifier,
message: CharacteristicTextMessageUio,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 3.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.timestamp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = message.timestamp,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.timestamp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = ">",
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(
Res.string.chat__characteristic_change,
message.character,
message.value,
message.characteristic,
),
)
}
}

View file

@ -0,0 +1,62 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
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.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.chat__diminished_change
import org.jetbrains.compose.resources.stringResource
@Stable
data class DiminishedTextMessageUio(
override val id: String,
override val timestamp: String,
val character: String,
val diminished: Int,
) : TextMessage
@Composable
fun DiminishedTextMessage(
modifier: Modifier = Modifier,
message: DiminishedTextMessageUio,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 3.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.timestamp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = message.timestamp,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.timestamp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = ">",
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(
Res.string.chat__diminished_change,
message.character,
message.diminished
),
)
}
}

View file

@ -1,26 +1,43 @@
package com.pixelized.desktop.lwa.ui.screen.settings package com.pixelized.desktop.lwa.ui.screen.settings
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingItemUio
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingSection
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingSectionUio
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingToggleItem
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingToggleItemUio
import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun SettingsScreen() { fun SettingsScreen(
viewModel: SettingsViewModel = koinViewModel(),
) {
val screen = LocalScreenController.current val screen = LocalScreenController.current
Surface { Surface {
SettingsContent( SettingsContent(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
items = viewModel.items,
onBack = { onBack = {
screen.popBackStack() screen.popBackStack()
}, },
@ -31,13 +48,18 @@ fun SettingsScreen() {
@Composable @Composable
private fun SettingsContent( private fun SettingsContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: ScrollState = rememberScrollState(),
spacing: Dp = 8.dp,
items: List<SettingItemUio>,
onBack: () -> Unit, onBack: () -> Unit,
) { ) {
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { }, title = {
Text(text = "Paramètres de l\'application")
},
navigationIcon = { navigationIcon = {
IconButton( IconButton(
onClick = onBack, onClick = onBack,
@ -52,10 +74,24 @@ private fun SettingsContent(
}, },
content = { paddingValues -> content = { paddingValues ->
Column( Column(
modifier = Modifier.padding(paddingValues = paddingValues), modifier = Modifier
.padding(paddingValues = paddingValues)
.verticalScroll(state = state),
verticalArrangement = Arrangement.spacedBy(space = spacing),
) { ) {
items.forEach {
when (it) {
is SettingSectionUio -> {
SettingSection(item = it)
}
is SettingToggleItemUio -> {
SettingToggleItem(item = it)
}
}
}
} }
} }
) )
} }

View file

@ -0,0 +1,81 @@
package com.pixelized.desktop.lwa.ui.screen.settings
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingSectionUio
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingToggleItemUio
import kotlinx.coroutines.launch
class SettingsViewModel(
private val settingsRepository: SettingsRepository,
) : ViewModel() {
private val settings = settingsRepository.settingsFlow()
private val states = hashMapOf<String, MutableState<Boolean>>()
val items = listOf(
SettingSectionUio(
title = "Portrait joueurs."
),
SettingToggleItemUio(
title = "Dés dynamiques",
description = "Affiche un dés à côté du portrait d'un personnage lorsqu\'un jet est fait par ce dernier.",
checked = states.dynamicDice,
onToggle = {
settingsRepository.update(settings = settings.value.copy(dynamicDice = it))
},
),
SettingSectionUio(
title = "Chatlog options."
),
SettingToggleItemUio(
title = "Afficher automatiquement",
description = "Affiche automatiquement le chat lors de la réception d'un message.",
checked = states.autoShowChat,
onToggle = {
settingsRepository.update(settings = settings.value.copy(autoShowChat = it))
},
),
SettingToggleItemUio(
title = "Cacher automatiquement",
description = "Cache automatiquement le chat au bout d'un certain temps.",
checked = states.autoHideChat,
onToggle = {
settingsRepository.update(settings = settings.value.copy(autoHideChat = it))
},
),
SettingToggleItemUio(
title = "Défilement automatique",
description = "Défilement automatique de chat vers le dernier message reçu lors de la réception de ce dernier.",
checked = states.autoScrollChat,
onToggle = {
settingsRepository.update(settings = settings.value.copy(autoScrollChat = it))
},
),
)
init {
viewModelScope.launch {
settingsRepository.settingsFlow().collect { settings ->
states.dynamicDice.value = settings.dynamicDice
states.autoShowChat.value = settings.autoShowChat
states.autoHideChat.value = settings.autoHideChat
states.autoScrollChat.value = settings.autoScrollChat
}
}
}
private val HashMap<String, MutableState<Boolean>>.dynamicDice
get() = getOrPut("DYNAMIC_DICE") { mutableStateOf(settings.value.dynamicDice) }
private val HashMap<String, MutableState<Boolean>>.autoShowChat
get() = getOrPut("AUTO_SHOW_CHAT") { mutableStateOf(settings.value.autoShowChat) }
private val HashMap<String, MutableState<Boolean>>.autoHideChat
get() = getOrPut("AUTO_HIDE_CHAT") { mutableStateOf(settings.value.autoHideChat) }
private val HashMap<String, MutableState<Boolean>>.autoScrollChat
get() = getOrPut("AUTO_SCROLL_CHAT") { mutableStateOf(settings.value.autoScrollChat) }
}

View file

@ -0,0 +1,6 @@
package com.pixelized.desktop.lwa.ui.screen.settings.composable
import androidx.compose.runtime.Stable
@Stable
sealed interface SettingItemUio

View file

@ -0,0 +1,30 @@
package com.pixelized.desktop.lwa.ui.screen.settings.composable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
@Stable
data class SettingSectionUio(
val title: String,
) : SettingItemUio
@Composable
fun SettingSection(
modifier: Modifier = Modifier,
padding: PaddingValues = PaddingValues(start = 16.dp, top = 32.dp, end = 16.dp),
item: SettingSectionUio,
) {
Text(
modifier = modifier.padding(paddingValues = padding),
style = MaterialTheme.lwa.typography.base.h6,
maxLines = 1,
text = item.title,
)
}

View file

@ -0,0 +1,64 @@
package com.pixelized.desktop.lwa.ui.screen.settings.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
@Stable
data class SettingToggleItemUio(
val title: String,
val description: String,
val checked: State<Boolean>,
val onToggle: (Boolean) -> Unit,
) : SettingItemUio
@Composable
fun SettingToggleItem(
modifier: Modifier = Modifier,
padding: PaddingValues = PaddingValues(start = 16.dp, top = 8.dp, end = 16.dp),
spacing: Dp = 8.dp,
item: SettingToggleItemUio,
) {
Row(
modifier = modifier.padding(paddingValues = padding),
horizontalArrangement = Arrangement.spacedBy(space = spacing),
) {
Column(
modifier = Modifier.weight(weight = 1f),
verticalArrangement = Arrangement.spacedBy(space = spacing),
) {
Text(
style = MaterialTheme.lwa.typography.base.body1,
maxLines = 1,
text = item.title,
)
Text(
style = MaterialTheme.lwa.typography.base.caption,
fontStyle = FontStyle.Italic,
text = item.description,
)
}
Switch(
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.lwa.colorScheme.base.primary,
),
onCheckedChange = item.onToggle,
checked = item.checked.value
)
}
}

View file

@ -8,6 +8,10 @@ class SettingsUseCase {
host = DEFAULT_HOST, host = DEFAULT_HOST,
port = DEFAULT_PORT, port = DEFAULT_PORT,
playerName = "", playerName = "",
dynamicDice = true,
autoHideChat = true,
autoShowChat = true,
autoScrollChat = true,
) )
companion object { companion object {