Refactor the setting data class + change the roll text message

This commit is contained in:
Thomas Andres Gomez 2025-03-20 14:32:19 +01:00
parent 353183f0f5
commit 07ed4ce579
15 changed files with 216 additions and 93 deletions

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M120,880v-60h100v-30h-60v-60h60v-30L120,700v-60h120q17,0 28.5,11.5T280,680v40q0,17 -11.5,28.5T240,760q17,0 28.5,11.5T280,800v40q0,17 -11.5,28.5T240,880L120,880ZM120,600v-110q0,-17 11.5,-28.5T160,450h60v-30L120,420v-60h120q17,0 28.5,11.5T280,400v70q0,17 -11.5,28.5T240,510h-60v30h100v60L120,600ZM180,320v-180h-60v-60h120v240h-60ZM360,760v-80h480v80L360,760ZM360,520v-80h480v80L360,520ZM360,280v-80h480v80L360,280Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -207,6 +207,8 @@
<string name="settings__chat_log__auto_hide_delay_description">Délai après lequel le chat disparaît</string>
<string name="settings__chat_log__auto_scroll_title">Défilement automatique</string>
<string name="settings__chat_log__auto_scroll_description">Défilement automatique du chat vers le dernier message reçu lors de la réception de ce dernier.</string>
<string name="settings__chat_log__line_count_title">Nombre de lignes de textes visibles</string>
<string name="settings__chat_log__line_count_description">Limite le nombre maximale de messages affichés par le chat.</string>
<string name="level_up__title">Montée de niveau</string>
<string name="level_up__action">Level Up !</string>

View file

@ -28,8 +28,8 @@ class DataSyncViewModel(
if (settings.playerName.isEmpty()) return
networkRepository.connect(
host = settings.host,
port = settings.port,
host = settings.network.host,
port = settings.network.port,
)
}

View file

@ -19,7 +19,7 @@ class LwaClientImpl(
private val client: HttpClient,
setting: SettingsRepository,
) : LwaClient {
private val root = setting.settings().root
private val root = setting.settings().network.root
override suspend fun characters(): List<CharacterPreviewJson> = client
.get("$root/characters")

View file

@ -13,14 +13,15 @@ class SettingsFactory(
settings: Settings,
): SettingsJson {
return SettingsJsonV1(
host = settings.host,
port = settings.port,
host = settings.network.host,
port = settings.network.port,
playerName = settings.playerName,
dynamicDice = settings.dynamicDice,
autoHideChat = settings.autoHideChat,
autoHideDelay = settings.autoHideDelay,
autoShowChat = settings.autoShowChat,
autoScrollChat = settings.autoScrollChat,
dynamicDice = settings.portrait.dynamicDice,
autoHideChat = settings.chat.autoHideChat,
autoHideDelay = settings.chat.autoHideDelay,
autoShowChat = settings.chat.autoShowChat,
autoScrollChat = settings.chat.autoScrollChat,
maxLineCount = settings.chat.maxLineCount,
isAdmin = settings.isAdmin,
isGameMaster = settings.isGameMaster,
)
@ -39,14 +40,21 @@ class SettingsFactory(
): Settings {
val default = useCase.defaultSettings()
return Settings(
host = json.host ?: default.host,
port = json.port ?: default.port,
playerName = json.playerName ?: default.playerName,
dynamicDice = json.dynamicDice ?: default.dynamicDice,
autoHideChat = json.autoHideChat ?: default.autoHideChat,
autoHideDelay = json.autoHideDelay ?: default.autoHideDelay,
autoShowChat = json.autoShowChat ?: default.autoShowChat,
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
network = Settings.Network(
host = json.host ?: default.network.host,
port = json.port ?: default.network.port,
),
portrait = Settings.Portrait(
dynamicDice = json.dynamicDice ?: default.portrait.dynamicDice,
),
chat = Settings.Chat(
autoHideChat = json.autoHideChat ?: default.chat.autoHideChat,
autoHideDelay = json.autoHideDelay ?: default.chat.autoHideDelay,
autoShowChat = json.autoShowChat ?: default.chat.autoShowChat,
autoScrollChat = json.autoScrollChat ?: default.chat.autoScrollChat,
maxLineCount = json.maxLineCount ?: default.chat.maxLineCount,
),
isAdmin = json.isAdmin ?: default.isAdmin,
isGameMaster = json.isGameMaster ?: default.isGameMaster,
)

View file

@ -1,16 +1,29 @@
package com.pixelized.desktop.lwa.repository.settings.model
data class Settings(
val host: String,
val port: Int,
val playerName: String,
val dynamicDice: Boolean,
val autoHideChat: Boolean,
val autoHideDelay: Int,
val autoShowChat: Boolean,
val autoScrollChat: Boolean,
val portrait: Portrait,
val chat: Chat,
val network: Network,
val isAdmin: Boolean? = null,
val isGameMaster: Boolean? = null,
) {
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
data class Portrait(
val dynamicDice: Boolean,
)
data class Chat(
val autoHideChat: Boolean,
val autoHideDelay: Int,
val autoShowChat: Boolean,
val autoScrollChat: Boolean,
val maxLineCount: Int,
)
data class Network(
val host: String,
val port: Int,
) {
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
}
}

View file

@ -12,6 +12,7 @@ data class SettingsJsonV1(
val autoHideDelay: Int?,
val autoShowChat: Boolean?,
val autoScrollChat: Boolean?,
val maxLineCount: Int?,
val isGameMaster: Boolean?,
val isAdmin: Boolean?,
) : SettingsJson

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
import kotlinx.coroutines.CancellationException
@ -32,8 +33,8 @@ class NetworkViewModel(
) : ViewModel() {
private val settings = settingsRepository.settings()
private val nameFlow = MutableStateFlow(settings.playerName)
private val hostFlow = MutableStateFlow(settings.host)
private val portFlow = MutableStateFlow(settings.port)
private val hostFlow = MutableStateFlow(settings.network.host)
private val portFlow = MutableStateFlow(settings.network.port)
private val _networkError = MutableSharedFlow<ErrorSnackUio>()
val networkError: SharedFlow<ErrorSnackUio> get() = _networkError
@ -57,9 +58,9 @@ class NetworkViewModel(
player = name,
status = status,
host = host,
resetHost = settings.host != host,
resetHost = settings.network.host != host,
port = port,
resetPort = settings.port != port,
resetPort = settings.network.port != port,
)
}.stateIn(
scope = viewModelScope,
@ -71,8 +72,8 @@ class NetworkViewModel(
settingsRepository.settingsFlow()
.onEach {
nameFlow.value = it.playerName
hostFlow.value = it.host
portFlow.value = it.port
hostFlow.value = it.network.host
portFlow.value = it.network.port
}
.launchIn(viewModelScope)
}
@ -86,7 +87,7 @@ class NetworkViewModel(
}
fun onResetPortChange() {
portFlow.value = settings.port
portFlow.value = settings.network.port
}
fun onHostChange(host: String) {
@ -94,7 +95,7 @@ class NetworkViewModel(
}
fun onResetHostChange() {
hostFlow.value = settings.host
hostFlow.value = settings.network.host
}
suspend fun connect() {
@ -103,14 +104,16 @@ class NetworkViewModel(
if (
settings.playerName != nameFlow.value ||
settings.host != hostFlow.value ||
settings.port != portFlow.value
settings.network.host != hostFlow.value ||
settings.network.port != portFlow.value
) {
settingsRepository.update(
settings = settings.copy(
playerName = nameFlow.value,
host = hostFlow.value,
port = portFlow.value
network = Settings.Network(
host = hostFlow.value,
port = portFlow.value,
),
)
)
}

View file

@ -117,7 +117,7 @@ abstract class CharacterRibbonViewModel(
settingsRepository.settingsFlow(),
rollHistoryRepository.rolls,
) { settings, roll ->
if (settings.dynamicDice &&
if (settings.portrait.dynamicDice &&
characterId.equals(roll.prefix, roll.characterSheetId, roll.instanceId)
) {
state.value = CharacterPortraitRollUio(

View file

@ -32,7 +32,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.unit.min
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.screen.campaign.CampaignLayoutScope
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
@ -45,9 +44,27 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessag
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
import kotlinx.coroutines.launch
import org.koin.compose.viewmodel.koinViewModel
@Stable
data class ChatSettingsUio(
val autoShowChat: Boolean,
val autoScrollChat: Boolean,
val autoHideChat: Boolean,
) {
companion object {
fun default() = with(SettingsUseCase().defaultSettings()) {
ChatSettingsUio(
autoShowChat = chat.autoShowChat,
autoScrollChat = chat.autoScrollChat,
autoHideChat = chat.autoHideChat,
)
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun CampaignChat(
@ -119,7 +136,7 @@ fun CampaignChat(
private fun ChatScrollDownEffect(
lazyState: LazyListState,
messages: State<List<TextMessage>>,
settings: State<Settings>,
settings: State<ChatSettingsUio>,
displayChat: suspend () -> Unit,
hideChat: suspend () -> Unit,
) {

View file

@ -6,40 +6,54 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.stateIn
class CampaignChatViewModel(
private val settingsRepository: SettingsRepository,
networkRepository: NetworkRepository,
textMessageFactory: TextMessageFactory,
settingsRepository: SettingsRepository,
) : ViewModel() {
val settings = settingsRepository.settingsFlow()
val chatAnimatedVisibility = Animatable(0f)
private var _messages = emptyList<TextMessage>()
val messages: StateFlow<List<TextMessage>> = networkRepository.data
.mapNotNull { message ->
val text = textMessageFactory
.convertToTextMessage(message = message)
?: return@mapNotNull _messages
_messages = _messages.toMutableList().also {
it.add(index = it.lastIndex + 1, element = text)
}
_messages
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = _messages,
val settings = settingsRepository.settingsFlow().map {
ChatSettingsUio(
autoShowChat = it.chat.autoShowChat,
autoScrollChat = it.chat.autoScrollChat,
autoHideChat = it.chat.autoHideChat,
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = ChatSettingsUio.default(),
)
val messages = combine(
settingsRepository.settingsFlow(),
networkRepository.data.runningFold(
initial = mutableListOf(),
operation = List<SocketMessage>::plus
)
) { settings, messages ->
messages.mapNotNull { message ->
textMessageFactory.convertToTextMessage(
settings = settings,
message = message,
)
}.takeLast(
n = settings.chat.maxLineCount,
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = emptyList(),
)
suspend fun displayChat() {
chatAnimatedVisibility.animateTo(
@ -48,11 +62,12 @@ class CampaignChatViewModel(
}
suspend fun hideChat() {
val settings = settingsRepository.settingsFlow().value
chatAnimatedVisibility.animateTo(
targetValue = 0f,
animationSpec = tween(
durationMillis = 2000,
delayMillis = settings.value.autoHideDelay * 1000,
delayMillis = settings.chat.autoHideDelay * 1000,
)
)
}

View file

@ -1,12 +1,11 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.text
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
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.CampaignMessage
@ -26,13 +25,12 @@ import kotlin.math.abs
class TextMessageFactory(
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 formatTime = SimpleDateFormat("HH:mm:ss")
suspend fun convertToTextMessage(
settings: Settings,
message: SocketMessage,
): TextMessage? {
val time = message.timestamp
@ -44,6 +42,8 @@ class TextMessageFactory(
.characterPreview(characterId = message.characterSheetId)
?: return null
val isGm = settings.isGameMaster ?: false
RollTextMessageUio(
id = "${message.uuid}-${message.timestamp}",
timestamp = formatTime.format(time),
@ -51,7 +51,7 @@ class TextMessageFactory(
skillLabel = message.skillLabel,
rollDifficulty = message.rollDifficulty,
rollValue = message.rollValue,
rollSuccessLimit = message.rollSuccessLimit,
rollSuccessLimit = takeIf { isGm }?.let { message.rollSuccessLimit },
resultLabel = message.resultLabel,
resultType = when (message.critical) {
RollMessage.Critical.CRITICAL_SUCCESS -> RollTextMessageUio.Critical.CRITICAL_SUCCESS

View file

@ -8,7 +8,6 @@ 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
@ -72,7 +71,6 @@ fun RollTextMessage(
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
fontWeight = FontWeight.ExtraLight,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(Res.string.roll_history__item__throw),
@ -114,7 +112,7 @@ fun RollTextMessage(
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "${message.rollValue}",
text = "(${message.rollValue}",
)
message.rollSuccessLimit?.let {
Text(
@ -125,6 +123,13 @@ fun RollTextMessage(
text = "/$it",
)
}
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = ")",
)
}
message.rollDifficulty?.let {
Text(

View file

@ -12,6 +12,7 @@ import com.pixelized.desktop.lwa.usecase.SettingsUseCase
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_fan_focus_24dp
import lwacharactersheet.composeapp.generated.resources.ic_format_list_numbered_24dp
import lwacharactersheet.composeapp.generated.resources.ic_ifl_24dp
import lwacharactersheet.composeapp.generated.resources.ic_timer_24dp
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
@ -28,6 +29,8 @@ import lwacharactersheet.composeapp.generated.resources.settings__chat_log__sect
import lwacharactersheet.composeapp.generated.resources.settings__player_portrait__dyn_dice_description
import lwacharactersheet.composeapp.generated.resources.settings__player_portrait__dyn_dice_tile
import lwacharactersheet.composeapp.generated.resources.settings__player_portrait__section
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__line_count_title
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__line_count_description
class SettingsViewModel(
@ -48,7 +51,11 @@ class SettingsViewModel(
description = Res.string.settings__player_portrait__dyn_dice_description,
checked = booleanStates.dynamicDice,
onToggle = {
settingsRepository.update(settings = settings.value.copy(dynamicDice = it))
settingsRepository.update(
settings = settings.value.copy(
portrait = settings.value.portrait.copy(dynamicDice = it)
)
)
},
),
SettingSectionUio(
@ -60,7 +67,11 @@ class SettingsViewModel(
description = Res.string.settings__chat_log__auto_show_description,
checked = booleanStates.autoShowChat,
onToggle = {
settingsRepository.update(settings = settings.value.copy(autoShowChat = it))
settingsRepository.update(
settings = settings.value.copy(
chat = settings.value.chat.copy(autoShowChat = it)
)
)
},
),
SettingToggleItemUio(
@ -69,7 +80,11 @@ class SettingsViewModel(
description = Res.string.settings__chat_log__auto_hide_description,
checked = booleanStates.autoHideChat,
onToggle = {
settingsRepository.update(settings = settings.value.copy(autoHideChat = it))
settingsRepository.update(
settings = settings.value.copy(
chat = settings.value.chat.copy(autoHideChat = it)
)
)
},
),
SettingNumberItemUio(
@ -80,7 +95,11 @@ class SettingsViewModel(
value = intStates.autoHideDelay,
onValueChange = {
if (it in 0..999) {
settingsRepository.update(settings = settings.value.copy(autoHideDelay = it))
settingsRepository.update(
settings = settings.value.copy(
chat = settings.value.chat.copy(autoHideDelay = it)
)
)
}
}
),
@ -90,19 +109,40 @@ class SettingsViewModel(
description = Res.string.settings__chat_log__auto_scroll_description,
checked = booleanStates.autoScrollChat,
onToggle = {
settingsRepository.update(settings = settings.value.copy(autoScrollChat = it))
settingsRepository.update(
settings = settings.value.copy(
chat = settings.value.chat.copy(autoScrollChat = it)
)
)
},
),
SettingNumberItemUio(
icon = Res.drawable.ic_format_list_numbered_24dp,
title = Res.string.settings__chat_log__line_count_title,
description = Res.string.settings__chat_log__line_count_description,
enable = mutableStateOf(true),
value = intStates.maxLineCount,
onValueChange = {
if (it in 0..999) {
settingsRepository.update(
settings = settings.value.copy(
chat = settings.value.chat.copy(maxLineCount = it)
)
)
}
}
),
)
init {
viewModelScope.launch {
settingsRepository.settingsFlow().collect { settings ->
booleanStates.dynamicDice.value = settings.dynamicDice
booleanStates.autoShowChat.value = settings.autoShowChat
booleanStates.autoHideChat.value = settings.autoHideChat
intStates.autoHideDelay.value = settings.autoHideDelay
booleanStates.autoScrollChat.value = settings.autoScrollChat
booleanStates.dynamicDice.value = settings.portrait.dynamicDice
booleanStates.autoShowChat.value = settings.chat.autoShowChat
booleanStates.autoHideChat.value = settings.chat.autoHideChat
intStates.autoHideDelay.value = settings.chat.autoHideDelay
booleanStates.autoScrollChat.value = settings.chat.autoScrollChat
intStates.maxLineCount.value = settings.chat.maxLineCount
}
}
}
@ -120,17 +160,20 @@ class SettingsViewModel(
}
private val HashMap<String, MutableState<Boolean>>.dynamicDice
get() = getOrPut("DYNAMIC_DICE") { mutableStateOf(settings.value.dynamicDice) }
get() = getOrPut("DYNAMIC_DICE") { mutableStateOf(settings.value.portrait.dynamicDice) }
private val HashMap<String, MutableState<Boolean>>.autoShowChat
get() = getOrPut("AUTO_SHOW_CHAT") { mutableStateOf(settings.value.autoShowChat) }
get() = getOrPut("AUTO_SHOW_CHAT") { mutableStateOf(settings.value.chat.autoShowChat) }
private val HashMap<String, MutableState<Boolean>>.autoHideChat
get() = getOrPut("AUTO_HIDE_CHAT") { mutableStateOf(settings.value.autoHideChat) }
get() = getOrPut("AUTO_HIDE_CHAT") { mutableStateOf(settings.value.chat.autoHideChat) }
private val HashMap<String, MutableState<Int>>.autoHideDelay
get() = getOrPut("AUTO_HIDE_DELAY") { mutableStateOf(settings.value.autoHideDelay) }
get() = getOrPut("AUTO_HIDE_DELAY") { mutableStateOf(settings.value.chat.autoHideDelay) }
private val HashMap<String, MutableState<Boolean>>.autoScrollChat
get() = getOrPut("AUTO_SCROLL_CHAT") { mutableStateOf(settings.value.autoScrollChat) }
get() = getOrPut("AUTO_SCROLL_CHAT") { mutableStateOf(settings.value.chat.autoScrollChat) }
private val HashMap<String, MutableState<Int>>.maxLineCount
get() = getOrPut("MAX_LINE_COUNT") { mutableStateOf(settings.value.chat.maxLineCount) }
}

View file

@ -5,14 +5,21 @@ import com.pixelized.desktop.lwa.repository.settings.model.Settings
class SettingsUseCase {
fun defaultSettings(): Settings = Settings(
host = DEFAULT_HOST,
port = DEFAULT_PORT,
playerName = "",
dynamicDice = true,
autoHideChat = true,
autoHideDelay = 8,
autoShowChat = true,
autoScrollChat = true,
portrait = Settings.Portrait(
dynamicDice = true
),
chat = Settings.Chat(
autoHideChat = true,
autoHideDelay = 8,
autoShowChat = true,
autoScrollChat = true,
maxLineCount = 200,
),
network = Settings.Network(
host = DEFAULT_HOST,
port = DEFAULT_PORT,
),
isAdmin = null,
isGameMaster = null,
)