Add client purse management.
This commit is contained in:
parent
4f33492b23
commit
8982bab22d
48 changed files with 1664 additions and 258 deletions
|
|
@ -96,3 +96,7 @@ compose.desktop {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.resources {
|
||||
generateResClass = auto
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M320,720L640,720L640,640L320,640L320,720ZM320,560L640,560L640,480L320,480L320,560ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360ZM240,160L240,160L240,360L240,360L240,160L240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z" />
|
||||
</vector>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M336,840Q245,840 182.5,777.5Q120,715 120,624Q120,586 133,550Q146,514 170,485L312,314L215,120L745,120L648,314L790,485Q814,514 827,550Q840,586 840,624Q840,715 777,777.5Q714,840 624,840L336,840ZM480,640Q447,640 423.5,616.5Q400,593 400,560Q400,527 423.5,503.5Q447,480 480,480Q513,480 536.5,503.5Q560,527 560,560Q560,593 536.5,616.5Q513,640 480,640ZM385,280L575,280L615,200L345,200L385,280ZM336,760L624,760Q681,760 720.5,720.5Q760,681 760,624Q760,600 751.5,577.5Q743,555 728,537L581,360L380,360L232,536Q217,554 208.5,577Q200,600 200,624Q200,681 239.5,720.5Q279,760 336,760Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M200,520L200,440L760,440L760,520L200,520Z" />
|
||||
</vector>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -3,6 +3,28 @@
|
|||
|
||||
<string name="generic__arrow">▸</string>
|
||||
|
||||
<string name="generic__gold__singular">Écu</string>
|
||||
<string name="generic__silver__singular">Écusson</string>
|
||||
<string name="generic__copper__singular">Liard</string>
|
||||
|
||||
<plurals name="generic__gold">
|
||||
<item quantity="zero">Écu</item>
|
||||
<item quantity="one">Écu</item>
|
||||
<item quantity="other">Écus</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="generic__silver">
|
||||
<item quantity="zero">Écusson</item>
|
||||
<item quantity="one">Écusson</item>
|
||||
<item quantity="other">Écussons</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="generic__copper">
|
||||
<item quantity="zero">Liard</item>
|
||||
<item quantity="one">Liard</item>
|
||||
<item quantity="other">Liards</item>
|
||||
</plurals>
|
||||
|
||||
<string name="error__missing_character_sheet__label">La feuille de personnage est introuvable</string>
|
||||
<string name="error__default__action">Ok</string>
|
||||
|
||||
|
|
@ -162,6 +184,9 @@
|
|||
<string name="character_sheet__delete_dialog__title">Supprimer la feuille de personnage</string>
|
||||
<string name="character_sheet__delete_dialog__description">Êtes-vous sûr de vouloir supprimer "%1$s" ?</string>
|
||||
|
||||
<string name="character__inventory__add_to_purse__title">Ajouter à la bourse</string>
|
||||
<string name="character__inventory__remove_from_purse__title">Retirer de la bourse</string>
|
||||
|
||||
<string name="tooltip__characteristics__characteristics">Les caractéristiques constituent les aptitudes innées d’un personnage comme son intelligence, sa force, son charisme, etc. Elles ne sont pas acquises, mais peuvent être parfois augmentées par un entraînement ou une utilisation réussie. Les caractéristiques des humains normaux varient de 2 (niveau extrêmement bas) à 20 (maximum du potentiel humain), avec une moyenne de 10 ou 11. Plus une caractéristique est élevée plus le personnage est puissant dans cette aptitude.\nÀ la création de votre personnage, répartissez les valeurs suivantes dans les différentes caractéristiques : 15, 15, 13, 11, 10, 9 et 7.</string>
|
||||
<string name="tooltip__characteristics__strength">La Force représente essentiellement la puissance musculaire du personnage. Elle ne décrit pas nécessairement la masse musculaire brute, mais l’efficacité avec laquelle le personnage exerce ses muscles pour accomplir des actions physiques pénibles.\n\n- Bonus aux dégats\n- Réflexe\n- Athlétisme\n- Lancer\n- Saisie</string>
|
||||
<string name="tooltip__characteristics__constitution">La Constitution est une mesure de la ténacité et de la résilience du personnage. Elle sert à résister aux maladies. Mais son aspect le plus important réside dans la détermination du nombre de dommages qu’un personnage peut supporter avant de succomber.\n\n- Point de vie maximum\n- Athlétisme\n- Acrobatie</string>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
|||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
|
|
@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
|
||||
class DataSyncViewModel(
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val inventoryRepository: InventoryRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val itemRepository: ItemRepository,
|
||||
private val campaignRepository: CampaignRepository,
|
||||
|
|
@ -65,6 +67,9 @@ class DataSyncViewModel(
|
|||
characterRepository.updateCharacterSheet(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
inventoryRepository.updateInventoryFlow(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
|||
import com.pixelized.desktop.lwa.repository.campaign.CampaignStore
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetStore
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryStore
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemStore
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
|
|
@ -23,13 +25,17 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
|
|||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogFactory
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogFactory
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||
|
|
@ -112,6 +118,7 @@ val storeDependencies
|
|||
singleOf(::CampaignStore)
|
||||
singleOf(::TagStore)
|
||||
singleOf(::ItemStore)
|
||||
singleOf(::InventoryStore)
|
||||
}
|
||||
|
||||
val repositoryDependencies
|
||||
|
|
@ -124,6 +131,7 @@ val repositoryDependencies
|
|||
singleOf(::CampaignRepository)
|
||||
singleOf(::TagRepository)
|
||||
singleOf(::ItemRepository)
|
||||
singleOf(::InventoryRepository)
|
||||
}
|
||||
|
||||
val factoryDependencies
|
||||
|
|
@ -135,10 +143,13 @@ val factoryDependencies
|
|||
factoryOf(::SettingsFactory)
|
||||
factoryOf(::CampaignJsonFactory)
|
||||
factoryOf(::CharacterRibbonFactory)
|
||||
factoryOf(::CharacterDetailFactory)
|
||||
factoryOf(::CharacterDetailHeaderFactory)
|
||||
factoryOf(::CharacterDetailSheetFactory)
|
||||
factoryOf(::CharacterDetailInventoryFactory)
|
||||
factoryOf(::CharacterSheetCharacteristicDialogFactory)
|
||||
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
||||
factoryOf(::CharacterSheetAlterationDialogFactory)
|
||||
factoryOf(::PurseDialogFactory)
|
||||
factoryOf(::TextMessageFactory)
|
||||
factoryOf(::LevelUpFactory)
|
||||
factoryOf(::GMTagFactory)
|
||||
|
|
@ -160,10 +171,11 @@ val viewModelDependencies
|
|||
viewModelOf(::NetworkViewModel)
|
||||
viewModelOf(::PlayerRibbonViewModel)
|
||||
viewModelOf(::NpcRibbonViewModel)
|
||||
viewModelOf(::CharacterDetailViewModel)
|
||||
viewModelOf(::CharacterDetailPanelViewModel)
|
||||
viewModelOf(::CharacterSheetDiminishedDialogViewModel)
|
||||
viewModelOf(::CharacterSheetCharacteristicDialogViewModel)
|
||||
viewModelOf(::CharacterSheetAlterationDialogViewModel)
|
||||
viewModelOf(::PurseDialogViewModel)
|
||||
viewModelOf(::CampaignChatViewModel)
|
||||
viewModelOf(::SettingsViewModel)
|
||||
viewModelOf(::LevelUpViewModel)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.pixelized.desktop.lwa.network
|
|||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import com.pixelized.shared.lwa.model.inventory.InventoryJson
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
|
|
@ -102,6 +104,21 @@ interface LwaClient {
|
|||
itemId: String,
|
||||
): APIResponse<Unit>
|
||||
|
||||
// Inventory
|
||||
|
||||
suspend fun getInventory(
|
||||
characterSheetId: String,
|
||||
): APIResponse<InventoryJson>
|
||||
|
||||
suspend fun putInventory(
|
||||
json: InventoryJson,
|
||||
create: Boolean,
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun deleteInventory(
|
||||
characterSheetId: String,
|
||||
): APIResponse<Unit>
|
||||
|
||||
// Tags
|
||||
|
||||
suspend fun getAlterationTags(): APIResponse<List<TagJson>>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
|||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.inventory.InventoryJson
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
|
|
@ -175,6 +176,27 @@ class LwaClientImpl(
|
|||
.delete("$root/item/delete?itemId=$itemId")
|
||||
.body()
|
||||
|
||||
@Throws
|
||||
override suspend fun getInventory(characterSheetId: String): APIResponse<InventoryJson> = client
|
||||
.get("$root/inventory/detail?characterSheetId=$characterSheetId")
|
||||
.body()
|
||||
|
||||
@Throws
|
||||
override suspend fun putInventory(
|
||||
inventory: InventoryJson,
|
||||
create: Boolean,
|
||||
): APIResponse<Unit> = client
|
||||
.put("$root/inventory/update?create=$create") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(inventory)
|
||||
}
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
@Throws
|
||||
override suspend fun deleteInventory(characterSheetId: String): APIResponse<Unit> = client
|
||||
.delete("$root/inventory/delete?characterSheetId=$characterSheetId")
|
||||
.body()
|
||||
|
||||
@Throws
|
||||
override suspend fun getAlterationTags(): APIResponse<List<TagJson>> = client
|
||||
.get("$root/tag/alteration")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package com.pixelized.desktop.lwa.repository.inventory
|
||||
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class InventoryRepository(
|
||||
private val inventoryStore: InventoryStore,
|
||||
) {
|
||||
val inventoryFlow get() = inventoryStore.inventories
|
||||
|
||||
suspend fun updateInventoryFlow(characterSheetId: String) {
|
||||
inventoryStore.updateInventoryFlow(characterSheetId = characterSheetId)
|
||||
}
|
||||
|
||||
fun inventory(
|
||||
characterSheetId: String?,
|
||||
): Inventory? {
|
||||
return inventoryFlow.value[characterSheetId]
|
||||
}
|
||||
|
||||
fun inventoryFlow(
|
||||
characterSheetId: String?,
|
||||
): Flow<Inventory?> {
|
||||
return inventoryFlow.map { it[characterSheetId] }
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun updateInventory(
|
||||
inventory: Inventory,
|
||||
create: Boolean,
|
||||
) {
|
||||
inventoryStore.putInventory(
|
||||
inventory = inventory,
|
||||
create = create,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteItem(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
inventoryStore.deleteInventory(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.pixelized.desktop.lwa.repository.inventory
|
||||
|
||||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import com.pixelized.shared.lwa.model.inventory.factory.InventoryJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
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.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class InventoryStore(
|
||||
private val network: NetworkRepository,
|
||||
private val factory: InventoryJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val _inventories = MutableStateFlow<Map<String, Inventory>>(emptyMap())
|
||||
val inventories: StateFlow<Map<String, Inventory>> = _inventories
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
network.data.collect(::handleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateInventoryFlow(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
val inventory = try {
|
||||
getInventory(characterSheetId = characterSheetId)
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
null
|
||||
}
|
||||
// update the flow with the new item.
|
||||
_inventories.update { items ->
|
||||
items.toMutableMap().also {
|
||||
it[characterSheetId] = inventory
|
||||
?: Inventory.empty(characterSheetId = characterSheetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getInventory(
|
||||
characterSheetId: String,
|
||||
): Inventory? {
|
||||
val request = client.getInventory(characterSheetId = characterSheetId)
|
||||
return when (request.success) {
|
||||
true -> request.data?.let { factory.convertFromJson(json = it) }
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun putInventory(
|
||||
inventory: Inventory,
|
||||
create: Boolean,
|
||||
) {
|
||||
val request = client.putInventory(
|
||||
json = factory.convertToJson(inventory = inventory),
|
||||
create = create,
|
||||
)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteInventory(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
val request = client.deleteInventory(characterSheetId = characterSheetId)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
// region: WebSocket & data update.
|
||||
|
||||
private suspend fun handleMessage(message: SocketMessage) {
|
||||
when (message) {
|
||||
is ApiSynchronisation.InventoryApiSynchronisation -> when (message) {
|
||||
is ApiSynchronisation.InventoryUpdate -> updateInventoryFlow(
|
||||
characterSheetId = message.characterSheetId,
|
||||
)
|
||||
|
||||
is ApiSynchronisation.InventoryDelete -> _inventories.update { items ->
|
||||
items.toMutableMap().also {
|
||||
it.remove(message.characterSheetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -96,7 +96,10 @@ private fun CharacterSheetCharacteristicContent(
|
|||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(event = onDismissRequest)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = { onConfirm(dialog) },
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@ private fun CharacterSheetDiminishedContent(
|
|||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(event = onDismissRequest)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = { onConfirm(dialog) },
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.purse
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
|
||||
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_remove_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@Stable
|
||||
data class PurseDialogUio(
|
||||
val characterSheetId: String,
|
||||
val label: StateFlow<String>,
|
||||
val add: StateFlow<Boolean>,
|
||||
val gold: LwaTextFieldUio,
|
||||
val silver: LwaTextFieldUio,
|
||||
val copper: LwaTextFieldUio,
|
||||
val enableConfirm: StateFlow<Boolean>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PurseDialog(
|
||||
dialog: State<PurseDialogUio?>,
|
||||
onConfirm: (PurseDialogUio) -> Unit,
|
||||
onSwapSign: (PurseDialogUio) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = {
|
||||
PurseContent(
|
||||
dialog = it,
|
||||
onConfirm = onConfirm,
|
||||
onSwapSign = onSwapSign,
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
PurseDialogKeyHandler(
|
||||
onSwap = { onSwapSign(it) },
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PurseContent(
|
||||
dialog: PurseDialogUio,
|
||||
onConfirm: (PurseDialogUio) -> Unit,
|
||||
onSwapSign: (PurseDialogUio) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = { onConfirm(dialog) },
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
DecoratedBox {
|
||||
Surface {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AnimatedContent(
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
|
||||
targetState = dialog.label.collectAsState().value,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
SignButton(
|
||||
modifier = Modifier
|
||||
.size(size = 56.dp)
|
||||
.background(
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
),
|
||||
add = dialog.add,
|
||||
onClick = { onSwapSign(dialog) },
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.focusRequester(focusRequester = focusRequester)
|
||||
.width(100.dp),
|
||||
field = dialog.gold,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(100.dp),
|
||||
field = dialog.silver,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(100.dp),
|
||||
field = dialog.copper,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 4.dp)
|
||||
.align(alignment = Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 4.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Text(
|
||||
color = MaterialTheme.colors.primary.copy(alpha = .7f),
|
||||
text = stringResource(Res.string.dialog__cancel_action)
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
enabled = dialog.enableConfirm.collectAsState().value,
|
||||
onClick = { onConfirm(dialog) },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.dialog__confirm_action)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignButton(
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
enabled: Boolean = true,
|
||||
add: StateFlow<Boolean>,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource,
|
||||
) {
|
||||
val rotation = animateFloatAsState(
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
targetValue = when (add.collectAsState().value) {
|
||||
true -> 90f
|
||||
else -> 0f
|
||||
}
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.graphicsLayer {
|
||||
this.rotationZ = rotation.value * 2f
|
||||
},
|
||||
painter = painterResource(Res.drawable.ic_remove_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.graphicsLayer {
|
||||
this.rotationZ = rotation.value * 3f
|
||||
},
|
||||
painter = painterResource(Res.drawable.ic_remove_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PurseDialogKeyHandler(
|
||||
onSwap: () -> Unit,
|
||||
) {
|
||||
KeyHandler {
|
||||
if (it.type == KeyEventType.KeyDown) {
|
||||
when (it.key) {
|
||||
Key.AltLeft, Key.AltRight -> {
|
||||
onSwap()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.purse
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
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
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__add_to_purse__title
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__remove_from_purse__title
|
||||
import lwacharactersheet.composeapp.generated.resources.generic__copper__singular
|
||||
import lwacharactersheet.composeapp.generated.resources.generic__gold__singular
|
||||
import lwacharactersheet.composeapp.generated.resources.generic__silver__singular
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class PurseDialogFactory {
|
||||
private val decimalChecker = Regex("""^\s*\d*\s*${'$'}""")
|
||||
|
||||
suspend fun convertToDialogUio(
|
||||
scope: CoroutineScope,
|
||||
characterSheetId: String?,
|
||||
signFlow: StateFlow<Boolean>,
|
||||
): PurseDialogUio? {
|
||||
if (characterSheetId == null) return null
|
||||
|
||||
val gold = createFlows(initialValue = "")
|
||||
.createCoinTextField(label = getString(Res.string.generic__gold__singular))
|
||||
val silver = createFlows(initialValue = "")
|
||||
.createCoinTextField(label = getString(Res.string.generic__silver__singular))
|
||||
val copper = createFlows(initialValue = "")
|
||||
.createCoinTextField(label = getString(Res.string.generic__copper__singular))
|
||||
|
||||
return PurseDialogUio(
|
||||
characterSheetId = characterSheetId,
|
||||
label = signFlow.map {
|
||||
when (it) {
|
||||
true -> getString(Res.string.character__inventory__add_to_purse__title)
|
||||
false -> getString(Res.string.character__inventory__remove_from_purse__title)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = getString(Res.string.character__inventory__add_to_purse__title),
|
||||
),
|
||||
add = signFlow,
|
||||
gold = gold,
|
||||
silver = silver,
|
||||
copper = copper,
|
||||
enableConfirm = combine(
|
||||
gold.isError,
|
||||
silver.isError,
|
||||
copper.isError
|
||||
) { goldError, silverError, copperError ->
|
||||
!goldError && !silverError && !copperError
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = !gold.isError.value && !silver.isError.value && !copper.isError.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun check(value: String): Boolean = !decimalChecker.matches(value)
|
||||
|
||||
private fun Pair<MutableStateFlow<String>, MutableStateFlow<Boolean>>.createCoinTextField(
|
||||
label: String,
|
||||
): LwaTextFieldUio {
|
||||
val (valueFlow, errorFlow) = this
|
||||
return LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = errorFlow,
|
||||
valueFlow = valueFlow,
|
||||
label = label,
|
||||
placeHolder = null,
|
||||
onValueChange = {
|
||||
errorFlow.value = check(it)
|
||||
valueFlow.value = it
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFlows(
|
||||
initialValue: String,
|
||||
initialError: Boolean = false,
|
||||
): Pair<MutableStateFlow<String>, MutableStateFlow<Boolean>> {
|
||||
return MutableStateFlow(initialValue) to MutableStateFlow(initialError)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.purse
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class PurseDialogViewModel(
|
||||
private val inventoryRepository: InventoryRepository,
|
||||
private val factory: PurseDialogFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val characterSheetIdFlow = MutableStateFlow<String?>(null)
|
||||
private val signFlow = MutableStateFlow(true)
|
||||
|
||||
private val _error = MutableSharedFlow<ErrorSnackUio>()
|
||||
val error: MutableSharedFlow<ErrorSnackUio> = _error
|
||||
|
||||
val purseDialog = characterSheetIdFlow.map { characterSheetId ->
|
||||
factory.convertToDialogUio(
|
||||
scope = viewModelScope,
|
||||
characterSheetId = characterSheetId,
|
||||
signFlow = signFlow,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
fun showPurseDialog(
|
||||
characterSheetId: String?,
|
||||
) {
|
||||
signFlow.value = true
|
||||
characterSheetIdFlow.value = characterSheetId
|
||||
}
|
||||
|
||||
fun hidePurseDialog() {
|
||||
characterSheetIdFlow.value = null
|
||||
}
|
||||
|
||||
fun swapPurseSign() {
|
||||
signFlow.update { it.not() }
|
||||
}
|
||||
|
||||
suspend fun confirmPurse(dialog: PurseDialogUio): Boolean {
|
||||
// guard case: check if the dialog confirm state is enable
|
||||
if (dialog.enableConfirm.value.not()) {
|
||||
return false
|
||||
}
|
||||
// Get the player inventory
|
||||
val inventory = inventoryRepository
|
||||
.inventory(characterSheetId = dialog.characterSheetId)
|
||||
?: return false
|
||||
// compute the new purse
|
||||
val sign = if (dialog.add.value) 1 else -1
|
||||
val goldValue = dialog.gold.valueFlow.value.toIntOrNull() ?: 0
|
||||
val silverValue = dialog.silver.valueFlow.value.toIntOrNull() ?: 0
|
||||
val copperValue = dialog.copper.valueFlow.value.toIntOrNull() ?: 0
|
||||
val purse = Inventory.Purse(
|
||||
gold = inventory.purse.gold + goldValue * sign,
|
||||
silver = inventory.purse.silver + silverValue * sign,
|
||||
copper = inventory.purse.copper + copperValue * sign,
|
||||
)
|
||||
// guard case: check if the purse change, not an error case, but avoid useless API call.
|
||||
if (inventory.purse == purse) {
|
||||
return true
|
||||
}
|
||||
// API call.
|
||||
return try {
|
||||
inventoryRepository.updateInventory(
|
||||
inventory = inventory.copy(
|
||||
purse = purse
|
||||
),
|
||||
create = false,
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(value = message)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.isAltPressed
|
||||
import androidx.compose.ui.input.key.isCtrlPressed
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
|
|
@ -43,10 +45,12 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
|||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToLevelScreen
|
||||
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlay
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChat
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
|
||||
|
|
@ -60,8 +64,9 @@ val LocalCampaignLayoutScope = compositionLocalOf<CampaignLayoutScope> {
|
|||
|
||||
@Composable
|
||||
fun CampaignScreen(
|
||||
playerDetailViewModel: CharacterDetailViewModel = koinViewModel(key = "player"),
|
||||
npcDetailViewModel: CharacterDetailViewModel = koinViewModel(key = "npc"),
|
||||
playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(),
|
||||
playerDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "player"),
|
||||
npcDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "npc"),
|
||||
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel = koinViewModel(),
|
||||
dismissedViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(),
|
||||
alterationViewModel: CharacterSheetAlterationDialogViewModel = koinViewModel(),
|
||||
|
|
@ -111,9 +116,45 @@ fun CampaignScreen(
|
|||
leftPanel = {
|
||||
PlayerRibbon(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
onCharacter = {
|
||||
viewModel = playerRibbonViewModel,
|
||||
onCharacterLeftClick = {
|
||||
scope.launch {
|
||||
playerDetailViewModel.showCharacter(characterSheetId = it)
|
||||
playerDetailViewModel.showCharacter(
|
||||
characterSheetId = it,
|
||||
panel = DetailPanelUio.Sheet,
|
||||
)
|
||||
}
|
||||
},
|
||||
onCharacterRightClick = {
|
||||
scope.launch {
|
||||
playerDetailViewModel.showCharacter(
|
||||
characterSheetId = it,
|
||||
panel = DetailPanelUio.Inventory,
|
||||
)
|
||||
}
|
||||
},
|
||||
onLevelUp = {
|
||||
screen.navigateToLevelScreen(characterSheetId = it)
|
||||
}
|
||||
)
|
||||
},
|
||||
rightPanel = {
|
||||
NpcRibbon(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
onCharacterLeftClick = {
|
||||
scope.launch {
|
||||
npcDetailViewModel.showCharacter(
|
||||
characterSheetId = it,
|
||||
panel = DetailPanelUio.Sheet,
|
||||
)
|
||||
}
|
||||
},
|
||||
onCharacterRightClick = {
|
||||
scope.launch {
|
||||
npcDetailViewModel.showCharacter(
|
||||
characterSheetId = it,
|
||||
panel = DetailPanelUio.Inventory,
|
||||
)
|
||||
}
|
||||
},
|
||||
onLevelUp = {
|
||||
|
|
@ -129,7 +170,7 @@ fun CampaignScreen(
|
|||
.fillMaxHeight(),
|
||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Ltr),
|
||||
blurController = blurController,
|
||||
detailViewModel = npcDetailViewModel,
|
||||
detailPanelViewModel = npcDetailViewModel,
|
||||
characterDiminishedViewModel = dismissedViewModel,
|
||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||
alterationViewModel = alterationViewModel,
|
||||
|
|
@ -138,19 +179,6 @@ fun CampaignScreen(
|
|||
}
|
||||
)
|
||||
},
|
||||
rightPanel = {
|
||||
NpcRibbon(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
onCharacter = {
|
||||
scope.launch {
|
||||
npcDetailViewModel.showCharacter(characterSheetId = it)
|
||||
}
|
||||
},
|
||||
onLevelUp = {
|
||||
screen.navigateToLevelScreen(characterSheetId = it)
|
||||
}
|
||||
)
|
||||
},
|
||||
rightOverlay = {
|
||||
CharacterDetailPanel(
|
||||
modifier = Modifier
|
||||
|
|
@ -159,7 +187,7 @@ fun CampaignScreen(
|
|||
.fillMaxHeight(),
|
||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
|
||||
blurController = blurController,
|
||||
detailViewModel = playerDetailViewModel,
|
||||
detailPanelViewModel = playerDetailViewModel,
|
||||
characterDiminishedViewModel = dismissedViewModel,
|
||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||
alterationViewModel = alterationViewModel,
|
||||
|
|
@ -233,6 +261,26 @@ fun CampaignScreen(
|
|||
playerDetailViewModel.hideCharacter()
|
||||
npcDetailViewModel.hideCharacter()
|
||||
}
|
||||
},
|
||||
onPlayerNumber = {
|
||||
scope.launch {
|
||||
val characterSheetId = playerRibbonViewModel.characters(index = it) ?: return@launch
|
||||
npcDetailViewModel.hideCharacter()
|
||||
playerDetailViewModel.showCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = DetailPanelUio.Sheet,
|
||||
)
|
||||
}
|
||||
},
|
||||
onAltPLayerNumber = {
|
||||
scope.launch {
|
||||
val characterSheetId = playerRibbonViewModel.characters(index = it) ?: return@launch
|
||||
npcDetailViewModel.hideCharacter()
|
||||
playerDetailViewModel.showCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = DetailPanelUio.Inventory,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -339,16 +387,30 @@ private fun CampaignLayout(
|
|||
@Composable
|
||||
private fun CampaignKeyHandler(
|
||||
onDismissRequest: () -> Unit,
|
||||
onPlayerNumber: (index: Int) -> Unit,
|
||||
onAltPLayerNumber: (index: Int) -> Unit,
|
||||
) {
|
||||
KeyHandler {
|
||||
when {
|
||||
it.type == KeyEventType.KeyDown && it.key == Key.Escape -> {
|
||||
onDismissRequest()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
if (it.type != KeyEventType.KeyDown) return@KeyHandler false
|
||||
if (it.key == Key.Escape) {
|
||||
onDismissRequest()
|
||||
return@KeyHandler true
|
||||
}
|
||||
if (it.isCtrlPressed.not()) return@KeyHandler false
|
||||
when (it.key) {
|
||||
Key.Escape -> onDismissRequest()
|
||||
Key.One, Key.NumPad1 -> if (it.isAltPressed) onAltPLayerNumber(0) else onPlayerNumber(0)
|
||||
Key.Two, Key.NumPad2 -> if (it.isAltPressed) onAltPLayerNumber(1) else onPlayerNumber(1)
|
||||
Key.Three, Key.NumPad3 -> if (it.isAltPressed) onAltPLayerNumber(2) else onPlayerNumber(2)
|
||||
Key.Four, Key.NumPad4 -> if (it.isAltPressed) onAltPLayerNumber(3) else onPlayerNumber(3)
|
||||
Key.Five, Key.NumPad5 -> if (it.isAltPressed) onAltPLayerNumber(4) else onPlayerNumber(4)
|
||||
Key.Six, Key.NumPad6 -> if (it.isAltPressed) onAltPLayerNumber(5) else onPlayerNumber(5)
|
||||
Key.Seven, Key.NumPad7 -> if (it.isAltPressed) onAltPLayerNumber(6) else onPlayerNumber(6)
|
||||
Key.Eight, Key.NumPad8 -> if (it.isAltPressed) onAltPLayerNumber(7) else onPlayerNumber(7)
|
||||
Key.Nine, Key.NumPad9 -> if (it.isAltPressed) onAltPLayerNumber(8) else onPlayerNumber(8)
|
||||
else -> return@KeyHandler false
|
||||
}
|
||||
return@KeyHandler true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.PointerMatcher
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -23,6 +24,7 @@ import androidx.compose.foundation.layout.offset
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.onClick
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
|
|
@ -36,6 +38,7 @@ import androidx.compose.ui.draw.drawWithContent
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.PointerButton
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
|
|
@ -69,13 +72,15 @@ data class CharacterPortraitUio(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun CharacterPortrait(
|
||||
modifier: Modifier = Modifier,
|
||||
size: DpSize = MaterialTheme.lwa.size.portrait.minimized,
|
||||
levelUpOffset: Dp = 9.dp,
|
||||
character: CharacterPortraitUio,
|
||||
onCharacter: (characterSheetId: String) -> Unit,
|
||||
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
||||
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
||||
onLevelUp: (characterSheetId: String) -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.lwa.colorScheme
|
||||
|
|
@ -86,7 +91,16 @@ fun CharacterPortrait(
|
|||
.size(size = size)
|
||||
.clip(shape = MaterialTheme.lwa.shapes.portrait)
|
||||
.background(color = colorScheme.elevated.base1dp)
|
||||
.clickable(character.enableDetail) { onCharacter(character.characterSheetId) },
|
||||
.onClick(
|
||||
matcher = PointerMatcher.mouse(PointerButton.Primary),
|
||||
enabled = character.enableDetail,
|
||||
onClick = { onCharacterLeftClick(character.characterSheetId) }
|
||||
)
|
||||
.onClick(
|
||||
matcher = PointerMatcher.mouse(PointerButton.Secondary),
|
||||
enabled = character.enableDetail,
|
||||
onClick = { onCharacterRightClick(character.characterSheetId) }
|
||||
),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = character.portrait,
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
|
@ -37,31 +39,44 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
|
|||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheet
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
data class CharacterDetailPanelUio(
|
||||
val characterSheetId: String?,
|
||||
val panel: DetailPanelUio,
|
||||
val header: StateFlow<CharacterDetailHeaderUio?>,
|
||||
val sheet: StateFlow<CharacterDetailSheetUio?>,
|
||||
val inventory: StateFlow<CharacterDetailInventoryUio?>,
|
||||
)
|
||||
|
||||
@Stable
|
||||
enum class DetailPanelUio {
|
||||
Sheet,
|
||||
Inventory,
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterDetailPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
blurController: BlurContentController,
|
||||
transitionSpec: AnimatedContentTransitionScope<CharacterDetailPanelUio>.() -> ContentTransform = rememberTransitionAnimation(),
|
||||
detailViewModel: CharacterDetailViewModel,
|
||||
detailPanelViewModel: CharacterDetailPanelViewModel,
|
||||
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel,
|
||||
characterDiminishedViewModel: CharacterSheetDiminishedDialogViewModel,
|
||||
alterationViewModel: CharacterSheetAlterationDialogViewModel,
|
||||
|
|
@ -69,16 +84,42 @@ fun CharacterDetailPanel(
|
|||
) {
|
||||
val roll = LocalRollHostState.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val detail: State<CharacterDetailPanelUio> = detailViewModel.detail.collectAsState()
|
||||
val pager = rememberPagerState(initialPage = 0) { DetailPanelUio.entries.size }
|
||||
val detail: State<CharacterDetailPanelUio> = detailPanelViewModel.detail.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
detailPanelViewModel.detail
|
||||
.map { it.panel }
|
||||
.distinctUntilChanged()
|
||||
.onEach { pager.animateScrollToPage(page = it.ordinal) }
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
CharacterDetailAnimatedPanel(
|
||||
modifier = modifier,
|
||||
pagerState = pager,
|
||||
detail = detail,
|
||||
transitionSpec = transitionSpec,
|
||||
onDismissRequest = {
|
||||
detailViewModel.hideCharacter()
|
||||
detailPanelViewModel.hideCharacter()
|
||||
},
|
||||
onLevelUp = onLevelUp,
|
||||
onSheet = { characterSheetId ->
|
||||
scope.launch {
|
||||
detailPanelViewModel.showCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = DetailPanelUio.Sheet,
|
||||
)
|
||||
}
|
||||
},
|
||||
onInventory = { characterSheetId ->
|
||||
scope.launch {
|
||||
detailPanelViewModel.showCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = DetailPanelUio.Inventory,
|
||||
)
|
||||
}
|
||||
},
|
||||
onAlteration = {
|
||||
blurController.show()
|
||||
alterationViewModel.show(characterSheetId = it)
|
||||
|
|
@ -124,7 +165,7 @@ fun CharacterDetailPanel(
|
|||
onSkill = {
|
||||
scope.launch {
|
||||
val result = roll.showRollOverlay(roll = it.roll)
|
||||
detailViewModel.onSkillUse(
|
||||
detailPanelViewModel.onSkillUse(
|
||||
skillId = it.skillId,
|
||||
result = result,
|
||||
)
|
||||
|
|
@ -133,7 +174,7 @@ fun CharacterDetailPanel(
|
|||
},
|
||||
onUseSkill = {
|
||||
scope.launch {
|
||||
detailViewModel.onSkillUse(
|
||||
detailPanelViewModel.onSkillUse(
|
||||
skillId = it.skillId,
|
||||
used = it.used,
|
||||
)
|
||||
|
|
@ -151,10 +192,14 @@ fun CharacterDetailPanel(
|
|||
@Composable
|
||||
fun CharacterDetailAnimatedPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = MaterialTheme.lwa.shapes.panel,
|
||||
pagerState: PagerState,
|
||||
detail: State<CharacterDetailPanelUio>,
|
||||
transitionSpec: AnimatedContentTransitionScope<CharacterDetailPanelUio>.() -> ContentTransform,
|
||||
onDismissRequest: (characterSheetId: String) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
onLevelUp: (characterSheetId: String) -> Unit,
|
||||
onSheet: (characterSheetId: String) -> Unit,
|
||||
onInventory: (characterSheetId: String) -> Unit,
|
||||
onAlteration: (characterSheetId: String) -> Unit,
|
||||
onDiminished: (characterSheetId: String) -> Unit,
|
||||
onHp: (characterSheetId: String) -> Unit,
|
||||
|
|
@ -184,10 +229,13 @@ fun CharacterDetailAnimatedPanel(
|
|||
) {
|
||||
CharacterDetailContent(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
header = it.header.collectAsState(),
|
||||
sheet = it.sheet.collectAsState(),
|
||||
onDismissRequest = { onDismissRequest(it.characterSheetId) },
|
||||
pagerState = pagerState,
|
||||
detail = it,
|
||||
shape = shape,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onLevelUp = { onLevelUp(it.characterSheetId) },
|
||||
onSheet = { onSheet(it.characterSheetId) },
|
||||
onInventory = { onInventory(it.characterSheetId) },
|
||||
onAlteration = { onAlteration(it.characterSheetId) },
|
||||
onDiminished = { onDiminished(it.characterSheetId) },
|
||||
onHp = { onHp(it.characterSheetId) },
|
||||
|
|
@ -209,9 +257,11 @@ fun CharacterDetailAnimatedPanel(
|
|||
fun CharacterDetailContent(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = MaterialTheme.lwa.shapes.panel,
|
||||
header: State<CharacterDetailHeaderUio?>,
|
||||
sheet: State<CharacterDetailSheetUio?>,
|
||||
detail: CharacterDetailPanelUio,
|
||||
pagerState: PagerState,
|
||||
onLevelUp: () -> Unit,
|
||||
onSheet: () -> Unit,
|
||||
onInventory: () -> Unit,
|
||||
onAlteration: () -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
onDiminished: () -> Unit,
|
||||
|
|
@ -224,7 +274,7 @@ fun CharacterDetailContent(
|
|||
onAction: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
) {
|
||||
|
|
@ -232,29 +282,42 @@ fun CharacterDetailContent(
|
|||
CharacterDetailHeader(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
|
||||
.padding(bottom = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
header = header,
|
||||
header = detail.header.collectAsState(),
|
||||
onDismissRequest = onDismissRequest,
|
||||
onLevelUp = onLevelUp,
|
||||
onSheet = onSheet,
|
||||
onInventory = onInventory,
|
||||
onAlteration = onAlteration,
|
||||
onDiminished = onDiminished,
|
||||
onHp = onHp,
|
||||
onPp = onPp,
|
||||
onReflex = onReflex,
|
||||
)
|
||||
CharacterDetailSheet(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 8.dp, bottom = 16.dp),
|
||||
sheet = sheet,
|
||||
onCharacteristic = onCharacteristic,
|
||||
onSkill = onSkill,
|
||||
onUseSkill = onUseSkill,
|
||||
onAction = onAction,
|
||||
)
|
||||
HorizontalPager(
|
||||
modifier = Modifier.weight(1f),
|
||||
state = pagerState,
|
||||
) { page ->
|
||||
when (page) {
|
||||
DetailPanelUio.Sheet.ordinal -> {
|
||||
CharacterDetailSheet(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
sheet = detail.sheet.collectAsState(),
|
||||
onCharacteristic = onCharacteristic,
|
||||
onSkill = onSkill,
|
||||
onUseSkill = onUseSkill,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
|
||||
DetailPanelUio.Inventory.ordinal -> {
|
||||
CharacterDetailInventory(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
inventory = detail.inventory.collectAsState(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,87 +2,80 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Difficulty
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Result
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
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
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class CharacterDetailViewModel(
|
||||
class CharacterDetailPanelViewModel(
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
settingRepository: SettingsRepository,
|
||||
private val characterDetailFactory: CharacterDetailFactory,
|
||||
private val characterHeaderFactory: CharacterDetailHeaderFactory,
|
||||
private val characterSheetFactory: CharacterDetailSheetFactory,
|
||||
private val characterInventoryFactory: CharacterDetailInventoryFactory,
|
||||
private val network: NetworkRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val displayedCharacterId = MutableStateFlow<String?>(null)
|
||||
private val characterSheetPanelFlow = MutableStateFlow<CharacterSheetPanel?>(null)
|
||||
|
||||
val detail: StateFlow<CharacterDetailPanelUio> = displayedCharacterId
|
||||
.map { characterSheetId ->
|
||||
if (characterSheetId == null) return@map empty()
|
||||
|
||||
CharacterDetailPanelUio(
|
||||
val detail: StateFlow<CharacterDetailPanelUio> = characterSheetPanelFlow
|
||||
.map { characterSheetPanel ->
|
||||
val (characterSheetId, panel) = characterSheetPanel ?: return@map emptyPanel()
|
||||
return@map CharacterDetailPanelUio(
|
||||
characterSheetId = characterSheetId,
|
||||
header = combine(
|
||||
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
|
||||
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId),
|
||||
settingRepository.settingsFlow()
|
||||
) { characterSheet, alterations, settings ->
|
||||
characterDetailFactory.convertToCharacterDetailHeaderUio(
|
||||
characterSheetId = characterSheetId,
|
||||
characterSheet = characterSheet,
|
||||
settings = settings,
|
||||
alterations = alterations,
|
||||
)
|
||||
}.stateIn(
|
||||
panel = panel,
|
||||
header = characterHeaderFactory.convertToCharacterDetailHeaderUioFlow(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = panel,
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null,
|
||||
initialValue = ::emptyHeader,
|
||||
),
|
||||
sheet = combine(
|
||||
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
|
||||
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId),
|
||||
) { characterSheet, alterations ->
|
||||
characterDetailFactory.convertToCharacterDetailSheetUio(
|
||||
characterSheetId = characterSheetId,
|
||||
characterSheet = characterSheet,
|
||||
alterations = alterations,
|
||||
)
|
||||
}.stateIn(
|
||||
sheet = characterSheetFactory.convertToCharacterDetailSheetUioFlow(
|
||||
characterSheetId = characterSheetId,
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null,
|
||||
initialValue = ::emptySheet,
|
||||
),
|
||||
inventory = characterInventoryFactory.convertToCharacterInventoryUioFlow(
|
||||
characterSheetId = characterSheetId,
|
||||
scope = viewModelScope,
|
||||
initialValue = ::emptyInventory,
|
||||
),
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = empty(),
|
||||
initialValue = emptyPanel(),
|
||||
)
|
||||
|
||||
suspend fun showCharacter(
|
||||
characterSheetId: String,
|
||||
panel: DetailPanelUio,
|
||||
) {
|
||||
if (characterSheetRepository.characterDetail(characterSheetId = characterSheetId) == null) {
|
||||
characterSheetRepository.updateCharacterSheet(characterSheetId = characterSheetId)
|
||||
}
|
||||
displayedCharacterId.value = characterSheetId
|
||||
characterSheetPanelFlow.value = CharacterSheetPanel(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = panel,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun editCharacter(
|
||||
|
|
@ -99,14 +92,14 @@ class CharacterDetailViewModel(
|
|||
}
|
||||
|
||||
fun hideCharacter() {
|
||||
displayedCharacterId.value = null
|
||||
characterSheetPanelFlow.value = null
|
||||
}
|
||||
|
||||
suspend fun onSkillUse(
|
||||
skillId: String,
|
||||
result: RollResult,
|
||||
) {
|
||||
val characterSheetId = displayedCharacterId.value ?: return
|
||||
val (characterSheetId, _) = characterSheetPanelFlow.value ?: return
|
||||
// check if the RollResult is a BoundedRollResult. can work with other roll result.
|
||||
val roll = result as? RollResult.BoundedRollResult ?: return
|
||||
// check if the roll is a success with some challenge.
|
||||
|
|
@ -136,7 +129,7 @@ class CharacterDetailViewModel(
|
|||
skillId: String,
|
||||
used: Boolean,
|
||||
) {
|
||||
val characterSheetId = displayedCharacterId.value ?: return
|
||||
val (characterSheetId, _) = characterSheetPanelFlow.value ?: return
|
||||
network.share(
|
||||
message = CharacterSheetEvent.UpdateSkillUsageEvent(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
|
|
@ -147,9 +140,22 @@ class CharacterDetailViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
private fun empty() = CharacterDetailPanelUio(
|
||||
private fun emptyPanel() = CharacterDetailPanelUio(
|
||||
characterSheetId = null,
|
||||
header = MutableStateFlow(null),
|
||||
sheet = MutableStateFlow(null),
|
||||
panel = DetailPanelUio.Sheet,
|
||||
header = MutableStateFlow(value = emptyHeader()),
|
||||
sheet = MutableStateFlow(value = emptySheet()),
|
||||
inventory = MutableStateFlow(value = emptyInventory()),
|
||||
)
|
||||
|
||||
private fun emptyHeader(): CharacterDetailHeaderUio? = null
|
||||
|
||||
private fun emptySheet(): CharacterDetailSheetUio? = null
|
||||
|
||||
private fun emptyInventory(): CharacterDetailInventoryUio? = null
|
||||
|
||||
data class CharacterSheetPanel(
|
||||
val characterSheetId: String,
|
||||
val panel: DetailPanelUio,
|
||||
)
|
||||
}
|
||||
|
|
@ -13,8 +13,11 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
|
|
@ -26,9 +29,11 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -38,6 +43,7 @@ import com.pixelized.desktop.lwa.ui.composable.shapes.ArrowShape
|
|||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__level
|
||||
|
|
@ -45,9 +51,11 @@ import lwacharactersheet.composeapp.generated.resources.ic_azm_24dp
|
|||
import lwacharactersheet.composeapp.generated.resources.ic_blur_on_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_cognition_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_description_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_directions_run_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_heart_plus_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_money_bag_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_pan_tool_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_shield_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_skull_24dp
|
||||
|
|
@ -59,6 +67,7 @@ import org.jetbrains.compose.resources.stringResource
|
|||
@Stable
|
||||
data class CharacterDetailHeaderUio(
|
||||
val characterSheetId: String,
|
||||
val panel: DetailPanelUio,
|
||||
val portrait: String?,
|
||||
val diminished: Int,
|
||||
val alteration: Boolean,
|
||||
|
|
@ -88,25 +97,39 @@ data class CharacterDetailHeaderUio(
|
|||
val initiativeTooltip: TooltipUio,
|
||||
)
|
||||
|
||||
@Stable
|
||||
object CharacterDetailHeaderDefault {
|
||||
@Stable
|
||||
val paddings = PaddingValues(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun CharacterDetailHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings: PaddingValues = CharacterDetailHeaderDefault.paddings,
|
||||
iconSize: Dp = MaterialTheme.lwa.size.sheet.subCategory,
|
||||
header: State<CharacterDetailHeaderUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onLevelUp: () -> Unit,
|
||||
onSheet: () -> Unit,
|
||||
onInventory: () -> Unit,
|
||||
onAlteration: () -> Unit,
|
||||
onDiminished: () -> Unit,
|
||||
onHp: () -> Unit,
|
||||
onPp: () -> Unit,
|
||||
onReflex: (RollAction.Uio) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val startPadding = remember(layoutDirection) { paddings.calculateStartPadding(layoutDirection) }
|
||||
val endPadding = remember(layoutDirection) { paddings.calculateEndPadding(layoutDirection) }
|
||||
val bottomPadding = remember { paddings.calculateBottomPadding() }
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
modifier = Modifier.padding(start = startPadding),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -130,6 +153,50 @@ fun CharacterDetailHeader(
|
|||
),
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = header.value?.levelUp == true,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onLevelUp,
|
||||
) {
|
||||
ArrowShape(
|
||||
color = MaterialTheme.lwa.colorScheme.portrait.levelUp,
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedContent(
|
||||
targetState = header.value?.panel,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
when (it) {
|
||||
DetailPanelUio.Inventory -> IconButton(
|
||||
onClick = onSheet,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
painter = painterResource(Res.drawable.ic_description_24dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
DetailPanelUio.Sheet -> IconButton(
|
||||
onClick = onInventory,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
painter = painterResource(Res.drawable.ic_money_bag_24dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = header.value?.alteration == true,
|
||||
enter = fadeIn(),
|
||||
|
|
@ -146,19 +213,6 @@ fun CharacterDetailHeader(
|
|||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = header.value?.levelUp == true,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onLevelUp,
|
||||
) {
|
||||
ArrowShape(
|
||||
color = MaterialTheme.lwa.colorScheme.portrait.levelUp,
|
||||
)
|
||||
}
|
||||
}
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = onDiminished,
|
||||
|
|
@ -200,7 +254,11 @@ fun CharacterDetailHeader(
|
|||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.padding(
|
||||
start = startPadding,
|
||||
end = endPadding,
|
||||
bottom = bottomPadding,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 10.dp),
|
||||
) {
|
||||
TooltipLayout(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
|
||||
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
|
||||
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hp_grow
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__initiative
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__learning
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__reflex
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hp_grow
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__initiative
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__learning
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__reflex
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class CharacterDetailHeaderFactory(
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val settingRepository: SettingsRepository,
|
||||
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
|
||||
) {
|
||||
fun convertToCharacterDetailHeaderUioFlow(
|
||||
characterSheetId: String,
|
||||
panel: DetailPanelUio,
|
||||
scope: CoroutineScope,
|
||||
started: SharingStarted = SharingStarted.Eagerly,
|
||||
initialValue: () -> CharacterDetailHeaderUio?,
|
||||
): StateFlow<CharacterDetailHeaderUio?> {
|
||||
return combine(
|
||||
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
|
||||
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId),
|
||||
settingRepository.settingsFlow()
|
||||
) { characterSheet, alterations, settings ->
|
||||
convertToCharacterDetailHeaderUio(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = panel,
|
||||
characterSheet = characterSheet,
|
||||
settings = settings,
|
||||
alterations = alterations,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = started,
|
||||
initialValue = initialValue(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun convertToCharacterDetailHeaderUio(
|
||||
characterSheetId: String,
|
||||
characterSheet: CharacterSheet?,
|
||||
panel: DetailPanelUio,
|
||||
settings: Settings,
|
||||
alterations: Map<String, List<FieldAlteration>>,
|
||||
): CharacterDetailHeaderUio? {
|
||||
if (characterSheet == null) return null
|
||||
|
||||
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
|
||||
characterSheet = characterSheet,
|
||||
alterations = alterations,
|
||||
)
|
||||
|
||||
val maxHp = alteredCharacterSheet.maxHp
|
||||
val maxPp = alteredCharacterSheet.maxPp
|
||||
|
||||
return CharacterDetailHeaderUio(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = panel,
|
||||
portrait = alteredCharacterSheet.portrait,
|
||||
diminished = alteredCharacterSheet.diminished,
|
||||
alteration = settings.isAdmin ?: false,
|
||||
levelUp = alteredCharacterSheet.shouldLevelUp,
|
||||
name = alteredCharacterSheet.name,
|
||||
level = alteredCharacterSheet.level,
|
||||
hp = "${maxHp - alteredCharacterSheet.damage}",
|
||||
hpTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__hit_point),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__hit_point)
|
||||
),
|
||||
maxHp = "$maxHp",
|
||||
pp = "${maxPp - alteredCharacterSheet.fatigue}",
|
||||
ppTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__power_point),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__power_point)
|
||||
),
|
||||
maxPp = "$maxPp",
|
||||
mov = "${alteredCharacterSheet.movement}",
|
||||
movTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__movement),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__movement)
|
||||
),
|
||||
armor = "${alteredCharacterSheet.armor}",
|
||||
armorTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__armor),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__armor)
|
||||
),
|
||||
bonus = alteredCharacterSheet.damageBonus,
|
||||
bonusTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__bonus_damage)
|
||||
),
|
||||
grow = "${alteredCharacterSheet.hpGrow}",
|
||||
growTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__hp_grow),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__hp_grow)
|
||||
),
|
||||
learn = "${alteredCharacterSheet.learning}",
|
||||
learnTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__learning),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__learning)
|
||||
),
|
||||
reflex = "${alteredCharacterSheet.reflex}",
|
||||
reflexTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__reflex),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__reflex)
|
||||
),
|
||||
reflexRoll = RollAction.Uio.BoundedRollActionUio(
|
||||
characterSheetId = characterSheetId,
|
||||
label = getString(Res.string.character_sheet__sub_characteristics__reflex),
|
||||
rollAction = "1d100",
|
||||
rollSuccessValue = alteredCharacterSheet.reflex * 5,
|
||||
),
|
||||
initiative = "${alteredCharacterSheet.initiative}",
|
||||
initiativeTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__initiative),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__initiative)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.LocalBlurController
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item.InventoryItem
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item.InventoryItemUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_copper_32px
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_gold_32px
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_silver_32px
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Stable
|
||||
data class CharacterDetailInventoryUio(
|
||||
val characterSheetId: String,
|
||||
val purse: PurseUio,
|
||||
val items: List<InventoryItemUio>,
|
||||
) {
|
||||
@Stable
|
||||
data class PurseUio(
|
||||
val gold: Int,
|
||||
val silver: Int,
|
||||
val copper: Int,
|
||||
)
|
||||
}
|
||||
|
||||
object CharacterDetailInventoryDefault {
|
||||
val padding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterDetailInventory(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings: PaddingValues = CharacterDetailInventoryDefault.padding,
|
||||
purseViewModel: PurseDialogViewModel = koinViewModel(),
|
||||
inventory: State<CharacterDetailInventoryUio?>,
|
||||
) {
|
||||
val blur = LocalBlurController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
when (val unWrap = inventory.value) {
|
||||
null -> Box(modifier = modifier)
|
||||
|
||||
else -> CharacterDetailInventoryContent(
|
||||
modifier = modifier,
|
||||
paddings = paddings,
|
||||
inventory = unWrap,
|
||||
onPurse = {
|
||||
blur.show()
|
||||
purseViewModel.showPurseDialog(characterSheetId = it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
PurseDialog(
|
||||
dialog = purseViewModel.purseDialog.collectAsState(),
|
||||
onDismissRequest = {
|
||||
blur.hide()
|
||||
purseViewModel.hidePurseDialog()
|
||||
},
|
||||
onSwapSign = {
|
||||
purseViewModel.swapPurseSign()
|
||||
},
|
||||
onConfirm = {
|
||||
scope.launch {
|
||||
if (purseViewModel.confirmPurse(dialog = it)) {
|
||||
blur.hide()
|
||||
purseViewModel.hidePurseDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ErrorSnackHandler(
|
||||
error = purseViewModel.error,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CharacterDetailInventoryContent(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings: PaddingValues,
|
||||
inventory: CharacterDetailInventoryUio,
|
||||
onPurse: (String) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentPadding = paddings,
|
||||
) {
|
||||
items(
|
||||
items = inventory.items,
|
||||
key = { it.inventoryId },
|
||||
) {
|
||||
InventoryItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
item = it,
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Purse(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
paddings = paddings,
|
||||
purse = inventory.purse,
|
||||
onPurse = { onPurse(inventory.characterSheetId) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Purse(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings: PaddingValues,
|
||||
purse: CharacterDetailInventoryUio.PurseUio,
|
||||
onPurse: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
|
||||
.then(other = modifier),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable { onPurse() }
|
||||
.padding(paddingValues = paddings),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Image(
|
||||
painter = painterResource(Res.drawable.ic_gold_32px),
|
||||
modifier = Modifier.padding(bottom = 2.dp).size(size = 16.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = purse.gold,
|
||||
transitionSpec = coinTransitionSpec(),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "$it",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Image(
|
||||
painter = painterResource(Res.drawable.ic_silver_32px),
|
||||
modifier = Modifier.padding(bottom = 2.dp).size(size = 16.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = purse.silver,
|
||||
transitionSpec = coinTransitionSpec(),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "$it",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Image(
|
||||
painter = painterResource(Res.drawable.ic_copper_32px),
|
||||
modifier = Modifier.padding(bottom = 2.dp).size(size = 16.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = purse.copper,
|
||||
transitionSpec = coinTransitionSpec(),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "$it",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
private fun coinTransitionSpec(): AnimatedContentTransitionScope<Int>.() -> ContentTransform = {
|
||||
val enter = fadeIn() + slideInVertically { -16 }
|
||||
val exit = fadeOut() + slideOutVertically { 16 }
|
||||
enter togetherWith exit using SizeTransform(clip = false)
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item.InventoryItemUio
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import com.pixelized.shared.lwa.model.item.Item
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class CharacterDetailInventoryFactory(
|
||||
private val inventoryRepository: InventoryRepository,
|
||||
private val itemRepository: ItemRepository,
|
||||
) {
|
||||
|
||||
fun convertToCharacterInventoryUioFlow(
|
||||
characterSheetId: String,
|
||||
scope: CoroutineScope,
|
||||
started: SharingStarted = SharingStarted.Eagerly,
|
||||
initialValue: () -> CharacterDetailInventoryUio?,
|
||||
): StateFlow<CharacterDetailInventoryUio?> {
|
||||
return combine(
|
||||
inventoryRepository.inventoryFlow(characterSheetId = characterSheetId),
|
||||
itemRepository.itemFlow,
|
||||
) { inventory, items ->
|
||||
convertToCharacterInventoryUio(
|
||||
characterSheetId = characterSheetId,
|
||||
purse = inventory?.purse,
|
||||
inventory = inventory?.items,
|
||||
items = items,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = started,
|
||||
initialValue = initialValue(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun convertToCharacterInventoryUio(
|
||||
characterSheetId: String?,
|
||||
purse: Inventory.Purse?,
|
||||
inventory: List<Inventory.Item>?,
|
||||
items: Map<String, Item>,
|
||||
): CharacterDetailInventoryUio? {
|
||||
if (characterSheetId == null) return null
|
||||
|
||||
return CharacterDetailInventoryUio(
|
||||
characterSheetId = characterSheetId,
|
||||
purse = CharacterDetailInventoryUio.PurseUio(
|
||||
gold = purse?.gold ?: 0,
|
||||
silver = purse?.silver ?: 0,
|
||||
copper = purse?.copper ?: 0,
|
||||
),
|
||||
items = inventory
|
||||
?.mapNotNull {
|
||||
val label = items[it.itemId]?.metadata?.name ?: return@mapNotNull null
|
||||
InventoryItemUio(
|
||||
inventoryId = it.inventoryId,
|
||||
label = label,
|
||||
)
|
||||
}
|
||||
?: emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
||||
@Stable
|
||||
data class InventoryItemUio(
|
||||
val inventoryId: String,
|
||||
val label: String,
|
||||
)
|
||||
|
||||
@Stable
|
||||
object GMCharacterPreviewDefault {
|
||||
@Stable
|
||||
val paddings = PaddingValues(horizontal = 16.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InventoryItem(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = GMCharacterPreviewDefault.paddings,
|
||||
item: InventoryItemUio,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||
.clickable(onClick = onClick)
|
||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
text = item.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
|||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -16,8 +19,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetAction
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetCharacteristic
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetSkill
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
|
||||
|
|
@ -34,9 +42,16 @@ data class CharacterDetailSheetUio(
|
|||
val actions: List<CharacterDetailSheetActionUio>,
|
||||
)
|
||||
|
||||
@Stable
|
||||
object CharacterDetailSheetDefault {
|
||||
@Stable
|
||||
val paddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterDetailSheet(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings : PaddingValues = CharacterDetailSheetDefault.paddings,
|
||||
sheet: State<CharacterDetailSheetUio?>,
|
||||
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
|
||||
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
|
|
@ -44,7 +59,9 @@ fun CharacterDetailSheet(
|
|||
onAction: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
modifier = modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.padding(paddingValues = paddings),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
|
||||
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__cha
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__con
|
||||
|
|
@ -21,15 +25,6 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet__charact
|
|||
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__int
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__pow
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__str
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hp_grow
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__initiative
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__learning
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__reflex
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__charisma
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__constitution
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__dexterity
|
||||
|
|
@ -37,99 +32,34 @@ import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics
|
|||
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__intelligence
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__power
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__strength
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hp_grow
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__initiative
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__learning
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point
|
||||
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__reflex
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import java.text.Collator
|
||||
|
||||
class CharacterDetailFactory(
|
||||
class CharacterDetailSheetFactory(
|
||||
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
|
||||
private val expressionUseCase: ExpressionUseCase,
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
) {
|
||||
suspend fun convertToCharacterDetailHeaderUio(
|
||||
fun convertToCharacterDetailSheetUioFlow(
|
||||
characterSheetId: String,
|
||||
characterSheet: CharacterSheet?,
|
||||
settings: Settings,
|
||||
alterations: Map<String, List<FieldAlteration>>,
|
||||
): CharacterDetailHeaderUio? {
|
||||
if (characterSheet == null) return null
|
||||
|
||||
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
|
||||
characterSheet = characterSheet,
|
||||
alterations = alterations,
|
||||
)
|
||||
|
||||
val maxHp = alteredCharacterSheet.maxHp
|
||||
val maxPp = alteredCharacterSheet.maxPp
|
||||
|
||||
return CharacterDetailHeaderUio(
|
||||
characterSheetId = characterSheetId,
|
||||
portrait = alteredCharacterSheet.portrait,
|
||||
diminished = alteredCharacterSheet.diminished,
|
||||
alteration = settings.isAdmin ?: false,
|
||||
levelUp = alteredCharacterSheet.shouldLevelUp,
|
||||
name = alteredCharacterSheet.name,
|
||||
level = alteredCharacterSheet.level,
|
||||
hp = "${maxHp - alteredCharacterSheet.damage}",
|
||||
hpTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__hit_point),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__hit_point)
|
||||
),
|
||||
maxHp = "$maxHp",
|
||||
pp = "${maxPp - alteredCharacterSheet.fatigue}",
|
||||
ppTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__power_point),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__power_point)
|
||||
),
|
||||
maxPp = "$maxPp",
|
||||
mov = "${alteredCharacterSheet.movement}",
|
||||
movTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__movement),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__movement)
|
||||
),
|
||||
armor = "${alteredCharacterSheet.armor}",
|
||||
armorTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__armor),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__armor)
|
||||
),
|
||||
bonus = alteredCharacterSheet.damageBonus,
|
||||
bonusTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__bonus_damage)
|
||||
),
|
||||
grow = "${alteredCharacterSheet.hpGrow}",
|
||||
growTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__hp_grow),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__hp_grow)
|
||||
),
|
||||
learn = "${alteredCharacterSheet.learning}",
|
||||
learnTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__learning),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__learning)
|
||||
),
|
||||
reflex = "${alteredCharacterSheet.reflex}",
|
||||
reflexTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__reflex),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__reflex)
|
||||
),
|
||||
reflexRoll = RollAction.Uio.BoundedRollActionUio(
|
||||
scope: CoroutineScope,
|
||||
started: SharingStarted = SharingStarted.Eagerly,
|
||||
initialValue: () -> CharacterDetailSheetUio?,
|
||||
): StateFlow<CharacterDetailSheetUio?> {
|
||||
return combine(
|
||||
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
|
||||
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId),
|
||||
) { characterSheet, alterations ->
|
||||
convertToCharacterDetailSheetUio(
|
||||
characterSheetId = characterSheetId,
|
||||
label = getString(Res.string.character_sheet__sub_characteristics__reflex),
|
||||
rollAction = "1d100",
|
||||
rollSuccessValue = alteredCharacterSheet.reflex * 5,
|
||||
),
|
||||
initiative = "${alteredCharacterSheet.initiative}",
|
||||
initiativeTooltip = TooltipUio(
|
||||
title = getString(Res.string.character_sheet__sub_characteristics__initiative),
|
||||
description = getString(Res.string.tooltip__sub_characteristics__initiative)
|
||||
),
|
||||
characterSheet = characterSheet,
|
||||
alterations = alterations,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = started,
|
||||
initialValue = initialValue(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.item
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -105,6 +105,10 @@ abstract class CharacterRibbonViewModel(
|
|||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun characters(index: Int): String? = characters.value
|
||||
.getOrNull(index)
|
||||
?.characterSheetId
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun roll(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ fun NpcRibbon(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: NpcRibbonViewModel = koinViewModel(),
|
||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
||||
onCharacter: (characterSheetId: String) -> Unit,
|
||||
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
||||
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
||||
onLevelUp: (characterSheetId: String) -> Unit,
|
||||
) {
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
|
|
@ -46,7 +47,8 @@ fun NpcRibbon(
|
|||
)
|
||||
CharacterPortrait(
|
||||
character = it,
|
||||
onCharacter = onCharacter,
|
||||
onCharacterLeftClick = onCharacterLeftClick,
|
||||
onCharacterRightClick = onCharacterRightClick,
|
||||
onLevelUp = onLevelUp,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ fun PlayerRibbon(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: PlayerRibbonViewModel = koinViewModel(),
|
||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
||||
onCharacter: (characterSheetId: String) -> Unit,
|
||||
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
||||
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
||||
onLevelUp: (characterSheetId: String) -> Unit,
|
||||
) {
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
|
|
@ -37,7 +38,8 @@ fun PlayerRibbon(
|
|||
) {
|
||||
CharacterPortrait(
|
||||
character = it,
|
||||
onCharacter = onCharacter,
|
||||
onCharacterLeftClick = onCharacterLeftClick,
|
||||
onCharacterRightClick = onCharacterRightClick,
|
||||
onLevelUp = onLevelUp,
|
||||
)
|
||||
CharacterPortraitRoll(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
|||
import androidx.lifecycle.ViewModel
|
||||
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.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
|
@ -10,7 +11,8 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class CampaignToolbarViewModel(
|
||||
campaignRepository: CampaignRepository,
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val campaignRepository: CampaignRepository,
|
||||
networkRepository: NetworkRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
) : ViewModel() {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,10 @@ private fun Dialog(
|
|||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(event = onDismissRequest)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = { onConfirm(dialog) },
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -21,14 +21,11 @@ import androidx.compose.material.Surface
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
|
||||
|
|
@ -93,7 +90,10 @@ private fun Dialog(
|
|||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(event = onDismissRequest)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = { onConfirm(dialog) },
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@ private fun CharacterSheetDeleteConfirmationContent(
|
|||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(event = onDismissRequest)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = { onConfirm(dialog) },
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@ data class GMCharacterItemUio(
|
|||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
object GMCharacterPreviewDefault {
|
||||
@Stable
|
||||
val padding = PaddingValues(start = 16.dp)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
|||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToLevelScreen
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
|
|
@ -62,7 +63,7 @@ import org.koin.compose.viewmodel.koinViewModel
|
|||
@Composable
|
||||
fun GMCharacterPage(
|
||||
viewModel: GMCharacterViewModel = koinViewModel(),
|
||||
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
||||
characterDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(),
|
||||
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel = koinViewModel(),
|
||||
dismissedViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(),
|
||||
alterationViewModel: CharacterSheetAlterationDialogViewModel = koinViewModel(),
|
||||
|
|
@ -84,7 +85,10 @@ fun GMCharacterPage(
|
|||
onCharacterAction = viewModel::onCharacterAction,
|
||||
onCharacterSheetDetail = { characterSheetId ->
|
||||
scope.launch {
|
||||
characterDetailViewModel.showCharacter(characterSheetId = characterSheetId)
|
||||
characterDetailViewModel.showCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
panel = DetailPanelUio.Sheet,
|
||||
)
|
||||
}
|
||||
},
|
||||
onCharacterSheetEdit = { characterSheetId ->
|
||||
|
|
@ -113,7 +117,7 @@ fun GMCharacterPage(
|
|||
.fillMaxHeight(),
|
||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
|
||||
blurController = blurController,
|
||||
detailViewModel = characterDetailViewModel,
|
||||
detailPanelViewModel = characterDetailViewModel,
|
||||
characterDiminishedViewModel = dismissedViewModel,
|
||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||
alterationViewModel = alterationViewModel,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp
|
|||
data class LwaShapes(
|
||||
val base: Shapes,
|
||||
val portrait: Shape,
|
||||
val item: Shape,
|
||||
val panel: Shape,
|
||||
val settings: Shape,
|
||||
val gameMaster: Shape,
|
||||
|
|
@ -24,6 +25,7 @@ fun lwaShapes(
|
|||
small = RoundedCornerShape(4.dp),
|
||||
),
|
||||
portrait: Shape = RoundedCornerShape(8.dp),
|
||||
item: Shape = RoundedCornerShape(8.dp),
|
||||
panel: Shape = RoundedCornerShape(8.dp),
|
||||
settings: Shape = RoundedCornerShape(8.dp),
|
||||
gameMaster: Shape = RoundedCornerShape(8.dp),
|
||||
|
|
@ -31,6 +33,7 @@ fun lwaShapes(
|
|||
LwaShapes(
|
||||
base = base,
|
||||
portrait = portrait,
|
||||
item = item,
|
||||
panel = panel,
|
||||
settings = settings,
|
||||
gameMaster = gameMaster,
|
||||
|
|
|
|||
|
|
@ -2,16 +2,27 @@ package com.pixelized.desktop.lwa.utils.extention
|
|||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
|
||||
fun Modifier.onPreviewEscape(event: () -> Unit): Modifier = this.onPreviewKeyEvent {
|
||||
when {
|
||||
it.key == Key.Escape -> {
|
||||
event()
|
||||
fun Modifier.onPreviewEscape(
|
||||
escape: () -> Unit,
|
||||
enter: () -> Unit,
|
||||
): Modifier = this.onPreviewKeyEvent {
|
||||
when (it.key) {
|
||||
Key.Escape -> {
|
||||
if (it.type != KeyEventType.KeyDown) escape()
|
||||
true
|
||||
}
|
||||
Key.NumPadEnter, Key.Enter -> {
|
||||
if (it.type != KeyEventType.KeyDown) enter()
|
||||
true
|
||||
}
|
||||
Key.Spacebar -> {
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ class InventoryStore(
|
|||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
file.name to inventory
|
||||
inventory.characterSheetId to inventory
|
||||
}
|
||||
?.toMap()
|
||||
?: emptyMap()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ data class Inventory(
|
|||
)
|
||||
|
||||
data class Item(
|
||||
val inventoryId: String,
|
||||
val itemId: String,
|
||||
val count: Int,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ data class InventoryJsonV1(
|
|||
|
||||
@Serializable
|
||||
data class ItemJson(
|
||||
val inventoryId: String,
|
||||
val itemId: String,
|
||||
val count: Int,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class InventoryJsonFactoryV1 {
|
|||
),
|
||||
items = json.items.map {
|
||||
Inventory.Item(
|
||||
inventoryId = it.inventoryId,
|
||||
itemId = it.itemId,
|
||||
count = it.count,
|
||||
)
|
||||
|
|
@ -32,6 +33,7 @@ class InventoryJsonFactoryV1 {
|
|||
),
|
||||
items = inventory.items.map {
|
||||
InventoryJsonV1.ItemJson(
|
||||
inventoryId = it.inventoryId,
|
||||
itemId = it.itemId,
|
||||
count = it.count,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue