Update the old / new UI to work with the server.
This commit is contained in:
		
							parent
							
								
									bd4d65fe6a
								
							
						
					
					
						commit
						ed1b27039d
					
				
					 40 changed files with 568 additions and 390 deletions
				
			
		| 
						 | 
				
			
			@ -29,8 +29,11 @@ import androidx.compose.ui.unit.Dp
 | 
			
		|||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.window.ApplicationScope
 | 
			
		||||
import androidx.compose.ui.window.Window
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
 | 
			
		||||
| 
						 | 
				
			
			@ -39,12 +42,8 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheet
 | 
			
		|||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CampaignScreen
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignScreen
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +53,6 @@ import lwacharactersheet.composeapp.generated.resources.network__disconnect__mes
 | 
			
		|||
import org.jetbrains.compose.resources.getString
 | 
			
		||||
import org.jetbrains.compose.ui.tooling.preview.Preview
 | 
			
		||||
import org.koin.compose.koinInject
 | 
			
		||||
import org.koin.compose.viewmodel.koinViewModel
 | 
			
		||||
 | 
			
		||||
val LocalWindowController = compositionLocalOf<WindowController> {
 | 
			
		||||
    error("Local Window Controller is not yet ready")
 | 
			
		||||
| 
						 | 
				
			
			@ -127,8 +125,8 @@ fun ApplicationScope.App() {
 | 
			
		|||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        content = {
 | 
			
		||||
                            MainNavHost()
 | 
			
		||||
//                            CampaignScreen()
 | 
			
		||||
//                            MainNavHost()
 | 
			
		||||
                            CampaignScreen()
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                    NetworkSnackHandler(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,8 @@ import com.pixelized.desktop.lwa.business.ExpressionUseCase
 | 
			
		|||
import com.pixelized.desktop.lwa.business.RollUseCase
 | 
			
		||||
import com.pixelized.desktop.lwa.business.SettingsUseCase
 | 
			
		||||
import com.pixelized.desktop.lwa.business.SkillStepUseCase
 | 
			
		||||
import com.pixelized.desktop.lwa.network.LwaClient
 | 
			
		||||
import com.pixelized.desktop.lwa.network.LwaClientImpl
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.dice.DiceParser
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.expression.ExpressionParser
 | 
			
		||||
import com.pixelized.desktop.lwa.parser.word.WordParser
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +22,7 @@ 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.CharacterDetailViewModel
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonFactory
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetFactory
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetViewModel
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +75,7 @@ val toolsDependencies
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        single<LwaClient> { LwaClientImpl(get(), get()) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
val storeDependencies
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +104,7 @@ val factoryDependencies
 | 
			
		|||
        factoryOf(::SkillFieldFactory)
 | 
			
		||||
        factoryOf(::SettingsFactory)
 | 
			
		||||
        factoryOf(::CampaignJsonFactory)
 | 
			
		||||
        factoryOf(::PlayerRibbonFactory)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
val viewModelDependencies
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,13 @@ import com.pixelized.desktop.lwa.repository.settings.model.Settings
 | 
			
		|||
class SettingsUseCase {
 | 
			
		||||
 | 
			
		||||
    fun defaultSettings(): Settings = Settings(
 | 
			
		||||
        host = DEFAULT_HOST,
 | 
			
		||||
        port = DEFAULT_PORT,
 | 
			
		||||
        playerName = "",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val DEFAULT_HOST = "http://pixelized.freeboxos.fr"
 | 
			
		||||
        private const val DEFAULT_PORT = 16030
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.network
 | 
			
		||||
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
 | 
			
		||||
 | 
			
		||||
interface LwaClient {
 | 
			
		||||
 | 
			
		||||
    suspend fun characters(): List<CharacterPreviewJson>
 | 
			
		||||
 | 
			
		||||
    suspend fun character(id: String): CharacterSheetJson
 | 
			
		||||
 | 
			
		||||
    suspend fun updateCharacter(sheet: CharacterSheetJson)
 | 
			
		||||
 | 
			
		||||
    suspend fun deleteCharacter(id: String)
 | 
			
		||||
 | 
			
		||||
    suspend fun campaign(): CampaignJson
 | 
			
		||||
 | 
			
		||||
    suspend fun campaignAddCharacter(id: String)
 | 
			
		||||
 | 
			
		||||
    suspend fun campaignDeleteCharacter(id: String)
 | 
			
		||||
 | 
			
		||||
    suspend fun campaignAddNpc(id: String)
 | 
			
		||||
 | 
			
		||||
    suspend fun campaignDeleteNpc(id: String)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.network
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.call.body
 | 
			
		||||
import io.ktor.client.request.delete
 | 
			
		||||
import io.ktor.client.request.get
 | 
			
		||||
import io.ktor.client.request.put
 | 
			
		||||
import io.ktor.client.request.setBody
 | 
			
		||||
import io.ktor.http.ContentType
 | 
			
		||||
import io.ktor.http.contentType
 | 
			
		||||
import io.ktor.http.path
 | 
			
		||||
 | 
			
		||||
class LwaClientImpl(
 | 
			
		||||
    private val client: HttpClient,
 | 
			
		||||
    setting: SettingsRepository,
 | 
			
		||||
) : LwaClient {
 | 
			
		||||
    private val root = setting.settings().root
 | 
			
		||||
 | 
			
		||||
    override suspend fun characters(): List<CharacterPreviewJson> = client
 | 
			
		||||
        .get("$root/characters")
 | 
			
		||||
        .body()
 | 
			
		||||
 | 
			
		||||
    override suspend fun character(id: String): CharacterSheetJson = client
 | 
			
		||||
        .get("$root/character/detail?id=$id")
 | 
			
		||||
        .body()
 | 
			
		||||
 | 
			
		||||
    override suspend fun updateCharacter(sheet: CharacterSheetJson) = client
 | 
			
		||||
        .put {
 | 
			
		||||
            url {
 | 
			
		||||
                path("$host/character/update?id=")
 | 
			
		||||
                contentType(ContentType.Application.Json)
 | 
			
		||||
                setBody(sheet)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .body<Unit>()
 | 
			
		||||
 | 
			
		||||
    override suspend fun deleteCharacter(id: String) = client
 | 
			
		||||
        .delete("$root/character/delete?id=$id")
 | 
			
		||||
        .body<Unit>()
 | 
			
		||||
 | 
			
		||||
    override suspend fun campaign(): CampaignJson = client
 | 
			
		||||
        .get("$root/campaign")
 | 
			
		||||
        .body()
 | 
			
		||||
 | 
			
		||||
    override suspend fun campaignAddCharacter(id: String) = client
 | 
			
		||||
        .put("$root/campaign/character/update?id=$id")
 | 
			
		||||
        .body<Unit>()
 | 
			
		||||
 | 
			
		||||
    override suspend fun campaignDeleteCharacter(id: String) = client
 | 
			
		||||
        .delete("$root/campaign/character/delete?id=$id")
 | 
			
		||||
        .body<Unit>()
 | 
			
		||||
 | 
			
		||||
    override suspend fun campaignAddNpc(id: String) = client
 | 
			
		||||
        .put("$root/campaign/npc/update?id=$id")
 | 
			
		||||
        .body<Unit>()
 | 
			
		||||
 | 
			
		||||
    override suspend fun campaignDeleteNpc(id: String) = client
 | 
			
		||||
        .delete("$root/campaign/npc/delete?id=$id")
 | 
			
		||||
        .body<Unit>()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.campaign
 | 
			
		||||
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.Campaign
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.character
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.Job
 | 
			
		||||
| 
						 | 
				
			
			@ -10,31 +11,21 @@ import kotlinx.coroutines.flow.mapNotNull
 | 
			
		|||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
 | 
			
		||||
class CampaignRepository(
 | 
			
		||||
    store: CampaignStore,
 | 
			
		||||
    private val store: CampaignStore,
 | 
			
		||||
) {
 | 
			
		||||
    private val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
 | 
			
		||||
    private val campaign = store.campaignFlow()
 | 
			
		||||
        .stateIn(
 | 
			
		||||
            scope = scope,
 | 
			
		||||
            started = SharingStarted.Eagerly,
 | 
			
		||||
            initialValue = Campaign.EMPTY,
 | 
			
		||||
        )
 | 
			
		||||
    val campaignFlow get() = store.campaignFlow
 | 
			
		||||
 | 
			
		||||
    fun campaignFlow(): StateFlow<Campaign> = campaign
 | 
			
		||||
 | 
			
		||||
    fun characterInstance(id: String): StateFlow<Campaign.CharacterInstance> {
 | 
			
		||||
        return campaign
 | 
			
		||||
    fun characterInstanceFlow(id: String): StateFlow<Campaign.CharacterInstance> {
 | 
			
		||||
        return campaignFlow
 | 
			
		||||
            .mapNotNull {
 | 
			
		||||
                it.characters[id]
 | 
			
		||||
            }
 | 
			
		||||
            .stateIn(
 | 
			
		||||
                scope = scope,
 | 
			
		||||
                started = SharingStarted.Eagerly,
 | 
			
		||||
                initialValue = campaign.value.characters[id] ?: Campaign.CharacterInstance(
 | 
			
		||||
                    characteristic = emptyMap(),
 | 
			
		||||
                    usedSkill = emptyList(),
 | 
			
		||||
                )
 | 
			
		||||
                initialValue = campaignFlow.value.character(id = id),
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,72 +1,82 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.campaign
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.network.LwaClient
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.Campaign
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.MessageType
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.character
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.Message
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage
 | 
			
		||||
import com.pixelized.shared.lwa.usecase.CampaignUseCase
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.call.body
 | 
			
		||||
import io.ktor.client.request.get
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.Job
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.mapNotNull
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlin.collections.set
 | 
			
		||||
 | 
			
		||||
class CampaignStore(
 | 
			
		||||
    private val client: LwaClient,
 | 
			
		||||
    private val network: NetworkRepository,
 | 
			
		||||
    private val factory: CampaignJsonFactory,
 | 
			
		||||
    private val useCase: CampaignUseCase,
 | 
			
		||||
    private val client: HttpClient,
 | 
			
		||||
    private val json: Json,
 | 
			
		||||
) {
 | 
			
		||||
    private val flow = MutableStateFlow(value = Campaign.EMPTY)
 | 
			
		||||
    private val _campaignFlow = MutableStateFlow(value = Campaign.EMPTY)
 | 
			
		||||
    val campaignFlow: StateFlow<Campaign> get() = _campaignFlow
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
        scope.launch {
 | 
			
		||||
            flow.value = load()
 | 
			
		||||
            update()
 | 
			
		||||
        }
 | 
			
		||||
        scope.launch {
 | 
			
		||||
            network.data
 | 
			
		||||
                .mapNotNull { it.takeIf { it.type == MessageType.UpdatePlayerCharacteristic } }
 | 
			
		||||
                .map { json.decodeFromString<UpdatePlayerCharacteristicMessage>(it.value) }
 | 
			
		||||
                .collect {
 | 
			
		||||
                    updateCharacteristic(it)
 | 
			
		||||
                }
 | 
			
		||||
            network.data.collect(::handleMessage)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun campaignFlow(): StateFlow<Campaign> = flow
 | 
			
		||||
    private suspend fun update() {
 | 
			
		||||
        _campaignFlow.value = load()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun load(): Campaign {
 | 
			
		||||
        val request: CampaignJson = client
 | 
			
		||||
            .get("http://pixelized.freeboxos.fr:16030/campaign") // TODO
 | 
			
		||||
            .body()
 | 
			
		||||
        val request = client.campaign()
 | 
			
		||||
        val data = factory.convertFromJson(json = request)
 | 
			
		||||
        return data
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateCharacteristic(
 | 
			
		||||
        message: UpdatePlayerCharacteristicMessage,
 | 
			
		||||
        characterId: String,
 | 
			
		||||
        characteristic: Campaign.CharacterInstance.Characteristic,
 | 
			
		||||
        value: Int,
 | 
			
		||||
    ) {
 | 
			
		||||
        val characters = flow.value.characters.toMutableMap()
 | 
			
		||||
        val character = characters[message.characterId] ?: Campaign.CharacterInstance(
 | 
			
		||||
            characteristic = emptyMap(),
 | 
			
		||||
            usedSkill = emptyList(),
 | 
			
		||||
        )
 | 
			
		||||
        characters[message.characterId] = useCase.updateCharacteristic(
 | 
			
		||||
            character = character,
 | 
			
		||||
            characteristic = message.characteristic,
 | 
			
		||||
            value = message.value
 | 
			
		||||
        )
 | 
			
		||||
        flow.value = flow.value.copy(characters = characters)
 | 
			
		||||
        val campaign = _campaignFlow.value
 | 
			
		||||
        val characters = campaign.characters.toMutableMap().also {
 | 
			
		||||
            it[characterId] = useCase.updateCharacteristic(
 | 
			
		||||
                character = campaign.character(id = characterId),
 | 
			
		||||
                characteristic = characteristic,
 | 
			
		||||
                value = value
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        _campaignFlow.value = _campaignFlow.value.copy(characters = characters)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun handleMessage(message: Message) {
 | 
			
		||||
        when (val payload = message.value) {
 | 
			
		||||
            is RestSynchronisation.Campaign -> {
 | 
			
		||||
                update()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            is UpdatePlayerCharacteristicMessage -> {
 | 
			
		||||
                updateCharacteristic(
 | 
			
		||||
                    characterId = payload.characterId,
 | 
			
		||||
                    characteristic = payload.characteristic,
 | 
			
		||||
                    value = payload.value,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            else -> Unit
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.campaign.model
 | 
			
		||||
 | 
			
		||||
data class CharacterSheetPreview(
 | 
			
		||||
    val id: String,
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val level: Int,
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +1,67 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.characterSheet
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.Job
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
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,
 | 
			
		||||
) {
 | 
			
		||||
    private val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
 | 
			
		||||
    private val sheets = store.characterSheetFlow()
 | 
			
		||||
        .stateIn(
 | 
			
		||||
            scope = scope,
 | 
			
		||||
            started = SharingStarted.Eagerly,
 | 
			
		||||
            initialValue = emptyList()
 | 
			
		||||
        )
 | 
			
		||||
    val characterSheetPreviewFlow get() = store.previewFlow
 | 
			
		||||
 | 
			
		||||
    private val diminished = MutableStateFlow<Map<String, Int>>(emptyMap())
 | 
			
		||||
 | 
			
		||||
    fun characterSheetFlow(): StateFlow<List<CharacterSheet>> {
 | 
			
		||||
        return sheets
 | 
			
		||||
    fun characterPreview(characterId: String?): CharacterSheetPreview? {
 | 
			
		||||
        return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun characterSheetFlow(id: String?): StateFlow<CharacterSheet?> {
 | 
			
		||||
        return sheets
 | 
			
		||||
            .map { sheets ->
 | 
			
		||||
                sheets.firstOrNull { sheet -> sheet.id == id }
 | 
			
		||||
            }
 | 
			
		||||
            .stateIn(
 | 
			
		||||
                scope = scope,
 | 
			
		||||
                started = SharingStarted.Eagerly,
 | 
			
		||||
                initialValue = sheets.value.firstOrNull { it.id == id }
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun characterDiminishedFlow(id: String?): StateFlow<Int> {
 | 
			
		||||
        return diminished
 | 
			
		||||
            .map {
 | 
			
		||||
                it[id] ?: 0
 | 
			
		||||
            }
 | 
			
		||||
            .stateIn(
 | 
			
		||||
                scope = scope,
 | 
			
		||||
                started = SharingStarted.Eagerly,
 | 
			
		||||
                initialValue = diminished.value[id] ?: 0
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun save(characterSheet: CharacterSheet) {
 | 
			
		||||
//        store.save(sheet = characterSheet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun delete(id: String) {
 | 
			
		||||
//        store.delete(id = id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setDiminishedForCharacter(id: String, diminished: Int) {
 | 
			
		||||
        this.diminished.value = this.diminished.value.toMutableMap().apply {
 | 
			
		||||
            this[id] = diminished
 | 
			
		||||
    suspend fun characterDetail(
 | 
			
		||||
        characterId: String?,
 | 
			
		||||
        forceUpdate: Boolean = false,
 | 
			
		||||
    ): CharacterSheet? {
 | 
			
		||||
        return try {
 | 
			
		||||
            characterId?.let { store.characterDetail(characterId = it, forceUpdate = forceUpdate) }
 | 
			
		||||
        } catch (exception: Exception) {
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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]
 | 
			
		||||
            }
 | 
			
		||||
            .stateIn(
 | 
			
		||||
                scope = scope,
 | 
			
		||||
                started = SharingStarted.Eagerly,
 | 
			
		||||
                initialValue = initial,
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun updateCharacter(characterSheet: CharacterSheet) {
 | 
			
		||||
        store.updateCharacter(sheet = characterSheet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun deleteCharacter(characterId: String) {
 | 
			
		||||
        store.deleteCharacter(characterId = characterId)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,77 +1,146 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.characterSheet
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.network.LwaClient
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.MessageType
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.Message
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
 | 
			
		||||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.client.call.body
 | 
			
		||||
import io.ktor.client.request.get
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.Job
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.mapNotNull
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
 | 
			
		||||
class CharacterSheetStore(
 | 
			
		||||
    private val client: LwaClient,
 | 
			
		||||
    private val network: NetworkRepository,
 | 
			
		||||
    private val factory: CharacterSheetJsonFactory,
 | 
			
		||||
    private val useCase: CharacterSheetUseCase,
 | 
			
		||||
    private val client: HttpClient,
 | 
			
		||||
    private val json: Json,
 | 
			
		||||
) {
 | 
			
		||||
    private val flow = MutableStateFlow<List<CharacterSheet>>(value = emptyList())
 | 
			
		||||
    private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
 | 
			
		||||
    val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow
 | 
			
		||||
    private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
 | 
			
		||||
    val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
        // initial data loading.
 | 
			
		||||
        scope.launch {
 | 
			
		||||
            flow.value = load()
 | 
			
		||||
            _previewFlow.value = charactersPreview()
 | 
			
		||||
        }
 | 
			
		||||
        // data update through WebSocket.
 | 
			
		||||
        scope.launch {
 | 
			
		||||
            network.data
 | 
			
		||||
                .mapNotNull { it.takeIf { it.type == MessageType.UpdateSkillUsage } }
 | 
			
		||||
                .map { json.decodeFromString<UpdateSkillUsageMessage>(it.value) }
 | 
			
		||||
                .collect {
 | 
			
		||||
                    updateCharacterSkillChange(
 | 
			
		||||
                        characterId = it.characterId,
 | 
			
		||||
                        skillId = it.skillId,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            network.data.collect(::handleMessage)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun characterSheetFlow(): StateFlow<List<CharacterSheet>> = flow
 | 
			
		||||
    // region Rest
 | 
			
		||||
 | 
			
		||||
    suspend fun load(): List<CharacterSheet> {
 | 
			
		||||
        val request: List<CharacterSheetJson> = client
 | 
			
		||||
            .get("http://pixelized.freeboxos.fr:16030/characters") // TODO
 | 
			
		||||
            .body()
 | 
			
		||||
    suspend fun charactersPreview(): List<CharacterSheetPreview> {
 | 
			
		||||
        val request = client.characters()
 | 
			
		||||
        val data = request.map {
 | 
			
		||||
            factory.convertFromJson(json = it)
 | 
			
		||||
            CharacterSheetPreview(
 | 
			
		||||
                id = it.id,
 | 
			
		||||
                name = it.name,
 | 
			
		||||
                level = it.level,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        return data
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateCharacterSkillChange(
 | 
			
		||||
    suspend fun characterDetail(
 | 
			
		||||
        characterId: String,
 | 
			
		||||
        forceUpdate: Boolean = false,
 | 
			
		||||
    ): CharacterSheet {
 | 
			
		||||
        val character = _detailFlow.value[characterId]
 | 
			
		||||
        if (forceUpdate || character == null) {
 | 
			
		||||
            val request = client.character(id = characterId)
 | 
			
		||||
            val data = factory.convertFromJson(json = request)
 | 
			
		||||
            // update the local detail flow.
 | 
			
		||||
            return _detailFlow.update(data)
 | 
			
		||||
        } else {
 | 
			
		||||
            return character
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun updateCharacter(
 | 
			
		||||
        sheet: CharacterSheet,
 | 
			
		||||
    ) {
 | 
			
		||||
        val json = factory.convertToJson(sheet = sheet)
 | 
			
		||||
        try {
 | 
			
		||||
            client.updateCharacter(sheet = json)
 | 
			
		||||
        } catch (exception: Exception) {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        _detailFlow.update(sheet = sheet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun deleteCharacter(
 | 
			
		||||
        characterId: String,
 | 
			
		||||
    ) {
 | 
			
		||||
        try {
 | 
			
		||||
            client.deleteCharacter(id = characterId)
 | 
			
		||||
        } catch (exception: Exception) {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        _detailFlow.delete(characterId = characterId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // endregion
 | 
			
		||||
    // region: WebSocket & data update.
 | 
			
		||||
 | 
			
		||||
    private suspend fun handleMessage(message: Message) {
 | 
			
		||||
        when (val payload = message.value) {
 | 
			
		||||
            is RestSynchronisation.CharacterUpdate -> characterDetail(
 | 
			
		||||
                characterId = payload.id,
 | 
			
		||||
                forceUpdate = true
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            is RestSynchronisation.CharacterDelete -> {
 | 
			
		||||
                _previewFlow.value = previewFlow.value.toMutableList()
 | 
			
		||||
                    .also { sheets -> sheets.removeIf { it.id == payload.characterId } }
 | 
			
		||||
                _detailFlow.delete(payload.characterId)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            is UpdateSkillUsageMessage -> {
 | 
			
		||||
                updateCharacterSkillChange(
 | 
			
		||||
                    characterId = payload.characterId,
 | 
			
		||||
                    skillId = payload.skillId,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            else -> Unit
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun updateCharacterSkillChange(
 | 
			
		||||
        characterId: String,
 | 
			
		||||
        skillId: String,
 | 
			
		||||
    ) {
 | 
			
		||||
        val characters = flow.value.toMutableList()
 | 
			
		||||
        val index = characters.indexOfFirst { it.id == characterId }
 | 
			
		||||
        val character = useCase.updateSkillUsage(
 | 
			
		||||
            character = characterDetail(characterId = characterId),
 | 
			
		||||
            skillId = skillId,
 | 
			
		||||
        )
 | 
			
		||||
        _detailFlow.update(character)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        if (index > -1) {
 | 
			
		||||
            characters[index] = useCase.updateSkillUsage(
 | 
			
		||||
                character = characters[index],
 | 
			
		||||
                skillId = skillId,
 | 
			
		||||
            )
 | 
			
		||||
            flow.value = characters
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    private fun MutableStateFlow<Map<String, CharacterSheet>>.update(sheet: CharacterSheet): CharacterSheet {
 | 
			
		||||
        value = value.toMutableMap().also {
 | 
			
		||||
            it[sheet.id] = sheet
 | 
			
		||||
        }
 | 
			
		||||
        return sheet
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun MutableStateFlow<Map<String, CharacterSheet>>.delete(characterId: String) {
 | 
			
		||||
        value = value.toMutableMap().also {
 | 
			
		||||
            it.remove(characterId)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,13 +4,8 @@ import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
 | 
			
		|||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.utils.extention.decodeFromFrame
 | 
			
		||||
import com.pixelized.desktop.lwa.utils.extention.encodeToFrame
 | 
			
		||||
import com.pixelized.shared.lwa.SERVER_PORT
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.Message
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.MessageType
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.MessagePayload
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.RollMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.Message
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.MessagePayload
 | 
			
		||||
import io.ktor.client.HttpClient
 | 
			
		||||
import io.ktor.websocket.Frame
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
| 
						 | 
				
			
			@ -23,19 +18,12 @@ import kotlinx.coroutines.flow.SharedFlow
 | 
			
		|||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
 | 
			
		||||
class NetworkRepository(
 | 
			
		||||
    private val settingsRepository: SettingsRepository,
 | 
			
		||||
    private val client: HttpClient,
 | 
			
		||||
    private val json: Json,
 | 
			
		||||
) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val DEFAULT_PORT = SERVER_PORT
 | 
			
		||||
        const val DEFAULT_HOST = "pixelized.freeboxos.fr"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val scope = CoroutineScope(Dispatchers.IO)
 | 
			
		||||
    private var networkJob: Job? = null
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -97,40 +85,11 @@ class NetworkRepository(
 | 
			
		|||
    suspend fun share(
 | 
			
		||||
        playerName: String = settingsRepository.settings().playerName,
 | 
			
		||||
        payload: MessagePayload,
 | 
			
		||||
    ) {
 | 
			
		||||
        if (status.value == Status.CONNECTED) {
 | 
			
		||||
            when (payload) {
 | 
			
		||||
                is RollMessage -> share(
 | 
			
		||||
                    playerName = playerName,
 | 
			
		||||
                    type = MessageType.Roll,
 | 
			
		||||
                    content = json.encodeToString(payload),
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                is UpdateSkillUsageMessage -> share(
 | 
			
		||||
                    playerName = playerName,
 | 
			
		||||
                    type = MessageType.UpdateSkillUsage,
 | 
			
		||||
                    content = json.encodeToString(payload),
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                is UpdatePlayerCharacteristicMessage -> share(
 | 
			
		||||
                    playerName = playerName,
 | 
			
		||||
                    type = MessageType.UpdatePlayerCharacteristic,
 | 
			
		||||
                    content = json.encodeToString(payload),
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun share(
 | 
			
		||||
        playerName: String = settingsRepository.settings().playerName,
 | 
			
		||||
        type: MessageType,
 | 
			
		||||
        content: String,
 | 
			
		||||
    ) {
 | 
			
		||||
        if (status.value == Status.CONNECTED) {
 | 
			
		||||
            val message = Message(
 | 
			
		||||
                from = playerName,
 | 
			
		||||
                type = type,
 | 
			
		||||
                value = content,
 | 
			
		||||
                value = payload,
 | 
			
		||||
            )
 | 
			
		||||
            // emit the message into the outgoing buffer
 | 
			
		||||
            outgoingMessageBuffer.emit(message)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,28 +1,22 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.roll_history
 | 
			
		||||
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.MessageType
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.RollMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.SharedFlow
 | 
			
		||||
import kotlinx.coroutines.flow.SharingStarted
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.mapNotNull
 | 
			
		||||
import kotlinx.coroutines.flow.shareIn
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
 | 
			
		||||
class RollHistoryRepository(
 | 
			
		||||
    private val network: NetworkRepository,
 | 
			
		||||
    private val jsonFormatter: Json,
 | 
			
		||||
) {
 | 
			
		||||
    private val scope = CoroutineScope(Dispatchers.IO)
 | 
			
		||||
 | 
			
		||||
    val rolls: SharedFlow<RollMessage> = network.data
 | 
			
		||||
        .mapNotNull { it.takeIf { it.type == MessageType.Roll } }
 | 
			
		||||
        .map { jsonFormatter.decodeFromString<RollMessage>(it.value) }
 | 
			
		||||
        .mapNotNull { it.value as? RollMessage }
 | 
			
		||||
        .shareIn(
 | 
			
		||||
            scope = scope,
 | 
			
		||||
            started = SharingStarted.Eagerly,
 | 
			
		||||
| 
						 | 
				
			
			@ -44,9 +38,6 @@ class RollHistoryRepository(
 | 
			
		|||
            resultLabel = resultLabel,
 | 
			
		||||
            rollSuccessLimit = rollSuccessLimit,
 | 
			
		||||
        )
 | 
			
		||||
        network.share(
 | 
			
		||||
            type = MessageType.Roll,
 | 
			
		||||
            content = jsonFormatter.encodeToString(content),
 | 
			
		||||
        )
 | 
			
		||||
        network.share(payload = content)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +14,8 @@ class SettingsFactory(
 | 
			
		|||
        settings: Settings,
 | 
			
		||||
    ): SettingsJson {
 | 
			
		||||
        return SettingsJsonV1(
 | 
			
		||||
            host = settings.host,
 | 
			
		||||
            port = settings.port,
 | 
			
		||||
            playerName = settings.playerName,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -29,10 +31,11 @@ class SettingsFactory(
 | 
			
		|||
    private fun convertFromJsonV1(
 | 
			
		||||
        json: SettingsJsonV1,
 | 
			
		||||
    ): Settings {
 | 
			
		||||
        return with(useCase.defaultSettings()) {
 | 
			
		||||
            Settings(
 | 
			
		||||
                playerName = json.playerName ?: playerName
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        val default = useCase.defaultSettings()
 | 
			
		||||
        return Settings(
 | 
			
		||||
            host = json.host ?: default.host,
 | 
			
		||||
            port = json.port ?: default.port,
 | 
			
		||||
            playerName = json.playerName ?: default.playerName
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -19,12 +19,14 @@ class SettingsStore(
 | 
			
		|||
    private val useCase: SettingsUseCase,
 | 
			
		||||
    private val jsonFormatter: Json,
 | 
			
		||||
) {
 | 
			
		||||
    private val settingsDirectory = File(storePath()).also { it.mkdirs() }
 | 
			
		||||
    private val flow = MutableStateFlow(value = useCase.defaultSettings())
 | 
			
		||||
 | 
			
		||||
    fun settingsFlow(): StateFlow<Settings> = flow
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        // create the directory
 | 
			
		||||
        File(storePath()).also { it.mkdirs() }
 | 
			
		||||
        // load the data.
 | 
			
		||||
        val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
        scope.launch {
 | 
			
		||||
            flow.value = load()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.repository.settings.model
 | 
			
		||||
 | 
			
		||||
data class Settings(
 | 
			
		||||
    val host: String,
 | 
			
		||||
    val port: Int,
 | 
			
		||||
    val playerName: String,
 | 
			
		||||
)
 | 
			
		||||
) {
 | 
			
		||||
    val root: String get() = "http://${host}:${port}"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,5 +4,7 @@ import kotlinx.serialization.Serializable
 | 
			
		|||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class SettingsJsonV1(
 | 
			
		||||
    val host: String?,
 | 
			
		||||
    val port: Int?,
 | 
			
		||||
    val playerName: String?,
 | 
			
		||||
) : SettingsJson
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player
 | 
			
		||||
package com.pixelized.desktop.lwa.ui.screen.campaign
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,7 +90,9 @@ fun CharacterDetail(
 | 
			
		|||
                        .width(width = 128.dp * 4),
 | 
			
		||||
                    character = it,
 | 
			
		||||
                    dynDetail = dynDetail,
 | 
			
		||||
                    onDismissRequest = { viewModel.hideCharacter() },
 | 
			
		||||
                    onDismissRequest = {
 | 
			
		||||
                        viewModel.hideCharacter()
 | 
			
		||||
                    },
 | 
			
		||||
                    onDiminished = {
 | 
			
		||||
                        scope.launch {
 | 
			
		||||
                            dismissedViewModel.showDiminishedDialog(id = it.id)
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +110,7 @@ fun CharacterDetail(
 | 
			
		|||
fun CharacterDetailContent(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    character: CharacterDetailUio,
 | 
			
		||||
    dynDetail: State<CharacterDynDetailUio>,
 | 
			
		||||
    dynDetail: State<CharacterDynDetailUio?>,
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onDiminished: () -> Unit,
 | 
			
		||||
    onHp: () -> Unit,
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +169,7 @@ private fun Background(
 | 
			
		|||
private fun CharacterHeader(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    character: CharacterDetailUio,
 | 
			
		||||
    dynDetail: State<CharacterDynDetailUio>,
 | 
			
		||||
    dynDetail: State<CharacterDynDetailUio?>,
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onDiminished: () -> Unit,
 | 
			
		||||
    onHp: () -> Unit,
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +224,7 @@ private fun CharacterHeader(
 | 
			
		|||
                    style = MaterialTheme.typography.h6,
 | 
			
		||||
                    color = MaterialTheme.lwa.colorScheme.base.primary,
 | 
			
		||||
                    fontWeight = FontWeight.Bold,
 | 
			
		||||
                    text = dynDetail.value.hp,
 | 
			
		||||
                    text = dynDetail.value?.hp ?: character.hp,
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    modifier = Modifier.alignByBaseline(),
 | 
			
		||||
| 
						 | 
				
			
			@ -245,7 +247,7 @@ private fun CharacterHeader(
 | 
			
		|||
                    style = MaterialTheme.typography.h6,
 | 
			
		||||
                    color = MaterialTheme.lwa.colorScheme.base.primary,
 | 
			
		||||
                    fontWeight = FontWeight.Bold,
 | 
			
		||||
                    text = dynDetail.value.pp,
 | 
			
		||||
                    text = dynDetail.value?.pp ?: character.pp,
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    modifier = Modifier.alignByBaseline(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,19 +5,22 @@ import androidx.compose.runtime.Stable
 | 
			
		|||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
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.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic.Damage
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic.Power
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.damage
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.power
 | 
			
		||||
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.stateIn
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
 | 
			
		||||
class CharacterDetailViewModel(
 | 
			
		||||
    private val characterRepository: CharacterSheetRepository,
 | 
			
		||||
| 
						 | 
				
			
			@ -27,19 +30,14 @@ class CharacterDetailViewModel(
 | 
			
		|||
 | 
			
		||||
    private val displayedCharacterId = MutableStateFlow<String?>(null)
 | 
			
		||||
 | 
			
		||||
    val detail: StateFlow<CharacterDetailUio?> = combine(
 | 
			
		||||
        displayedCharacterId,
 | 
			
		||||
        characterRepository.characterSheetFlow(),
 | 
			
		||||
        campaignRepository.campaignFlow(),
 | 
			
		||||
    ) { id, sheets, campaign ->
 | 
			
		||||
        val sheet = sheets.firstOrNull { it.id == id }
 | 
			
		||||
        if (sheet == null) return@combine 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 - (campaign.characters[id]?.characteristic?.get(Damage) ?: 0)}",
 | 
			
		||||
            pp = "${sheet.pp - (campaign.characters[id]?.characteristic?.get(Power) ?: 0)}",
 | 
			
		||||
            hp = "${sheet.hp}",
 | 
			
		||||
            pp = "${sheet.pp}",
 | 
			
		||||
            mov = "${sheet.movement}"
 | 
			
		||||
        )
 | 
			
		||||
    }.stateIn(
 | 
			
		||||
| 
						 | 
				
			
			@ -50,23 +48,25 @@ class CharacterDetailViewModel(
 | 
			
		|||
 | 
			
		||||
    @Composable
 | 
			
		||||
    @Stable
 | 
			
		||||
    fun collectDynamicDetailAsState(id: String): State<CharacterDynDetailUio> {
 | 
			
		||||
        val flow = remember(id) {
 | 
			
		||||
            campaignRepository.characterInstance(id = id)
 | 
			
		||||
        }
 | 
			
		||||
        return remember(id) {
 | 
			
		||||
            flow.mapNotNull { sheet ->
 | 
			
		||||
    fun collectDynamicDetailAsState(id: String): State<CharacterDynDetailUio?> {
 | 
			
		||||
        val scope = rememberCoroutineScope()
 | 
			
		||||
        val flow: StateFlow<CharacterDynDetailUio?> = remember(id) {
 | 
			
		||||
            combine(
 | 
			
		||||
                characterRepository.characterDetailFlow(id),
 | 
			
		||||
                campaignRepository.characterInstanceFlow(id = id),
 | 
			
		||||
            ) { sheet, instance ->
 | 
			
		||||
                if (sheet == null) return@combine null
 | 
			
		||||
                CharacterDynDetailUio(
 | 
			
		||||
                    hp = sheet.characteristic[Damage].toString(),
 | 
			
		||||
                    pp = sheet.characteristic[Power].toString(),
 | 
			
		||||
                    hp = "${sheet.hp - instance.damage}",
 | 
			
		||||
                    pp = "${sheet.power - instance.power}",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }.collectAsState(
 | 
			
		||||
            CharacterDynDetailUio(
 | 
			
		||||
                hp = flow.value.characteristic[Damage].toString(),
 | 
			
		||||
                pp = flow.value.characteristic[Power].toString(),
 | 
			
		||||
            }.stateIn(
 | 
			
		||||
                scope = scope,
 | 
			
		||||
                started = SharingStarted.Eagerly,
 | 
			
		||||
                initialValue = null,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        }
 | 
			
		||||
        return flow.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun showCharacter(id: String) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,9 +19,10 @@ class CharacterDiminishedViewModel(
 | 
			
		|||
    val diminishedDialog: State<DiminishedStatDialogUio?> get() = _diminishedDialog
 | 
			
		||||
 | 
			
		||||
    suspend fun showDiminishedDialog(id: String) {
 | 
			
		||||
        val diminished = repository.characterDiminishedFlow(id = id).value
 | 
			
		||||
        val textFieldValue =
 | 
			
		||||
            mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0)))
 | 
			
		||||
        val diminished = 0 // TODO repository.characterDiminishedFlow(id = id).value
 | 
			
		||||
        val textFieldValue = mutableStateOf(
 | 
			
		||||
            TextFieldValue("$diminished", selection = TextRange(index = 0))
 | 
			
		||||
        )
 | 
			
		||||
        _diminishedDialog.value = DiminishedStatDialogUio(
 | 
			
		||||
            id = id,
 | 
			
		||||
            label = getString(resource = Res.string.character_sheet__diminished__label),
 | 
			
		||||
| 
						 | 
				
			
			@ -41,9 +42,10 @@ class CharacterDiminishedViewModel(
 | 
			
		|||
 | 
			
		||||
    fun changeDiminished(dialog: DiminishedStatDialogUio) {
 | 
			
		||||
        val value = dialog.value().text.toIntOrNull() ?: 0
 | 
			
		||||
        repository.setDiminishedForCharacter(
 | 
			
		||||
            id = dialog.id,
 | 
			
		||||
            diminished = value,
 | 
			
		||||
        )
 | 
			
		||||
        // TODO
 | 
			
		||||
//        repository.setDiminishedForCharacter(
 | 
			
		||||
//            id = dialog.id,
 | 
			
		||||
//            diminished = value,
 | 
			
		||||
//        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -35,9 +35,9 @@ import org.jetbrains.compose.resources.painterResource
 | 
			
		|||
data class PlayerPortraitUio(
 | 
			
		||||
    val id: String,
 | 
			
		||||
    val portrait: String?,
 | 
			
		||||
    val damage: Int,
 | 
			
		||||
    val hp: Int,
 | 
			
		||||
    val maxHp: Int,
 | 
			
		||||
    val usedPp: Int,
 | 
			
		||||
    val pp: Int,
 | 
			
		||||
    val maxPp: Int,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +96,7 @@ fun PlayerPortrait(
 | 
			
		|||
                Text(
 | 
			
		||||
                    modifier = Modifier.padding(bottom = 2.dp),
 | 
			
		||||
                    style = MaterialTheme.typography.caption,
 | 
			
		||||
                    text = "${character.maxHp - character.damage}/${character.maxHp}",
 | 
			
		||||
                    text = "${character.hp}/${character.maxHp}",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            Row(
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +111,7 @@ fun PlayerPortrait(
 | 
			
		|||
                Text(
 | 
			
		||||
                    modifier = Modifier.padding(bottom = 2.dp),
 | 
			
		||||
                    style = MaterialTheme.typography.caption,
 | 
			
		||||
                    text = "${character.maxPp - character.usedPp}/${character.maxPp}",
 | 
			
		||||
                    text = "${character.pp}/${character.maxPp}",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,6 +46,7 @@ import org.jetbrains.compose.resources.painterResource
 | 
			
		|||
data class PlayerPortraitRollUio(
 | 
			
		||||
    val characterId: String,
 | 
			
		||||
    val value: Int?,
 | 
			
		||||
    val label: String?,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
| 
						 | 
				
			
			@ -119,6 +120,12 @@ fun PlayerPortraitRoll(
 | 
			
		|||
                    color = MaterialTheme.colors.onSurface,
 | 
			
		||||
                    text = it.value.toString()
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    modifier = Modifier.padding(top = 84.dp),
 | 
			
		||||
                    textAlign = TextAlign.Center,
 | 
			
		||||
                    style = MaterialTheme.typography.caption,
 | 
			
		||||
                    text = it.label ?: "",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
class PlayerRibbonFactory {
 | 
			
		||||
 | 
			
		||||
    fun convertToPlayerPortraitUio(
 | 
			
		||||
        characterSheet: CharacterSheet?,
 | 
			
		||||
        characterInstance: Campaign.CharacterInstance,
 | 
			
		||||
    ): PlayerPortraitUio? {
 | 
			
		||||
        if (characterSheet == null) return null
 | 
			
		||||
        return PlayerPortraitUio(
 | 
			
		||||
            id = characterSheet.id,
 | 
			
		||||
            portrait = characterSheet.thumbnail,
 | 
			
		||||
            hp = characterSheet.hp - characterInstance.damage,
 | 
			
		||||
            maxHp = characterSheet.hp,
 | 
			
		||||
            pp = characterSheet.pp - characterInstance.power,
 | 
			
		||||
            maxPp = characterSheet.pp,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11,39 +11,31 @@ import androidx.lifecycle.viewModelScope
 | 
			
		|||
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.shared.lwa.model.campaign.character
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.damage
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.power
 | 
			
		||||
import kotlinx.coroutines.flow.SharingStarted
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.combine
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
 | 
			
		||||
class PlayerRibbonViewModel(
 | 
			
		||||
    private val rollHistoryRepository: RollHistoryRepository,
 | 
			
		||||
    characterRepository: CharacterSheetRepository,
 | 
			
		||||
    private val ribbonFactory: PlayerRibbonFactory,
 | 
			
		||||
    campaignRepository: CampaignRepository,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
    val characters: StateFlow<List<PlayerPortraitUio>> = combine(
 | 
			
		||||
        characterRepository.characterSheetFlow(),
 | 
			
		||||
        campaignRepository.campaignFlow(),
 | 
			
		||||
    ) { sheets, campaign ->
 | 
			
		||||
        sheets.map { sheet ->
 | 
			
		||||
            val instance = campaign.character(id = sheet.id)
 | 
			
		||||
            PlayerPortraitUio(
 | 
			
		||||
                id = sheet.id,
 | 
			
		||||
                portrait = sheet.thumbnail,
 | 
			
		||||
                damage = instance.damage,
 | 
			
		||||
                maxHp = sheet.hp,
 | 
			
		||||
                usedPp = instance.power,
 | 
			
		||||
                maxPp = sheet.pp,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }.stateIn(
 | 
			
		||||
        scope = viewModelScope,
 | 
			
		||||
        started = SharingStarted.Eagerly,
 | 
			
		||||
        initialValue = emptyList()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
            scope = viewModelScope,
 | 
			
		||||
            started = SharingStarted.Eagerly,
 | 
			
		||||
            initialValue = emptyList()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    private val rolls = hashMapOf<String, MutableState<PlayerPortraitRollUio?>>()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,11 +44,12 @@ class PlayerRibbonViewModel(
 | 
			
		|||
    fun roll(characterId: String): State<PlayerPortraitRollUio?> {
 | 
			
		||||
        val state = rolls.getOrPut(characterId) { mutableStateOf(null) }
 | 
			
		||||
        LaunchedEffect(characterId) {
 | 
			
		||||
            rollHistoryRepository.rolls.collect {
 | 
			
		||||
                if (it.characterId == characterId) {
 | 
			
		||||
            rollHistoryRepository.rolls.collect { roll ->
 | 
			
		||||
                if (roll.characterId == characterId) {
 | 
			
		||||
                    state.value = PlayerPortraitRollUio(
 | 
			
		||||
                        characterId = characterId,
 | 
			
		||||
                        value = it.rollValue,
 | 
			
		||||
                        value = roll.rollValue,
 | 
			
		||||
                        label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it }
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
 | 
			
		|||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.text.TextRange
 | 
			
		||||
import androidx.compose.ui.text.input.TextFieldValue
 | 
			
		||||
import androidx.lifecycle.SavedStateHandle
 | 
			
		||||
| 
						 | 
				
			
			@ -17,21 +18,19 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheet
 | 
			
		|||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialogUio
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.StatChangeDialogUio
 | 
			
		||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
 | 
			
		||||
import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic
 | 
			
		||||
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.protocol.payload.UpdatePlayerCharacteristicMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage
 | 
			
		||||
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
 | 
			
		||||
import kotlinx.coroutines.flow.SharingStarted
 | 
			
		||||
import kotlinx.coroutines.flow.combine
 | 
			
		||||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.Res
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hit_point
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__power_point
 | 
			
		||||
import org.jetbrains.compose.resources.getString
 | 
			
		||||
| 
						 | 
				
			
			@ -62,16 +61,18 @@ class CharacterSheetViewModel(
 | 
			
		|||
    private val _diminishedDialog = mutableStateOf<DiminishedStatDialogUio?>(null)
 | 
			
		||||
    val diminishedDialog: State<DiminishedStatDialogUio?> get() = _diminishedDialog
 | 
			
		||||
 | 
			
		||||
    private val diminishedValueFlow = characterRepository.characterDiminishedFlow(id = argument.id)
 | 
			
		||||
    // TODO
 | 
			
		||||
//    private val diminishedValueFlow = characterRepository.characterDiminishedFlow(id = argument.id)
 | 
			
		||||
    val diminishedValue: State<Int?>
 | 
			
		||||
        @Composable
 | 
			
		||||
        get() = diminishedValueFlow.collectAsState { it ->
 | 
			
		||||
            it.takeIf { it > 0 }
 | 
			
		||||
        }
 | 
			
		||||
//        get() = diminishedValueFlow.collectAsState { it ->
 | 
			
		||||
//            it.takeIf { it > 0 }
 | 
			
		||||
//        }
 | 
			
		||||
        get() = remember { mutableStateOf(null) }
 | 
			
		||||
 | 
			
		||||
    private val sheetFlow = combine(
 | 
			
		||||
        characterRepository.characterSheetFlow(id = argument.id),
 | 
			
		||||
        campaignRepository.campaignFlow(),
 | 
			
		||||
        characterRepository.characterDetailFlow(characterId = argument.id),
 | 
			
		||||
        campaignRepository.campaignFlow,
 | 
			
		||||
        alteration.alterations(characterId = argument.id),
 | 
			
		||||
        transform = { sheet, campaign, alterations ->
 | 
			
		||||
            factory.convertToUio(
 | 
			
		||||
| 
						 | 
				
			
			@ -93,8 +94,8 @@ class CharacterSheetViewModel(
 | 
			
		|||
        alteration.toggle(argument.id, "65e37d32-3031-4bf8-9369-d2c45d2efac0")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun deleteCharacter(id: String) {
 | 
			
		||||
        characterRepository.delete(id = id)
 | 
			
		||||
    suspend fun deleteCharacter(id: String) {
 | 
			
		||||
        characterRepository.deleteCharacter(characterId = id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onUseSkill(skill: CharacterSheetPageUio.Node) {
 | 
			
		||||
| 
						 | 
				
			
			@ -109,10 +110,12 @@ class CharacterSheetViewModel(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fun showConfirmCharacterDeletionDialog() {
 | 
			
		||||
        characterRepository.characterSheetFlow(id = argument.id).value?.let { sheet ->
 | 
			
		||||
        characterRepository.characterPreview(
 | 
			
		||||
            characterId = argument.id
 | 
			
		||||
        )?.let { preview ->
 | 
			
		||||
            _displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio(
 | 
			
		||||
                id = sheet.id,
 | 
			
		||||
                name = sheet.name,
 | 
			
		||||
                id = preview.id,
 | 
			
		||||
                name = preview.name,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -122,8 +125,10 @@ class CharacterSheetViewModel(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun showSubCharacteristicDialog(id: String) {
 | 
			
		||||
        characterRepository.characterSheetFlow(id = argument.id).value?.let { sheet ->
 | 
			
		||||
            val instance = campaignRepository.characterInstance(id = argument.id).value
 | 
			
		||||
        characterRepository.characterDetail(
 | 
			
		||||
            characterId = argument.id,
 | 
			
		||||
        )?.let { sheet ->
 | 
			
		||||
            val instance = campaignRepository.characterInstanceFlow(id = argument.id).value
 | 
			
		||||
            _statChangeDialog.value = when (id) {
 | 
			
		||||
                CharacterSheet.CharacteristicId.HP -> {
 | 
			
		||||
                    val value = mutableStateOf(
 | 
			
		||||
| 
						 | 
				
			
			@ -169,8 +174,9 @@ class CharacterSheetViewModel(
 | 
			
		|||
        value: Int,
 | 
			
		||||
    ) {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            val sheet = characterRepository.characterSheetFlow(id = argument.id).value
 | 
			
		||||
            if (sheet != null) {
 | 
			
		||||
            characterRepository.characterDetail(
 | 
			
		||||
                characterId = argument.id,
 | 
			
		||||
            )?.let { sheet ->
 | 
			
		||||
                network.share(
 | 
			
		||||
                    payload = UpdatePlayerCharacteristicMessage(
 | 
			
		||||
                        characterId = argument.id,
 | 
			
		||||
| 
						 | 
				
			
			@ -195,20 +201,20 @@ class CharacterSheetViewModel(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun showDiminishedDialog() {
 | 
			
		||||
        val diminished = characterRepository.characterDiminishedFlow(id = argument.id).value
 | 
			
		||||
        val textFieldValue =
 | 
			
		||||
            mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0)))
 | 
			
		||||
        _diminishedDialog.value = DiminishedStatDialogUio(
 | 
			
		||||
            id = argument.id,
 | 
			
		||||
            label = getString(resource = Res.string.character_sheet__diminished__label),
 | 
			
		||||
            value = { textFieldValue.value },
 | 
			
		||||
            onValueChange = { value ->
 | 
			
		||||
                textFieldValue.value = when (value.text.toIntOrNull()?.takeIf { it >= 0 }) {
 | 
			
		||||
                    null -> TextFieldValue("0", selection = TextRange(index = 0))
 | 
			
		||||
                    else -> value
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
//        val diminished = characterRepository.characterDiminishedFlow(id = argument.id).value
 | 
			
		||||
//        val textFieldValue =
 | 
			
		||||
//            mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0)))
 | 
			
		||||
//        _diminishedDialog.value = DiminishedStatDialogUio(
 | 
			
		||||
//            id = argument.id,
 | 
			
		||||
//            label = getString(resource = Res.string.character_sheet__diminished__label),
 | 
			
		||||
//            value = { textFieldValue.value },
 | 
			
		||||
//            onValueChange = { value ->
 | 
			
		||||
//                textFieldValue.value = when (value.text.toIntOrNull()?.takeIf { it >= 0 }) {
 | 
			
		||||
//                    null -> TextFieldValue("0", selection = TextRange(index = 0))
 | 
			
		||||
//                    else -> value
 | 
			
		||||
//                }
 | 
			
		||||
//            },
 | 
			
		||||
//        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun hideDiminishedDialog() {
 | 
			
		||||
| 
						 | 
				
			
			@ -216,10 +222,10 @@ class CharacterSheetViewModel(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fun changeDiminished(dialog: DiminishedStatDialogUio) {
 | 
			
		||||
        val value = dialog.value().text.toIntOrNull() ?: 0
 | 
			
		||||
        characterRepository.setDiminishedForCharacter(
 | 
			
		||||
            id = dialog.id,
 | 
			
		||||
            diminished = value,
 | 
			
		||||
        )
 | 
			
		||||
//        val value = dialog.value().text.toIntOrNull() ?: 0
 | 
			
		||||
//        characterRepository.setDiminishedForCharacter(
 | 
			
		||||
//            id = dialog.id,
 | 
			
		||||
//            diminished = value,
 | 
			
		||||
//        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -27,13 +27,11 @@ class CharacterSheetEditViewModel(
 | 
			
		|||
    private val argument = CharacterSheetEditDestination.Argument(savedStateHandle)
 | 
			
		||||
 | 
			
		||||
    private val _characterSheet = mutableStateOf(
 | 
			
		||||
        characterSheetRepository.characterSheetFlow(id = argument.id).value.let {
 | 
			
		||||
            runBlocking {
 | 
			
		||||
                sheetFactory.convertToUio(
 | 
			
		||||
                    sheet = it,
 | 
			
		||||
                    onDeleteSkill = ::deleteSkill,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        runBlocking {
 | 
			
		||||
            sheetFactory.convertToUio(
 | 
			
		||||
                sheet = characterSheetRepository.characterDetail(characterId = argument.id),
 | 
			
		||||
                onDeleteSkill = ::deleteSkill,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    val characterSheet: State<CharacterSheetEditPageUio> get() = _characterSheet
 | 
			
		||||
| 
						 | 
				
			
			@ -110,10 +108,10 @@ class CharacterSheetEditViewModel(
 | 
			
		|||
 | 
			
		||||
    suspend fun save() {
 | 
			
		||||
        val updatedSheet = sheetFactory.updateCharacterSheet(
 | 
			
		||||
            currentSheet = characterSheetRepository.characterSheetFlow(id = _characterSheet.value.id).value,
 | 
			
		||||
            currentSheet = characterSheetRepository.characterDetail(characterId = _characterSheet.value.id),
 | 
			
		||||
            editedSheet = _characterSheet.value,
 | 
			
		||||
        )
 | 
			
		||||
        characterSheetRepository.save(
 | 
			
		||||
        characterSheetRepository.updateCharacter(
 | 
			
		||||
            characterSheet = updatedSheet,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,21 +11,22 @@ import com.pixelized.shared.lwa.OperatingSystem
 | 
			
		|||
import com.pixelized.shared.lwa.storePath
 | 
			
		||||
 | 
			
		||||
class MainPageViewModel(
 | 
			
		||||
    repository: CharacterSheetRepository,
 | 
			
		||||
    private val repository: CharacterSheetRepository,
 | 
			
		||||
    networkRepository: NetworkRepository,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
 | 
			
		||||
    private val charactersFlow = repository.characterSheetFlow()
 | 
			
		||||
    val characters: State<List<CharacterUio>>
 | 
			
		||||
        @Composable
 | 
			
		||||
        get() = charactersFlow.collectAsState { sheets ->
 | 
			
		||||
            sheets.map { sheet ->
 | 
			
		||||
                CharacterUio(
 | 
			
		||||
                    id = sheet.id,
 | 
			
		||||
                    name = sheet.name,
 | 
			
		||||
                )
 | 
			
		||||
        get() = repository
 | 
			
		||||
            .characterSheetPreviewFlow
 | 
			
		||||
            .collectAsState { sheets ->
 | 
			
		||||
                sheets.map { sheet ->
 | 
			
		||||
                    CharacterUio(
 | 
			
		||||
                        id = sheet.id,
 | 
			
		||||
                        name = sheet.name,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private val networkStatus = networkRepository.status
 | 
			
		||||
    val enableRollHistory: State<Boolean>
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +34,7 @@ class MainPageViewModel(
 | 
			
		|||
        get() = networkStatus.collectAsState { it == NetworkRepository.Status.CONNECTED }
 | 
			
		||||
 | 
			
		||||
    fun openSaveDirectory(
 | 
			
		||||
        os: OperatingSystem = OperatingSystem.current
 | 
			
		||||
        os: OperatingSystem = OperatingSystem.current,
 | 
			
		||||
    ) {
 | 
			
		||||
        when (os) {
 | 
			
		||||
            OperatingSystem.Windows -> shellRun("explorer.exe", listOf(storePath(os = os)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,10 +9,10 @@ import androidx.compose.runtime.mutableStateOf
 | 
			
		|||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
 | 
			
		||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
 | 
			
		||||
import kotlinx.coroutines.flow.MutableSharedFlow
 | 
			
		||||
import kotlinx.coroutines.flow.SharedFlow
 | 
			
		||||
| 
						 | 
				
			
			@ -23,8 +23,9 @@ class NetworkViewModel(
 | 
			
		|||
    private val networkRepository: NetworkRepository,
 | 
			
		||||
    private val factory: NetworkFactory,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
    private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
 | 
			
		||||
    private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
 | 
			
		||||
    private val settings = settingsRepository.settings()
 | 
			
		||||
    private val host = mutableStateOf(settings.host)
 | 
			
		||||
    private val port = mutableStateOf(settings.port)
 | 
			
		||||
 | 
			
		||||
    private val _networkError = MutableSharedFlow<ErrorSnackUio>()
 | 
			
		||||
    val networkError: SharedFlow<ErrorSnackUio> get() = _networkError
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +65,7 @@ class NetworkViewModel(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fun onPortChange(port: String) {
 | 
			
		||||
        this.port.value = port.toIntOrNull() ?: 0
 | 
			
		||||
        this.port.value = port.toIntOrNull() ?: settings.port
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onHostChange(host: String) {
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +76,15 @@ class NetworkViewModel(
 | 
			
		|||
        controller.show()
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
 | 
			
		||||
        if (settings.host != host.value || settings.port != port.value) {
 | 
			
		||||
            settingsRepository.update(
 | 
			
		||||
                settings = settings.copy(
 | 
			
		||||
                    host = host.value,
 | 
			
		||||
                    port = port.value
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        networkRepository.connect(
 | 
			
		||||
            host = host.value,
 | 
			
		||||
            port = port.value,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ class RollViewModel(
 | 
			
		|||
        sheet: CharacterSheetPageUio,
 | 
			
		||||
        characteristic: CharacterSheetPageUio.Characteristic,
 | 
			
		||||
    ) {
 | 
			
		||||
        val diminished = characterSheetRepository.characterDiminishedFlow(id = sheet.id).value
 | 
			
		||||
        val diminished = 0 // TODO characterSheetRepository.characterDiminishedFlow(id = sheet.id).value
 | 
			
		||||
        prepareRoll(
 | 
			
		||||
            sheet = sheet,
 | 
			
		||||
            label = characteristic.label,
 | 
			
		||||
| 
						 | 
				
			
			@ -98,12 +98,12 @@ class RollViewModel(
 | 
			
		|||
        rollAction: String,
 | 
			
		||||
        rollSuccessValue: Int?,
 | 
			
		||||
    ) {
 | 
			
		||||
        runBlocking {
 | 
			
		||||
        this.sheet = runBlocking {
 | 
			
		||||
            rollRotation.snapTo(0f)
 | 
			
		||||
            rollScale.snapTo(1f)
 | 
			
		||||
            characterSheetRepository.characterDetail(characterId = sheet.id)!!
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.sheet = characterSheetRepository.characterSheetFlow(id = sheet.id).value!!
 | 
			
		||||
        this.rollAction = rollAction
 | 
			
		||||
        this.rollSuccessValue = rollSuccessValue
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import androidx.compose.runtime.State
 | 
			
		|||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
 | 
			
		||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
 | 
			
		||||
| 
						 | 
				
			
			@ -21,15 +22,15 @@ class RollHistoryViewModel(
 | 
			
		|||
    init {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            combine(
 | 
			
		||||
                characterRepository.characterSheetFlow(),
 | 
			
		||||
                characterRepository.characterSheetPreviewFlow,
 | 
			
		||||
                rollRepository.rolls,
 | 
			
		||||
            ) { sheets: List<CharacterSheet>, content ->
 | 
			
		||||
            ) { sheets: List<CharacterSheetPreview>, content ->
 | 
			
		||||
                _rolls.value.toMutableList().apply {
 | 
			
		||||
                    val name = sheets.firstOrNull { it.id == content.characterId }?.name ?: ""
 | 
			
		||||
                    add(
 | 
			
		||||
                        index = 0,
 | 
			
		||||
                        element = RollHistoryItemUio(
 | 
			
		||||
                            character = sheets.firstOrNull { it.id == content.characterId }?.name
 | 
			
		||||
                                ?: "",
 | 
			
		||||
                            character = name,
 | 
			
		||||
                            skillLabel = content.skillLabel,
 | 
			
		||||
                            rollDifficulty = content.rollDifficulty,
 | 
			
		||||
                            resultLabel = content.resultLabel,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.utils.extention
 | 
			
		||||
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.Message
 | 
			
		||||
import com.pixelized.shared.lwa.protocol.websocket.Message
 | 
			
		||||
import io.ktor.websocket.Frame
 | 
			
		||||
import io.ktor.websocket.readText
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue