diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbonViewModel.kt new file mode 100644 index 0000000..742a089 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbonViewModel.kt @@ -0,0 +1,122 @@ +package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +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.roll_history.RollHistoryRepository +import com.pixelized.desktop.lwa.repository.settings.SettingsRepository +import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRollUio +import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitUio +import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.stateIn +import java.text.Collator + +abstract class CharacterRibbonViewModel( + private val rollHistoryRepository: RollHistoryRepository, + private val settingsRepository: SettingsRepository, + characterRepository: CharacterSheetRepository, + alterationRepository: AlterationRepository, + campaignRepository: CampaignRepository, + private val ribbonFactory: CharacterRibbonFactory, +) : ViewModel() { + + private val rolls = hashMapOf>() + + abstract val Campaign.data: Map + + /** + * This flow is a tad complex so there is an explanation of wtf it's about : + * On a campaign update it go through every element of the abstract [data] map and either: + * - build and flow of an empty list to handle the case where the map is empty. + * - build a flow of flow bind to each character & alteration to update portrait details. + * Then sort the result. + */ + @OptIn(ExperimentalCoroutinesApi::class) + val characters: StateFlow> = campaignRepository.campaignFlow + .flatMapMerge { campaign -> + when (campaign.data.isEmpty()) { + true -> flowOf(emptyList()) + else -> combine>( + flows = campaign.data.map { entry -> + combine( + characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId), + alterationRepository.alterationsFlow(characterInstanceId = entry.key), + ) { sheet, alterations -> + ribbonFactory.convertToPlayerPortraitUio( + characterSheet = sheet, + alterations = alterations, + characterInstanceId = entry.key, + characterInstance = entry.value, + enableDetail = settingsRepository.settings().isGM, + displayCharacterStats = settingsRepository.settings().isGM, + ) + } + }, + transform = { headers -> + headers.mapNotNull { it } + .sortedWith(compareBy(Collator.getInstance()) { it.name }) + .toList() + } + ) + } + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + + @Composable + @Stable + fun roll( + characterId: CharacterInstance.Id, + ): State { + val state = rolls.getOrPut(characterId) { mutableStateOf(null) } + + LaunchedEffect(characterId) { + combine( + settingsRepository.settingsFlow(), + rollHistoryRepository.rolls, + ) { settings, roll -> + if (settings.dynamicDice && + characterId.equals(roll.prefix, roll.characterSheetId, roll.instanceId) + ) { + state.value = CharacterPortraitRollUio( + characterId = CharacterInstance.Id( + prefix = characterId.prefix, + characterSheetId = characterId.characterSheetId, + instanceId = characterId.instanceId, + ), + value = roll.rollValue, + label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it } + ) + } + }.launchIn(this) + } + + return state + } + + fun onPortraitRollRightClick( + characterId: CharacterInstance.Id, + ) { + rolls[characterId]?.value = null + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt index 96aea29..f3637c6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt @@ -1,114 +1,28 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope 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.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository -import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRollUio -import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel import com.pixelized.shared.lwa.model.campaign.Campaign -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapMerge -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.stateIn -import java.text.Collator - -private typealias CharacterId = Campaign.CharacterInstance.Id class NpcRibbonViewModel( - private val rollHistoryRepository: RollHistoryRepository, - private val settingsRepository: SettingsRepository, + rollHistoryRepository: RollHistoryRepository, + settingsRepository: SettingsRepository, characterRepository: CharacterSheetRepository, alterationRepository: AlterationRepository, campaignRepository: CampaignRepository, - private val ribbonFactory: CharacterRibbonFactory, -) : ViewModel() { - private val rolls = hashMapOf>() - - @OptIn(ExperimentalCoroutinesApi::class) - val characters: StateFlow> = campaignRepository.campaignFlow - .flatMapMerge { campaign -> - if (campaign.npcs.isEmpty()) { - flowOf(emptyList()) - } else { - combine>( - flows = campaign.npcs.map { entry -> - combine( - characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId), - alterationRepository.alterationsFlow(characterInstanceId = entry.key), - ) { sheet, alterations -> - ribbonFactory.convertToPlayerPortraitUio( - characterSheet = sheet, - alterations = alterations, - characterInstanceId = entry.key, - characterInstance = entry.value, - enableDetail = settingsRepository.settings().isGM, - displayCharacterStats = settingsRepository.settings().isGM, - ) - } - }, - transform = { headers -> - headers.mapNotNull { it } - .sortedWith(compareBy(Collator.getInstance()) { it.name }) - .toList() - } - ) - } - } - .stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = emptyList() - ) - - @Composable - @Stable - fun roll( - characterId: Campaign.CharacterInstance.Id, - ): State { - val state = rolls.getOrPut(characterId) { mutableStateOf(null) } - - LaunchedEffect(characterId) { - combine( - settingsRepository.settingsFlow(), - rollHistoryRepository.rolls, - ) { settings, roll -> - if (settings.dynamicDice && - characterId.equals(roll.prefix, roll.characterSheetId, roll.instanceId) - ) { - state.value = CharacterPortraitRollUio( - characterId = Campaign.CharacterInstance.Id( - prefix = characterId.prefix, - characterSheetId = characterId.characterSheetId, - instanceId = characterId.instanceId, - ), - value = roll.rollValue, - label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it } - ) - } - }.launchIn(this) - } - - return state - } - - fun onPortraitRollRightClick( - characterId: Campaign.CharacterInstance.Id, - ) { - rolls[characterId]?.value = null - } + ribbonFactory: CharacterRibbonFactory, +) : CharacterRibbonViewModel( + rollHistoryRepository = rollHistoryRepository, + settingsRepository = settingsRepository, + characterRepository = characterRepository, + alterationRepository = alterationRepository, + campaignRepository = campaignRepository, + ribbonFactory = ribbonFactory, +) { + override val Campaign.data get() = npcs } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt index 6baf311..2c54998 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt @@ -1,115 +1,28 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope 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.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository -import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRollUio -import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel import com.pixelized.shared.lwa.model.campaign.Campaign -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapMerge -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.stateIn -import java.text.Collator - -private typealias CharacterId = Campaign.CharacterInstance.Id class PlayerRibbonViewModel( - private val rollHistoryRepository: RollHistoryRepository, - private val settingsRepository: SettingsRepository, + rollHistoryRepository: RollHistoryRepository, + settingsRepository: SettingsRepository, characterRepository: CharacterSheetRepository, alterationRepository: AlterationRepository, campaignRepository: CampaignRepository, - private val ribbonFactory: CharacterRibbonFactory, -) : ViewModel() { - - private val rolls = hashMapOf>() - - @OptIn(ExperimentalCoroutinesApi::class) - val characters: StateFlow> = campaignRepository.campaignFlow - .flatMapMerge { campaign -> - if (campaign.characters.isEmpty()) { - flowOf(emptyList()) - } else { - combine>( - flows = campaign.characters.map { entry -> - combine( - characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId), - alterationRepository.alterationsFlow(characterInstanceId = entry.key), - ) { sheet, alterations -> - ribbonFactory.convertToPlayerPortraitUio( - characterSheet = sheet, - alterations = alterations, - characterInstanceId = entry.key, - characterInstance = entry.value, - enableDetail = true, - displayCharacterStats = true, - ) - } - }, - transform = { headers -> - headers.mapNotNull { it } - .sortedWith(compareBy(Collator.getInstance()) { it.name }) - .toList() - } - ) - } - } - .stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = emptyList() - ) - - @Composable - @Stable - fun roll( - characterId: Campaign.CharacterInstance.Id, - ): State { - val state = rolls.getOrPut(characterId) { mutableStateOf(null) } - - LaunchedEffect(characterId) { - combine( - settingsRepository.settingsFlow(), - rollHistoryRepository.rolls, - ) { settings, roll -> - if (settings.dynamicDice && - characterId.equals(roll.prefix, roll.characterSheetId, roll.instanceId) - ) { - state.value = CharacterPortraitRollUio( - characterId = Campaign.CharacterInstance.Id( - prefix = characterId.prefix, - characterSheetId = characterId.characterSheetId, - instanceId = characterId.instanceId, - ), - value = roll.rollValue, - label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it } - ) - } - }.launchIn(this) - } - - return state - } - - fun onPortraitRollRightClick( - characterId: Campaign.CharacterInstance.Id, - ) { - rolls[characterId]?.value = null - } + ribbonFactory: CharacterRibbonFactory, +) : CharacterRibbonViewModel( + rollHistoryRepository = rollHistoryRepository, + settingsRepository = settingsRepository, + characterRepository = characterRepository, + alterationRepository = alterationRepository, + campaignRepository = campaignRepository, + ribbonFactory = ribbonFactory, +) { + override val Campaign.data get() = characters } \ No newline at end of file