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
|
|
@ -1,7 +1,7 @@
|
|||
plugins {
|
||||
alias(libs.plugins.composeMultiplatform) apply false
|
||||
alias(libs.plugins.kotlinSerialization) apply false
|
||||
alias(libs.plugins.composeCompiler) apply false
|
||||
alias(libs.plugins.kotlinJvm) apply false
|
||||
alias(libs.plugins.kotlinSerialization) apply false
|
||||
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
|
|
|||
|
|
@ -14,4 +14,7 @@
|
|||
|
||||
# Serialization
|
||||
-keep class io.ktor.serialization.kotlinx.json.** { *; }
|
||||
-keep class com.pixelized.shared.lwa.model.** { *; }
|
||||
-keep class com.pixelized.shared.lwa.model.** { *; }
|
||||
-keep class com.pixelized.shared.lwa.protocol.rest.** { *; }
|
||||
-keep class com.pixelized.desktop.lwa.repository.settings.model.** { *; }
|
||||
-keep @kotlinx.serialization.Serializable class * { *; }
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
[versions]
|
||||
kotlin = "2.0.21"
|
||||
kotlinx-coroutines = "1.9.0"
|
||||
kotlinx-json = "1.7.3"
|
||||
compose-multiplatform = "1.7.0"
|
||||
kotlinx-json = "1.8.0"
|
||||
compose-multiplatform = "1.7.3"
|
||||
androidx-lifecycle = "2.8.3"
|
||||
androidx-navigation = "2.8.0-alpha10"
|
||||
ktor = "3.0.1"
|
||||
|
|
@ -10,7 +10,6 @@ koin = "4.0.0"
|
|||
turtle = "0.5.0"
|
||||
logback = "1.5.11"
|
||||
coil = "3.1.0"
|
||||
filament-android = "1.17.1"
|
||||
|
||||
[plugins]
|
||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||
|
|
@ -38,7 +37,6 @@ koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", ver
|
|||
|
||||
ktor-serialization-json = { group = 'io.ktor', name = 'ktor-serialization-kotlinx-json', version.ref = "ktor" }
|
||||
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-cio = { group = 'io.ktor', name = "ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
|
||||
ktor-client-websockets = { group = 'io.ktor', name = "ktor-client-websockets", version.ref = "ktor" }
|
||||
ktor-client-negotiation = { group = 'io.ktor', name = 'ktor-client-content-negotiation', version.ref = "ktor" }
|
||||
|
|
@ -51,5 +49,4 @@ turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtl
|
|||
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||
|
||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||
coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||
filament-android = { group = "com.google.ar.sceneform", name = "filament-android", version.ref = "filament-android" }
|
||||
coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||
|
|
@ -22,5 +22,4 @@ dependencies {
|
|||
implementation(libs.ktor.server.websockets)
|
||||
implementation(libs.ktor.server.negotiation)
|
||||
implementation(libs.ktor.serialization.json)
|
||||
implementation(libs.filament.android)
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ class CampaignService(
|
|||
return true
|
||||
}
|
||||
|
||||
// Data manipulation threw WebSocket.
|
||||
// Data manipulation through WebSocket.
|
||||
|
||||
suspend fun updateCharacteristic(
|
||||
characterId: String,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class CharacterSheetService(
|
|||
return store.delete(id = characterId)
|
||||
}
|
||||
|
||||
// Data manipulation threw WebSocket.
|
||||
// Data manipulation through WebSocket.
|
||||
|
||||
fun updateCharacterLevel(
|
||||
characterId: String,
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ class Engine(
|
|||
|
||||
suspend fun handle(message: Message) {
|
||||
when (val data = message.value) {
|
||||
RestSynchronisation.Campaign -> Unit // TODO
|
||||
|
||||
is RestSynchronisation.CharacterUpdate -> Unit // TODO
|
||||
|
||||
is RollMessage -> Unit // Nothing to do here.
|
||||
|
||||
|
|
@ -34,9 +31,11 @@ class Engine(
|
|||
skillId = data.skillId
|
||||
)
|
||||
|
||||
is RestSynchronisation.CharacterDelete -> characterService.deleteCharacter(
|
||||
characterId = data.characterId,
|
||||
)
|
||||
RestSynchronisation.Campaign -> Unit // Handle in the Rest
|
||||
|
||||
is RestSynchronisation.CharacterUpdate -> Unit // Handle in the Rest
|
||||
|
||||
is RestSynchronisation.CharacterDelete -> Unit // Handle in the Rest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,33 @@ class LocalServer {
|
|||
}
|
||||
|
||||
routing {
|
||||
webSocket(
|
||||
path = "/ws",
|
||||
handler = {
|
||||
val job = launch {
|
||||
// send local message to the clients
|
||||
engine.webSocket.collect { message ->
|
||||
send(json.encodeToFrame(message))
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
// watching for clients incoming message
|
||||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
val message = Json.decodeFromFrame(frame = frame)
|
||||
// log the message
|
||||
engine.handle(message)
|
||||
// broadcast to clients the message
|
||||
engine.webSocket.emit(message)
|
||||
}
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
println("WebSocket exception: ${exception.localizedMessage}")
|
||||
}.also {
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
)
|
||||
get(
|
||||
path = "/characters",
|
||||
body = engine.getCharacters(),
|
||||
|
|
@ -116,33 +143,6 @@ class LocalServer {
|
|||
)
|
||||
}
|
||||
}
|
||||
webSocket(
|
||||
path = "/ws",
|
||||
handler = {
|
||||
val job = launch {
|
||||
// send local message to the clients
|
||||
engine.webSocket.collect { message ->
|
||||
send(json.encodeToFrame(message))
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
// watching for clients incoming message
|
||||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
val message = Json.decodeFromFrame(frame = frame)
|
||||
// log the message
|
||||
engine.handle(message)
|
||||
// broadcast to clients the message
|
||||
engine.webSocket.emit(message)
|
||||
}
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
println("WebSocket exception: ${exception.localizedMessage}")
|
||||
}.also {
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -154,7 +154,6 @@ class LocalServer {
|
|||
try {
|
||||
server?.start(wait = true)
|
||||
} catch (exception: Exception) {
|
||||
// TODO
|
||||
println("WebSocket exception: ${exception.localizedMessage}")
|
||||
} finally {
|
||||
println("Server close")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue