Link the player character ribbon to the server.
This commit is contained in:
		
							parent
							
								
									ed1b27039d
								
							
						
					
					
						commit
						4ed11660c3
					
				
					 17 changed files with 228 additions and 72 deletions
				
			
		| 
						 | 
				
			
			@ -20,6 +20,7 @@ import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
 | 
			
		|||
import com.pixelized.desktop.lwa.repository.settings.SettingsFactory
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.settings.SettingsStore
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonFactory
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +106,7 @@ val factoryDependencies
 | 
			
		|||
        factoryOf(::SettingsFactory)
 | 
			
		||||
        factoryOf(::CampaignJsonFactory)
 | 
			
		||||
        factoryOf(::PlayerRibbonFactory)
 | 
			
		||||
        factoryOf(::CharacterDetailFactory)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
val viewModelDependencies
 | 
			
		||||
| 
						 | 
				
			
			@ -134,4 +136,4 @@ val useCaseDependencies
 | 
			
		|||
        factoryOf(::ExpressionUseCase)
 | 
			
		||||
        factoryOf(::SettingsUseCase)
 | 
			
		||||
        factoryOf(::CharacterSheetUseCase)
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +99,10 @@ class ExpressionUseCase(
 | 
			
		|||
                rollUseCase.roll(expression.dice)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            is Expression.UrlExpression -> {
 | 
			
		||||
                0 // Ignore this case.
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            is Expression.WordExpression -> when (expression.word.type) {
 | 
			
		||||
                Word.Type.BDC -> evaluate(expressionParser.parse(sheet.damageBonus))
 | 
			
		||||
                Word.Type.BDD -> evaluate(expressionParser.parse(sheet.damageBonus))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,14 @@ sealed interface Expression {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data class UrlExpression(
 | 
			
		||||
        val url: String,
 | 
			
		||||
    ) : Expression {
 | 
			
		||||
        override fun toString(): String {
 | 
			
		||||
            return url
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data class DiceExpression(
 | 
			
		||||
        val dice: Dice,
 | 
			
		||||
    ) : Expression {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,9 @@ package com.pixelized.desktop.lwa.parser.expression
 | 
			
		|||
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.dice.DiceParser
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.word.WordParser
 | 
			
		||||
import org.jetbrains.skia.toIPoint
 | 
			
		||||
import java.net.URI
 | 
			
		||||
import java.net.URL
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Highly inspired by the following javascript implementation:
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +18,12 @@ class ExpressionParser(
 | 
			
		|||
        private val tokenBreak = arrayOf(
 | 
			
		||||
            '+', '-', '/', '*', '(', ')', ','
 | 
			
		||||
        )
 | 
			
		||||
        private val tokenBreakException = mapOf(
 | 
			
		||||
            '/' to listOf("http:", "https:"),
 | 
			
		||||
            '.' to listOf("http:", "https:"),
 | 
			
		||||
            '+' to listOf("http:", "https:"),
 | 
			
		||||
            '-' to listOf("http:", "https:"),
 | 
			
		||||
        )
 | 
			
		||||
        private val operators = mapOf(
 | 
			
		||||
            '+' to Operator(
 | 
			
		||||
                evaluations = { first, second -> Expression.Add(first, second) },
 | 
			
		||||
| 
						 | 
				
			
			@ -50,8 +59,11 @@ class ExpressionParser(
 | 
			
		|||
     * Every characters that are not un the [Companion.tokenBreak] list can be part of a token.
 | 
			
		||||
     * @see Companion.tokenBreak
 | 
			
		||||
     */
 | 
			
		||||
    private fun isToken(): Boolean = stack.peek().let {
 | 
			
		||||
        it != null && !tokenBreak.contains(it)
 | 
			
		||||
    private fun isToken(
 | 
			
		||||
        currentToken: String? = null,
 | 
			
		||||
    ): Boolean = stack.peek().let {
 | 
			
		||||
        it != null && !tokenBreak.contains(it) ||
 | 
			
		||||
                (tokenBreakException[it]?.any { currentToken?.contains(it) ?: false } ?: false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +81,7 @@ class ExpressionParser(
 | 
			
		|||
        val token = StringBuilder()
 | 
			
		||||
        do {
 | 
			
		||||
            stack.pull().let(token::append)
 | 
			
		||||
        } while (isToken())
 | 
			
		||||
        } while (isToken(token.toString()))
 | 
			
		||||
        return token.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -186,6 +198,16 @@ class ExpressionParser(
 | 
			
		|||
                        return Expression.DiceExpression(dice)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    val url = try {
 | 
			
		||||
                        println(token)
 | 
			
		||||
                        URI.create(token).toString()
 | 
			
		||||
                    } catch (_: Exception) {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                    if (url != null) {
 | 
			
		||||
                        return Expression.UrlExpression(url)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    throw Error.UnRecognizedToken(actual = token, expression = stack.input)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ class AlterationRepository(
 | 
			
		|||
    private val activeAlterationIdMapFlow: HashMap<CharacterId, MutableStateFlow<List<AlterationId>>> =
 | 
			
		||||
        hashMapOf("0f2117e9-e077-4354-8d77-20150df1c462" to MutableStateFlow(listOf("7c00dafa-a67d-4351-8ea9-67d933012cde", "65e37d32-3031-4bf8-9369-d2c45d2efac0")))
 | 
			
		||||
 | 
			
		||||
    fun alterations(characterId: String): Flow<Map<String, List<FieldAlteration>>> {
 | 
			
		||||
    fun alterationsFlow(characterId: String): Flow<Map<String, List<FieldAlteration>>> {
 | 
			
		||||
        return activeAlterationIdMapFlow
 | 
			
		||||
            .getOrPut(characterId) { MutableStateFlow(emptyList()) }
 | 
			
		||||
            .map { activeAlterationIds ->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.SharingStarted
 | 
			
		|||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
class CharacterSheetRepository(
 | 
			
		||||
    private val store: CharacterSheetStore,
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +16,7 @@ class CharacterSheetRepository(
 | 
			
		|||
    private val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
 | 
			
		||||
    val characterSheetPreviewFlow get() = store.previewFlow
 | 
			
		||||
    val characterDetailFlow get() = store.detailFlow
 | 
			
		||||
 | 
			
		||||
    fun characterPreview(characterId: String?): CharacterSheetPreview? {
 | 
			
		||||
        return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId }
 | 
			
		||||
| 
						 | 
				
			
			@ -35,17 +35,8 @@ class CharacterSheetRepository(
 | 
			
		|||
 | 
			
		||||
    fun characterDetailFlow(
 | 
			
		||||
        characterId: String?,
 | 
			
		||||
        forceUpdate: Boolean = false,
 | 
			
		||||
    ): StateFlow<CharacterSheet?> {
 | 
			
		||||
        val initial = store.detailFlow.value[characterId]
 | 
			
		||||
        if (forceUpdate || initial == null) {
 | 
			
		||||
            scope.launch {
 | 
			
		||||
                characterDetail(
 | 
			
		||||
                    characterId = characterId,
 | 
			
		||||
                    forceUpdate = forceUpdate,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return store.detailFlow
 | 
			
		||||
            .map { sheets ->
 | 
			
		||||
                sheets[characterId]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ import org.jetbrains.compose.resources.painterResource
 | 
			
		|||
import org.koin.compose.viewmodel.koinViewModel
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class CharacterDetailUio(
 | 
			
		||||
data class CharacterDetailHeaderUio(
 | 
			
		||||
    val id: String,
 | 
			
		||||
    val portrait: String?,
 | 
			
		||||
    val name: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +55,7 @@ data class CharacterDetailUio(
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class CharacterDynDetailUio(
 | 
			
		||||
data class CharacterDetailHeaderInstanceUio(
 | 
			
		||||
    val hp: String,
 | 
			
		||||
    val pp: String,
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -109,8 +109,8 @@ fun CharacterDetail(
 | 
			
		|||
@Composable
 | 
			
		||||
fun CharacterDetailContent(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    character: CharacterDetailUio,
 | 
			
		||||
    dynDetail: State<CharacterDynDetailUio?>,
 | 
			
		||||
    character: CharacterDetailHeaderUio,
 | 
			
		||||
    dynDetail: State<CharacterDetailHeaderInstanceUio?>,
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onDiminished: () -> Unit,
 | 
			
		||||
    onHp: () -> Unit,
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ fun CharacterDetailContent(
 | 
			
		|||
@Composable
 | 
			
		||||
private fun Background(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    character: CharacterDetailUio,
 | 
			
		||||
    character: CharacterDetailHeaderUio,
 | 
			
		||||
) {
 | 
			
		||||
    Surface(
 | 
			
		||||
        modifier = modifier.fillMaxSize(),
 | 
			
		||||
| 
						 | 
				
			
			@ -168,8 +168,8 @@ private fun Background(
 | 
			
		|||
@Composable
 | 
			
		||||
private fun CharacterHeader(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    character: CharacterDetailUio,
 | 
			
		||||
    dynDetail: State<CharacterDynDetailUio?>,
 | 
			
		||||
    character: CharacterDetailHeaderUio,
 | 
			
		||||
    dynDetail: State<CharacterDetailHeaderInstanceUio?>,
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onDiminished: () -> Unit,
 | 
			
		||||
    onHp: () -> Unit,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.business.ExpressionUseCase
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.expression.Expression
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.HP
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.MOV
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.PORTRAIT
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.PP
 | 
			
		||||
 | 
			
		||||
class CharacterDetailFactory(
 | 
			
		||||
    private val expressionUseCase: ExpressionUseCase,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun convertToCharacterDetailHeaderUio(
 | 
			
		||||
        sheet: CharacterSheet?,
 | 
			
		||||
        alterations: Map<String, List<FieldAlteration>>,
 | 
			
		||||
    ): CharacterDetailHeaderUio? {
 | 
			
		||||
        if (sheet == null) return null
 | 
			
		||||
 | 
			
		||||
        fun List<FieldAlteration>?.sum(): Int {
 | 
			
		||||
            return this?.sumOf {
 | 
			
		||||
                expressionUseCase.computeExpression(sheet = sheet, expression = it.expression)
 | 
			
		||||
            } ?: 0
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CharacterDetailHeaderUio(
 | 
			
		||||
            id = sheet.id,
 | 
			
		||||
            portrait = alterations[PORTRAIT]
 | 
			
		||||
                ?.firstNotNullOfOrNull { (it.expression as? Expression.UrlExpression)?.url }
 | 
			
		||||
                ?: sheet.portrait,
 | 
			
		||||
            name = sheet.name,
 | 
			
		||||
            hp = "${sheet.hp + alterations[HP].sum()}",
 | 
			
		||||
            pp = "${sheet.pp + alterations[PP].sum()}",
 | 
			
		||||
            mov = "${sheet.movement + alterations[MOV].sum()}"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13,33 +13,43 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
 | 
			
		|||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.damage
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.power
 | 
			
		||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.SharingStarted
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.combine
 | 
			
		||||
import kotlinx.coroutines.flow.mapNotNull
 | 
			
		||||
import kotlinx.coroutines.flow.emptyFlow
 | 
			
		||||
import kotlinx.coroutines.flow.flatMap
 | 
			
		||||
import kotlinx.coroutines.flow.flatMapConcat
 | 
			
		||||
import kotlinx.coroutines.flow.flatMapLatest
 | 
			
		||||
import kotlinx.coroutines.flow.flow
 | 
			
		||||
import kotlinx.coroutines.flow.flowOf
 | 
			
		||||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
 | 
			
		||||
class CharacterDetailViewModel(
 | 
			
		||||
    private val characterRepository: CharacterSheetRepository,
 | 
			
		||||
    private val campaignRepository: CampaignRepository,
 | 
			
		||||
    private val alterationRepository: AlterationRepository,
 | 
			
		||||
    private val characterDetailFactory: CharacterDetailFactory,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
 | 
			
		||||
    private val displayedCharacterId = MutableStateFlow<String?>(null)
 | 
			
		||||
 | 
			
		||||
    val detail: StateFlow<CharacterDetailUio?> = displayedCharacterId.map { id ->
 | 
			
		||||
        val sheet = characterRepository.characterDetail(id) ?: return@map null
 | 
			
		||||
        CharacterDetailUio(
 | 
			
		||||
            id = sheet.id,
 | 
			
		||||
            portrait = sheet.portrait,
 | 
			
		||||
            name = sheet.name,
 | 
			
		||||
            hp = "${sheet.hp}",
 | 
			
		||||
            pp = "${sheet.pp}",
 | 
			
		||||
            mov = "${sheet.movement}"
 | 
			
		||||
        )
 | 
			
		||||
    @OptIn(ExperimentalCoroutinesApi::class)
 | 
			
		||||
    val detail: StateFlow<CharacterDetailHeaderUio?> = displayedCharacterId.flatMapLatest { id ->
 | 
			
		||||
        if (id != null) {
 | 
			
		||||
            combine(
 | 
			
		||||
                characterRepository.characterDetailFlow(characterId = id),
 | 
			
		||||
                alterationRepository.alterationsFlow(characterId = id),
 | 
			
		||||
            ) { sheet, alteration ->
 | 
			
		||||
                characterDetailFactory.convertToCharacterDetailHeaderUio(
 | 
			
		||||
                    sheet = sheet,
 | 
			
		||||
                    alterations = alteration,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            flowOf(null)
 | 
			
		||||
        }
 | 
			
		||||
    }.stateIn(
 | 
			
		||||
        scope = viewModelScope,
 | 
			
		||||
        started = SharingStarted.Eagerly,
 | 
			
		||||
| 
						 | 
				
			
			@ -48,15 +58,15 @@ class CharacterDetailViewModel(
 | 
			
		|||
 | 
			
		||||
    @Composable
 | 
			
		||||
    @Stable
 | 
			
		||||
    fun collectDynamicDetailAsState(id: String): State<CharacterDynDetailUio?> {
 | 
			
		||||
    fun collectDynamicDetailAsState(id: String): State<CharacterDetailHeaderInstanceUio?> {
 | 
			
		||||
        val scope = rememberCoroutineScope()
 | 
			
		||||
        val flow: StateFlow<CharacterDynDetailUio?> = remember(id) {
 | 
			
		||||
        val flow: StateFlow<CharacterDetailHeaderInstanceUio?> = remember(id) {
 | 
			
		||||
            combine(
 | 
			
		||||
                characterRepository.characterDetailFlow(id),
 | 
			
		||||
                campaignRepository.characterInstanceFlow(id = id),
 | 
			
		||||
            ) { sheet, instance ->
 | 
			
		||||
                if (sheet == null) return@combine null
 | 
			
		||||
                CharacterDynDetailUio(
 | 
			
		||||
                CharacterDetailHeaderInstanceUio(
 | 
			
		||||
                    hp = "${sheet.hp - instance.damage}",
 | 
			
		||||
                    pp = "${sheet.power - instance.power}",
 | 
			
		||||
                )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,24 +1,48 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.business.ExpressionUseCase
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.expression.Expression
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.Campaign
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.damage
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.power
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.HP
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.PP
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.THUMBNAIL
 | 
			
		||||
 | 
			
		||||
class PlayerRibbonFactory {
 | 
			
		||||
class PlayerRibbonFactory(
 | 
			
		||||
    private val expressionUseCase: ExpressionUseCase,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun convertToPlayerPortraitUio(
 | 
			
		||||
        characterSheet: CharacterSheet?,
 | 
			
		||||
        characterInstance: Campaign.CharacterInstance,
 | 
			
		||||
        alterations: Map<String, List<FieldAlteration>>,
 | 
			
		||||
    ): PlayerPortraitUio? {
 | 
			
		||||
        if (characterSheet == null) return null
 | 
			
		||||
 | 
			
		||||
        fun List<FieldAlteration>?.sum(): Int {
 | 
			
		||||
            return this?.sumOf {
 | 
			
		||||
                expressionUseCase.computeExpression(
 | 
			
		||||
                    sheet = characterSheet,
 | 
			
		||||
                    expression = it.expression,
 | 
			
		||||
                )
 | 
			
		||||
            } ?: 0
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val maxHp = characterSheet.hp + alterations[HP].sum()
 | 
			
		||||
        val maxPp = characterSheet.pp + alterations[PP].sum()
 | 
			
		||||
 | 
			
		||||
        return PlayerPortraitUio(
 | 
			
		||||
            id = characterSheet.id,
 | 
			
		||||
            portrait = characterSheet.thumbnail,
 | 
			
		||||
            hp = characterSheet.hp - characterInstance.damage,
 | 
			
		||||
            maxHp = characterSheet.hp,
 | 
			
		||||
            pp = characterSheet.pp - characterInstance.power,
 | 
			
		||||
            maxPp = characterSheet.pp,
 | 
			
		||||
            portrait = alterations[THUMBNAIL]
 | 
			
		||||
                ?.firstNotNullOfOrNull { (it.expression as? Expression.UrlExpression)?.url }
 | 
			
		||||
                ?: characterSheet.thumbnail,
 | 
			
		||||
            hp = maxHp - characterInstance.damage,
 | 
			
		||||
            maxHp = maxHp,
 | 
			
		||||
            pp = maxPp - characterInstance.power,
 | 
			
		||||
            maxPp = maxPp,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,30 +8,49 @@ 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 kotlinx.coroutines.ExperimentalCoroutinesApi
 | 
			
		||||
import kotlinx.coroutines.flow.SharingStarted
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import kotlinx.coroutines.flow.combine
 | 
			
		||||
import kotlinx.coroutines.flow.flatMapMerge
 | 
			
		||||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
class PlayerRibbonViewModel(
 | 
			
		||||
    private val rollHistoryRepository: RollHistoryRepository,
 | 
			
		||||
    characterRepository: CharacterSheetRepository,
 | 
			
		||||
    alterationRepository: AlterationRepository,
 | 
			
		||||
    private val ribbonFactory: PlayerRibbonFactory,
 | 
			
		||||
    campaignRepository: CampaignRepository,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
 | 
			
		||||
    @OptIn(ExperimentalCoroutinesApi::class)
 | 
			
		||||
    val characters: StateFlow<List<PlayerPortraitUio>> = campaignRepository.campaignFlow
 | 
			
		||||
        .map { campaign ->
 | 
			
		||||
            campaign.characters.mapNotNull { entry ->
 | 
			
		||||
                ribbonFactory.convertToPlayerPortraitUio(
 | 
			
		||||
                    characterSheet = characterRepository.characterDetail(characterId = entry.key),
 | 
			
		||||
                    characterInstance = entry.value,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }.stateIn(
 | 
			
		||||
        .flatMapMerge { campaign ->
 | 
			
		||||
            combine<PlayerPortraitUio?, List<PlayerPortraitUio>>(
 | 
			
		||||
                flows = campaign.characters.map { entry ->
 | 
			
		||||
                    combine(
 | 
			
		||||
                        characterRepository.characterDetailFlow(characterId = entry.key),
 | 
			
		||||
                        alterationRepository.alterationsFlow(characterId = entry.key),
 | 
			
		||||
                    ) { sheet, alterations ->
 | 
			
		||||
                        ribbonFactory.convertToPlayerPortraitUio(
 | 
			
		||||
                            characterSheet = sheet,
 | 
			
		||||
                            characterInstance = entry.value,
 | 
			
		||||
                            alterations = alterations,
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                transform = { headers ->
 | 
			
		||||
                    headers.mapNotNull { it }.toList()
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        .stateIn(
 | 
			
		||||
            scope = viewModelScope,
 | 
			
		||||
            started = SharingStarted.Eagerly,
 | 
			
		||||
            initialValue = emptyList()
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +58,19 @@ class PlayerRibbonViewModel(
 | 
			
		|||
 | 
			
		||||
    private val rolls = hashMapOf<String, MutableState<PlayerPortraitRollUio?>>()
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            campaignRepository.campaignFlow.collectLatest {
 | 
			
		||||
                it.characters.keys.forEach { id ->
 | 
			
		||||
                    characterRepository.characterDetail(
 | 
			
		||||
                        characterId = id,
 | 
			
		||||
                        forceUpdate = true,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    @Stable
 | 
			
		||||
    fun roll(characterId: String): State<PlayerPortraitRollUio?> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,7 +46,6 @@ class CharacterSheetFactory(
 | 
			
		|||
    private val skillUseCase: ExpressionUseCase,
 | 
			
		||||
    private val expressionUseCase: ExpressionUseCase,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    suspend fun convertToUio(
 | 
			
		||||
        sheet: CharacterSheet?,
 | 
			
		||||
        campaign: Campaign,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,7 @@ class CharacterSheetViewModel(
 | 
			
		|||
    private val sheetFlow = combine(
 | 
			
		||||
        characterRepository.characterDetailFlow(characterId = argument.id),
 | 
			
		||||
        campaignRepository.campaignFlow,
 | 
			
		||||
        alteration.alterations(characterId = argument.id),
 | 
			
		||||
        alteration.alterationsFlow(characterId = argument.id),
 | 
			
		||||
        transform = { sheet, campaign, alterations ->
 | 
			
		||||
            factory.convertToUio(
 | 
			
		||||
                sheet = sheet,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.business
 | 
			
		||||
 | 
			
		||||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
class DamageBonusUseCaseTest {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.business
 | 
			
		||||
 | 
			
		||||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
class SkillNormalizerUseCaseText {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,6 +62,26 @@ class ExpressionParserTest {
 | 
			
		|||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun textUrlExpression() {
 | 
			
		||||
        val parser = ExpressionParser(
 | 
			
		||||
            diceParser = DiceParser(),
 | 
			
		||||
            wordParser = WordParser(),
 | 
			
		||||
        )
 | 
			
		||||
        parser.test(
 | 
			
		||||
            expression = "https://drive.google.com/uc?export=view&id=19ME-r7LiDQSrDCq8faVlTNQ7tfX8UUOO",
 | 
			
		||||
            expected = Expression.UrlExpression("https://drive.google.com/uc?export=view&id=19ME-r7LiDQSrDCq8faVlTNQ7tfX8UUOO"),
 | 
			
		||||
        )
 | 
			
		||||
        parser.test(
 | 
			
		||||
            expression = "www.google.fr",
 | 
			
		||||
            expected = Expression.UrlExpression("www.google.fr"),
 | 
			
		||||
        )
 | 
			
		||||
        parser.test(
 | 
			
		||||
            expression = "google.fr",
 | 
			
		||||
            expected = Expression.UrlExpression("google.fr"),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testFunctionExpression() {
 | 
			
		||||
        val parser = ExpressionParser(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue