tmp
This commit is contained in:
parent
76336dfbb0
commit
02987a0a53
54 changed files with 1487 additions and 332 deletions
|
|
@ -243,6 +243,16 @@
|
|||
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
||||
<string name="game_master__create_character_sheet">Créer un personnage</string>
|
||||
<string name="game_master__alteration__filter">Filtrer par nom :</string>
|
||||
<string name="game_master__alteration__create">Créer une altération</string>
|
||||
<string name="game_master__alteration__delete">Supprimer l'altération</string>
|
||||
<string name="game_master__alteration__edit_add_field">Ajouter un champ</string>
|
||||
<string name="game_master__alteration__edit_id">Identifiant de l'altération</string>
|
||||
<string name="game_master__alteration__edit_label">Nom</string>
|
||||
<string name="game_master__alteration__edit_description">Description</string>
|
||||
<string name="game_master__alteration__edit_tags">Tags</string>
|
||||
<string name="game_master__alteration__edit_field_id">Identifiant du champ</string>
|
||||
<string name="game_master__alteration__edit_field_expression">Expression</string>
|
||||
<string name="game_master__alteration__edit_field_save">Sauvegarder</string>
|
||||
<string name="game_master__alteration__edit_field_cancel">Annuler</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -41,7 +41,9 @@ class DataSyncViewModel(
|
|||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||
.onEach {
|
||||
alterationRepository.updateAlterations()
|
||||
alterationRepository.updateTags()
|
||||
characterRepository.updateCharacterPreviews()
|
||||
characterRepository.updateTags()
|
||||
campaignRepository.updateCampaign()
|
||||
}
|
||||
.launchIn(this)
|
||||
|
|
|
|||
|
|
@ -36,10 +36,13 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEdi
|
|||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||
|
|
@ -123,8 +126,10 @@ val factoryDependencies
|
|||
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
||||
factoryOf(::TextMessageFactory)
|
||||
factoryOf(::LevelUpFactory)
|
||||
factoryOf(::GMTagFactory)
|
||||
factoryOf(::GMCharacterFactory)
|
||||
factoryOf(::GMAlterationFactory)
|
||||
factoryOf(::GMAlterationEditFactory)
|
||||
}
|
||||
|
||||
val viewModelDependencies
|
||||
|
|
@ -149,6 +154,7 @@ val viewModelDependencies
|
|||
viewModelOf(::GameMasterViewModel)
|
||||
viewModelOf(::GMActionViewModel)
|
||||
viewModelOf(::GMAlterationViewModel)
|
||||
viewModelOf(::GMAlterationEditViewModel)
|
||||
}
|
||||
|
||||
val useCaseDependencies
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
|
||||
interface LwaClient {
|
||||
|
|
@ -15,6 +16,16 @@ interface LwaClient {
|
|||
alterationId: String,
|
||||
): AlterationJson?
|
||||
|
||||
suspend fun updateAlteration(
|
||||
alterationJson: AlterationJson
|
||||
)
|
||||
|
||||
suspend fun deleteAlteration(
|
||||
alterationId: String,
|
||||
)
|
||||
|
||||
suspend fun alterationTags(): List<TagJson>
|
||||
|
||||
// Campaign
|
||||
|
||||
suspend fun campaign(): CampaignJson
|
||||
|
|
@ -39,6 +50,8 @@ interface LwaClient {
|
|||
|
||||
suspend fun characters(): List<CharacterPreviewJson>
|
||||
|
||||
suspend fun characterTags(): List<TagJson>
|
||||
|
||||
suspend fun character(
|
||||
characterSheetId: String,
|
||||
): CharacterSheetJson
|
||||
|
|
|
|||
|
|
@ -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.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
|
|
@ -30,6 +31,23 @@ class LwaClientImpl(
|
|||
.get("$root/alteration/detail?alterationId=$alterationId")
|
||||
.body()
|
||||
|
||||
override suspend fun updateAlteration(
|
||||
alterationJson: AlterationJson
|
||||
) = client
|
||||
.put("$root/alteration/update") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(alterationJson)
|
||||
}
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun deleteAlteration(alterationId: String) = client
|
||||
.delete("$root/alteration/delete?alterationId=$alterationId")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun alterationTags(): List<TagJson> = client
|
||||
.get("$root/alteration/tags")
|
||||
.body()
|
||||
|
||||
override suspend fun campaign(): CampaignJson = client
|
||||
.get("$root/campaign")
|
||||
.body()
|
||||
|
|
@ -62,6 +80,10 @@ class LwaClientImpl(
|
|||
.get("$root/character/all")
|
||||
.body()
|
||||
|
||||
override suspend fun characterTags(): List<TagJson> = client
|
||||
.get("$root/character/tags")
|
||||
.body()
|
||||
|
||||
override suspend fun character(
|
||||
characterSheetId: String,
|
||||
): CharacterSheetJson = client
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class AlterationRepository(
|
|||
|
||||
val alterationFlow get() = alterationStore.alterationsFlow
|
||||
|
||||
val tagsFlow get() = alterationStore.tagsFlow
|
||||
|
||||
/**
|
||||
* This flow transform the campaign instance (player + npc) into a
|
||||
* Map<CharacterSheetId, List<AlterationId>>.
|
||||
|
|
@ -67,6 +69,16 @@ class AlterationRepository(
|
|||
alterationStore.updateAlterations()
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
alterationStore.updateTags()
|
||||
}
|
||||
|
||||
fun alteration(
|
||||
alterationId: String?,
|
||||
): Alteration? {
|
||||
return alterationFlow.value[alterationId]
|
||||
}
|
||||
|
||||
fun fieldAlterations(
|
||||
characterSheetId: String,
|
||||
): Map<String, List<FieldAlteration>> {
|
||||
|
|
@ -79,6 +91,18 @@ class AlterationRepository(
|
|||
return activeAlterationMapFlow.map { it[characterSheetId] ?: emptyMap() }
|
||||
}
|
||||
|
||||
suspend fun updateAlteration(
|
||||
alteration: Alteration
|
||||
) {
|
||||
alterationStore.putAlteration(alteration)
|
||||
}
|
||||
|
||||
suspend fun deleteAlteration(
|
||||
alterationId: String,
|
||||
) {
|
||||
alterationStore.deleteAlteration(alterationId = alterationId)
|
||||
}
|
||||
|
||||
private fun transformToAlterationFieldMap(
|
||||
alterations: Map<String, Alteration>,
|
||||
actives: List<String>,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,92 @@
|
|||
package com.pixelized.desktop.lwa.repository.alteration
|
||||
|
||||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
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 AlterationStore(
|
||||
private val networkRepository: NetworkRepository,
|
||||
private val alterationFactory: AlterationJsonFactory,
|
||||
private val tagFactory: TagJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val _alterationsFlow = MutableStateFlow<Map<String, Alteration>>(emptyMap())
|
||||
val alterationsFlow: StateFlow<Map<String, Alteration>> = _alterationsFlow
|
||||
|
||||
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
scope.launch {
|
||||
updateAlterations()
|
||||
updateTags()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
networkRepository.data.collect(::handleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun alterations(): Collection<Alteration> {
|
||||
return alterationsFlow.value.values
|
||||
}
|
||||
|
||||
fun tags(): Collection<Tag> {
|
||||
return tagsFlow.value.values
|
||||
}
|
||||
|
||||
fun alteration(alterationId: String): Alteration? {
|
||||
return alterationsFlow.value[alterationId]
|
||||
}
|
||||
|
||||
fun tag(tagId: String): Tag? {
|
||||
return tagsFlow.value[tagId]
|
||||
}
|
||||
|
||||
suspend fun updateAlterations() {
|
||||
_alterationsFlow.value = try {
|
||||
loadAlteration()
|
||||
getAlteration()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateAlteration(
|
||||
alterationId: String,
|
||||
) {
|
||||
val alteration = try {
|
||||
getAlteration(alterationId = alterationId)
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
null
|
||||
}
|
||||
|
||||
if (alteration == null) return
|
||||
|
||||
_alterationsFlow.update { alterations ->
|
||||
alterations.toMutableMap().also {
|
||||
it[alterationId] = alteration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
_tagsFlow.value = try {
|
||||
getAlterationTag()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
|
|
@ -34,17 +94,62 @@ class AlterationStore(
|
|||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun loadAlteration(): Map<String, Alteration> {
|
||||
private suspend fun getAlteration(): Map<String, Alteration> {
|
||||
val request = client.alterations()
|
||||
val data = request.map { alterationFactory.convertFromJson(json = it) }
|
||||
return data.associateBy { it.id }
|
||||
}
|
||||
|
||||
fun alterations(): Collection<Alteration> {
|
||||
return alterationsFlow.value.values
|
||||
@Throws
|
||||
private suspend fun getAlteration(
|
||||
alterationId: String,
|
||||
): Alteration? {
|
||||
val request = client.alterations(alterationId = alterationId)
|
||||
return request?.let { alterationFactory.convertFromJson(json = it) }
|
||||
}
|
||||
|
||||
fun alteration(alterationId: String): Alteration? {
|
||||
return alterationsFlow.value[alterationId]
|
||||
@Throws
|
||||
private suspend fun getAlterationTag(): Map<String, Tag> {
|
||||
val request = client.alterationTags()
|
||||
val data = request.map { tagFactory.convertFromJson(json = it) }
|
||||
return data.associateBy { it.id }
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun putAlteration(
|
||||
alteration: Alteration,
|
||||
) {
|
||||
client.updateAlteration(
|
||||
alterationJson = alterationFactory.convertToJson(data = alteration)
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteAlteration(
|
||||
alterationId: String
|
||||
) {
|
||||
client.deleteAlteration(
|
||||
alterationId = alterationId
|
||||
)
|
||||
}
|
||||
|
||||
// region: WebSocket & data update.
|
||||
|
||||
private suspend fun handleMessage(message: SocketMessage) {
|
||||
when (message) {
|
||||
is ApiSynchronisation.AlterationUpdate -> updateAlteration(
|
||||
alterationId = message.alterationId,
|
||||
)
|
||||
|
||||
is ApiSynchronisation.AlterationDelete -> _alterationsFlow.update { alterations ->
|
||||
alterations.toMutableMap().also {
|
||||
it.remove(message.alterationId)
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -19,9 +19,14 @@ class CharacterSheetRepository(
|
|||
val characterSheetPreviewFlow get() = store.previewFlow
|
||||
|
||||
val characterDetailFlow get() = store.detailFlow
|
||||
val tagsFlow get() = store.tagsFlow
|
||||
|
||||
suspend fun updateCharacterPreviews() {
|
||||
store.charactersPreview()
|
||||
store.updateCharactersPreview()
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
store.updateTags()
|
||||
}
|
||||
|
||||
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
|||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
|
|
@ -23,7 +25,7 @@ class CharacterSheetStore(
|
|||
private val client: LwaClient,
|
||||
private val network: NetworkRepository,
|
||||
private val factory: CharacterSheetJsonFactory,
|
||||
private val useCase: CharacterSheetUseCase,
|
||||
private val tagFactory: TagJsonFactory,
|
||||
) {
|
||||
private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
|
||||
val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow
|
||||
|
|
@ -31,11 +33,15 @@ class CharacterSheetStore(
|
|||
private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
|
||||
val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow
|
||||
|
||||
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// initial data loading.
|
||||
scope.launch {
|
||||
charactersPreview()
|
||||
updateCharactersPreview()
|
||||
updateTags()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
|
|
@ -45,7 +51,7 @@ class CharacterSheetStore(
|
|||
|
||||
// region Rest
|
||||
|
||||
suspend fun charactersPreview(): List<CharacterSheetPreview> {
|
||||
suspend fun updateCharactersPreview(): List<CharacterSheetPreview> {
|
||||
val request = try {
|
||||
client.characters()
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -58,6 +64,21 @@ class CharacterSheetStore(
|
|||
return _previewFlow.update(characters)
|
||||
}
|
||||
|
||||
suspend fun updateTags(): Map<String, Tag> {
|
||||
val request = try {
|
||||
client.characterTags()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
emptyList()
|
||||
}
|
||||
val tags = request.map {
|
||||
tagFactory.convertFromJson(json = it)
|
||||
}.associateBy { it.id }
|
||||
|
||||
_tagsFlow.value = tags
|
||||
return tags
|
||||
}
|
||||
|
||||
suspend fun getCharacterSheet(
|
||||
characterSheetId: String,
|
||||
forceUpdate: Boolean = false,
|
||||
|
|
@ -110,7 +131,7 @@ class CharacterSheetStore(
|
|||
forceUpdate = true,
|
||||
)
|
||||
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
|
||||
charactersPreview()
|
||||
updateCharactersPreview()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,6 +147,9 @@ class CharacterSheetStore(
|
|||
sheets.toMutableMap().also { it.remove(message.characterSheetId) }
|
||||
}
|
||||
}
|
||||
|
||||
is ApiSynchronisation.AlterationUpdate -> Unit
|
||||
is ApiSynchronisation.AlterationDelete -> Unit
|
||||
}
|
||||
|
||||
is CharacterSheetEvent -> when (message) {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
data class LwaTextFieldUio(
|
||||
val enable: Boolean = true,
|
||||
val isError: StateFlow<Boolean>,
|
||||
val labelFlow: StateFlow<String?>,
|
||||
val labelFlow: StateFlow<String?>?,
|
||||
val valueFlow: StateFlow<String>,
|
||||
val placeHolderFlow: StateFlow<String?>,
|
||||
val placeHolderFlow: StateFlow<String?>?,
|
||||
val onValueChange: (String) -> Unit,
|
||||
)
|
||||
|
||||
|
|
@ -43,9 +43,9 @@ fun LwaTextField(
|
|||
Modifier
|
||||
}
|
||||
|
||||
val label = field.labelFlow.collectAsState()
|
||||
val label = field.labelFlow?.collectAsState()
|
||||
val placeHolder = field.placeHolderFlow?.collectAsState()
|
||||
val value = field.valueFlow.collectAsState()
|
||||
val placeHolder = field.placeHolderFlow.collectAsState()
|
||||
val isError = field.isError.collectAsState()
|
||||
|
||||
TextField(
|
||||
|
|
@ -56,7 +56,7 @@ fun LwaTextField(
|
|||
},
|
||||
enabled = field.enable,
|
||||
singleLine = singleLine,
|
||||
placeholder = placeHolder.value?.let {
|
||||
placeholder = placeHolder?.value?.let {
|
||||
{
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -66,7 +66,7 @@ fun LwaTextField(
|
|||
}
|
||||
},
|
||||
isError = isError.value,
|
||||
label = label.value?.let {
|
||||
label = label?.value?.let {
|
||||
{
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
|
|
@ -15,8 +16,7 @@ object CharacterSheetEditDestination {
|
|||
|
||||
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
|
||||
|
||||
fun navigationRoute(characterSheetId: String?) = ROUTE +
|
||||
"?$CHARACTER_ID=$characterSheetId"
|
||||
fun navigationRoute(characterSheetId: String?) = "$ROUTE?$CHARACTER_ID=$characterSheetId"
|
||||
|
||||
fun arguments() = listOf(
|
||||
navArgument(CHARACTER_ID) {
|
||||
|
|
@ -25,6 +25,7 @@ object CharacterSheetEditDestination {
|
|||
},
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class Argument(
|
||||
val id: String?,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationPage
|
||||
|
||||
object GMAlterationDestination {
|
||||
private const val ROUTE = "GameMasterAlteration"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.utils.extention.ARG
|
||||
|
||||
@Stable
|
||||
object GMAlterationEditDestination {
|
||||
private const val ROUTE = "GameMasterAlterationEdit"
|
||||
private const val ALTERATION_ID = "id"
|
||||
|
||||
@Stable
|
||||
fun baseRoute() = "$ROUTE?${ALTERATION_ID.ARG}"
|
||||
|
||||
@Stable
|
||||
fun navigationRoute(alterationId: String?) = "$ROUTE?$ALTERATION_ID=$alterationId"
|
||||
|
||||
@Stable
|
||||
fun arguments() = listOf(
|
||||
navArgument(ALTERATION_ID) {
|
||||
nullable = true
|
||||
type = NavType.StringType
|
||||
},
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class Argument(
|
||||
val id: String?,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
id = savedStateHandle.get<String>(ALTERATION_ID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableGameMasterAlterationEditPage() {
|
||||
composable(
|
||||
route = GMAlterationEditDestination.baseRoute(),
|
||||
arguments = GMAlterationEditDestination.arguments(),
|
||||
) {
|
||||
GMAlterationEditPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToGameMasterAlterationEditPage(
|
||||
alterationId: String?,
|
||||
) {
|
||||
val route = GMAlterationEditDestination.navigationRoute(
|
||||
alterationId = alterationId,
|
||||
)
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterPage
|
||||
|
||||
object GMCharacterDestination {
|
||||
private const val ROUTE = "GameMasterCharacter"
|
||||
|
|
|
|||
|
|
@ -20,27 +20,18 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.desktop.lwa.LocalBlurController
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMActionDestination
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterCharacterPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterObjectPage
|
||||
|
|
@ -48,11 +39,8 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.nav
|
|||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterObjectPage
|
||||
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.rememberTransitionAnimation
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTab
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTabUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMTab
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMTabUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -175,6 +163,7 @@ private fun GameMasterContent(
|
|||
composableGameMasterActionPage()
|
||||
composableGameMasterCharacterPage()
|
||||
composableGameMasterAlterationPage()
|
||||
composableGameMasterAlterationEditPage()
|
||||
composableGameMasterObjectPage()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.action
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -6,10 +6,8 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAction
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_expression
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_id
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_id
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_label
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_tags
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import java.util.UUID
|
||||
|
||||
class GMAlterationEditFactory(
|
||||
private val expressionParser: ExpressionParser,
|
||||
) {
|
||||
suspend fun createForm(
|
||||
alteration: Alteration?,
|
||||
): GMAlterationEditPageUio {
|
||||
val id = MutableStateFlow(alteration?.id ?: "")
|
||||
val label = MutableStateFlow(alteration?.metadata?.name ?: "")
|
||||
val description = MutableStateFlow(alteration?.metadata?.description ?: "")
|
||||
val tags = MutableStateFlow(alteration?.tags?.joinToString(", ") { it } ?: "")
|
||||
val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(createField(null)))
|
||||
|
||||
return GMAlterationEditPageUio(
|
||||
id = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)),
|
||||
valueFlow = id,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { id.value = it },
|
||||
),
|
||||
label = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_label)),
|
||||
valueFlow = label,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { label.value = it },
|
||||
),
|
||||
description = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_description)),
|
||||
valueFlow = description,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { description.value = it },
|
||||
),
|
||||
tags = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_tags)),
|
||||
valueFlow = tags,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { tags.value = it },
|
||||
),
|
||||
fields = fields,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun createField(
|
||||
alteration: Alteration.Field?,
|
||||
): GMAlterationEditPageUio.SkillUio {
|
||||
val idFlow = MutableStateFlow(alteration?.fieldId ?: "")
|
||||
val expressionFlow = MutableStateFlow(alteration?.expression?.toString() ?: "")
|
||||
|
||||
return GMAlterationEditPageUio.SkillUio(
|
||||
key = "${UUID.randomUUID()}-${System.currentTimeMillis()}",
|
||||
id = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_field_id)),
|
||||
valueFlow = idFlow,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { idFlow.value = it },
|
||||
),
|
||||
expression = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_field_expression)),
|
||||
valueFlow = expressionFlow,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { expressionFlow.value = it },
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun createAlteration(
|
||||
form: GMAlterationEditPageUio?,
|
||||
): Alteration? {
|
||||
if (form == null) return null
|
||||
|
||||
return Alteration(
|
||||
id = form.id.valueFlow.value,
|
||||
metadata = Alteration.MetaData(
|
||||
name = form.label.valueFlow.value,
|
||||
description = form.description.valueFlow.value,
|
||||
),
|
||||
tags = form.tags.valueFlow.value.split(","),
|
||||
fields = form.fields.value.mapNotNull { field ->
|
||||
expressionParser.parse(input = field.expression.valueFlow.value)?.let {
|
||||
Alteration.Field(
|
||||
fieldId = field.id.valueFlow.value,
|
||||
expression = it
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_add_field
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_cancel
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_save
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_save_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Stable
|
||||
data class GMAlterationEditPageUio(
|
||||
val id: LwaTextFieldUio,
|
||||
val label: LwaTextFieldUio,
|
||||
val description: LwaTextFieldUio,
|
||||
val tags: LwaTextFieldUio,
|
||||
val fields: MutableStateFlow<List<SkillUio>>,
|
||||
) {
|
||||
@Stable
|
||||
data class SkillUio(
|
||||
val key: String,
|
||||
val id: LwaTextFieldUio,
|
||||
val expression: LwaTextFieldUio,
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
object GMAlterationEditPageDefault {
|
||||
val paddings = PaddingValues(all = 8.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GMAlterationEditPage(
|
||||
viewModel: GMAlterationEditViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val form = viewModel.form.collectAsState()
|
||||
|
||||
AnimatedContent(
|
||||
targetState = form.value,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
when (it) {
|
||||
null -> Box(modifier = Modifier.fillMaxSize())
|
||||
else -> GMAlterationEditContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
form = it,
|
||||
paddings = GMAlterationEditPageDefault.paddings,
|
||||
addField = {
|
||||
scope.launch {
|
||||
viewModel.addField()
|
||||
}
|
||||
},
|
||||
removeField = viewModel::removeField,
|
||||
onSave = {
|
||||
scope.launch {
|
||||
viewModel.save()
|
||||
}
|
||||
},
|
||||
onCancel = {
|
||||
screen.popBackStack()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AlterationEditKeyHandler(
|
||||
onDismissRequest = {
|
||||
screen.popBackStack()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GMAlterationEditContent(
|
||||
modifier: Modifier = Modifier,
|
||||
form: GMAlterationEditPageUio,
|
||||
paddings: PaddingValues,
|
||||
addField: () -> Unit,
|
||||
removeField: (index: Int) -> Unit,
|
||||
onSave: () -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
val fields = form.fields.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = paddings,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
item(
|
||||
key = "Id",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.id,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Name",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.label,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Description",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.description,
|
||||
singleLine = false,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Tags",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.tags,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
itemsIndexed(
|
||||
items = fields.value,
|
||||
key = { _, item -> item.key },
|
||||
) { index, item ->
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.id,
|
||||
singleLine = true,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.expression,
|
||||
singleLine = true,
|
||||
)
|
||||
IconButton(
|
||||
onClick = { removeField(index) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item(
|
||||
key = "Actions",
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = addField,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_add_field),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onCancel,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_cancel),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AlterationEditKeyHandler(
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
KeyHandler {
|
||||
when {
|
||||
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {
|
||||
onDismissRequest()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GMAlterationEditViewModel(
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val factory: GMAlterationEditFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
private val argument = GMAlterationEditDestination.Argument(savedStateHandle)
|
||||
|
||||
private val _form = MutableStateFlow<GMAlterationEditPageUio?>(null)
|
||||
val form: StateFlow<GMAlterationEditPageUio?> get() = _form
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
_form.value = factory.createForm(
|
||||
alteration = alterationRepository.alteration(alterationId = argument.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save() {
|
||||
val edited = factory.createAlteration(form = form.value)
|
||||
|
||||
val actual = alterationRepository.alterationFlow.value[edited?.id]
|
||||
|
||||
// TODO if argument.id == null et actual?.id != null on créer et on écrase existant !!!
|
||||
|
||||
if (edited != null)
|
||||
|
||||
alterationRepository.updateAlteration(edited)
|
||||
}
|
||||
|
||||
suspend fun addField() {
|
||||
form.value?.fields?.update { fields ->
|
||||
fields.toMutableList().also {
|
||||
it.add(element = factory.createField(alteration = null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeField(index: Int) {
|
||||
form.value?.fields?.update { fields ->
|
||||
fields.toMutableList().also {
|
||||
it.removeAt(index = index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -26,6 +26,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTag
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__delete
|
||||
|
|
@ -37,7 +39,7 @@ import org.jetbrains.compose.resources.stringResource
|
|||
data class GMAlterationUio(
|
||||
val alterationId: String,
|
||||
val label: String,
|
||||
val tags: List<GMTagItemUio>,
|
||||
val tags: List<GMTagUio>,
|
||||
)
|
||||
|
||||
@Stable
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlterationUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import java.text.Collator
|
||||
|
||||
class GMAlterationFactory {
|
||||
class GMAlterationFactory(
|
||||
private val tagFactory: GMTagFactory,
|
||||
) {
|
||||
|
||||
fun filterAlteration(
|
||||
alterations: Collection<Alteration>,
|
||||
|
|
@ -27,6 +29,7 @@ class GMAlterationFactory {
|
|||
|
||||
fun convertToGMAlterationUio(
|
||||
alterations: List<Alteration>,
|
||||
tags: Map<String, Tag>,
|
||||
selectedTagId: String?,
|
||||
): List<GMAlterationUio> {
|
||||
return alterations
|
||||
|
|
@ -34,28 +37,16 @@ class GMAlterationFactory {
|
|||
GMAlterationUio(
|
||||
alterationId = alteration.id,
|
||||
label = alteration.metadata.name,
|
||||
tags = alteration.tags.map { tag ->
|
||||
GMTagItemUio(
|
||||
id = tag,
|
||||
label = tag,
|
||||
highlight = tag == selectedTagId,
|
||||
)
|
||||
tags = alteration.tags.mapNotNull {
|
||||
tags[it]?.let { tag ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
tag = tag,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.sortedWith(compareBy(Collator.getInstance()) { it.label })
|
||||
}
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
alterationTagIds: List<String>,
|
||||
selectedTagId: String?,
|
||||
): List<GMTagItemUio> {
|
||||
return alterationTagIds.map {
|
||||
GMTagItemUio(
|
||||
id = it,
|
||||
label = it,
|
||||
highlight = it == selectedTagId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -21,17 +21,20 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlteration
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlterationUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__create
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__create_character_sheet
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
|
@ -40,8 +43,11 @@ import org.koin.compose.viewmodel.koinViewModel
|
|||
fun GMAlterationPage(
|
||||
viewModel: GMAlterationViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val alterations = viewModel.alterations.collectAsState()
|
||||
val tags = viewModel.alterationTags.collectAsState()
|
||||
val tags = viewModel.tags.collectAsState()
|
||||
|
||||
Box {
|
||||
GMAlterationContent(
|
||||
|
|
@ -50,9 +56,17 @@ fun GMAlterationPage(
|
|||
tags = tags,
|
||||
alterations = alterations,
|
||||
onTag = viewModel::onTag,
|
||||
onAlterationEdit = { },
|
||||
onAlterationDelete = { },
|
||||
onAlterationCreate = { },
|
||||
onAlterationEdit = {
|
||||
screen.navigateToGameMasterAlterationEditPage(alterationId = it)
|
||||
},
|
||||
onAlterationDelete = {
|
||||
scope.launch {
|
||||
viewModel.deleteAlteration(alterationId = it)
|
||||
}
|
||||
},
|
||||
onAlterationCreate = {
|
||||
screen.navigateToGameMasterAlterationEditPage(alterationId = null)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +77,7 @@ private fun GMAlterationContent(
|
|||
padding: Dp = 8.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
tags: State<List<GMTagUio>>,
|
||||
alterations: State<List<GMAlterationUio>>,
|
||||
onTag: (String) -> Unit,
|
||||
onAlterationEdit: (String) -> Unit,
|
||||
|
|
@ -131,7 +145,7 @@ private fun GMAlterationContent(
|
|||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__create_character_sheet),
|
||||
text = stringResource(Res.string.game_master__alteration__create),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -20,33 +20,29 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character__
|
|||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class GMAlterationViewModel(
|
||||
alterationRepository: AlterationRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
alterationFactory: GMAlterationFactory,
|
||||
tagFactory: GMTagFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _filter = MutableStateFlow("")
|
||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||
private val filterValue = MutableStateFlow("")
|
||||
|
||||
val filter = LwaTextFieldUio(
|
||||
enable = true,
|
||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||
valueFlow = _filter,
|
||||
valueFlow = filterValue,
|
||||
isError = MutableStateFlow(false),
|
||||
placeHolderFlow = MutableStateFlow(null),
|
||||
onValueChange = { _filter.value = it },
|
||||
onValueChange = { filterValue.value = it },
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val alterationTagIds = alterationRepository.alterationFlow
|
||||
.mapLatest { alterations -> alterations.values.flatMap { it.tags }.toSet().toList() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||
|
||||
val alterationTags = combine(
|
||||
alterationTagIds,
|
||||
val tags: StateFlow<List<GMTagUio>> = combine(
|
||||
alterationRepository.tagsFlow,
|
||||
selectedTagId,
|
||||
) { alterationTagIds, selectedTagId ->
|
||||
alterationFactory.convertToGMTagItemUio(
|
||||
alterationTagIds = alterationTagIds,
|
||||
) { tags, selectedTagId ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
tags = tags.values,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}.stateIn(
|
||||
|
|
@ -55,22 +51,20 @@ class GMAlterationViewModel(
|
|||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val alterations = combine(
|
||||
val alterations: StateFlow<List<GMAlterationUio>> = combine(
|
||||
alterationRepository.alterationFlow,
|
||||
alterationRepository.tagsFlow,
|
||||
filter.valueFlow.map { it.unAccent() },
|
||||
selectedTagId,
|
||||
transform = { alterations, unAccentFilter, selectedTagId ->
|
||||
alterationFactory.filterAlteration(
|
||||
) { alterations, tags, unAccentFilter, selectedTagId ->
|
||||
alterationFactory.convertToGMAlterationUio(
|
||||
alterations = alterationFactory.filterAlteration(
|
||||
alterations = alterations.values,
|
||||
unAccentFilter = unAccentFilter,
|
||||
selectedTagId = selectedTagId
|
||||
)
|
||||
}
|
||||
).mapLatest { alterations ->
|
||||
alterationFactory.convertToGMAlterationUio(
|
||||
alterations = alterations,
|
||||
selectedTagId = selectedTagId.value
|
||||
selectedTagId = selectedTagId,
|
||||
),
|
||||
tags = tags,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
|
|
@ -86,4 +80,8 @@ class GMAlterationViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteAlteration(alterationId: String) {
|
||||
alterationRepository.deleteAlteration(alterationId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class GMCharacterFactory {
|
||||
|
||||
companion object {
|
||||
const val PLAYER_ID = "PLAYER"
|
||||
const val NPC_ID = "NPC"
|
||||
}
|
||||
|
||||
suspend fun convertToGMCharacterPreviewUio(
|
||||
campaign: Campaign,
|
||||
characters: List<CharacterSheetPreview>,
|
||||
filter: String,
|
||||
tags: Map<String, Boolean>,
|
||||
): List<GMCharacterItemUio> {
|
||||
val normalizedFilter = filter.unAccent()
|
||||
|
||||
return characters.mapNotNull {
|
||||
convertToGMCharacterPreviewUio(
|
||||
campaign = campaign,
|
||||
character = it,
|
||||
filter = normalizedFilter,
|
||||
tags = tags,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun convertToGMCharacterPreviewUio(
|
||||
campaign: Campaign,
|
||||
character: CharacterSheetPreview,
|
||||
filter: String,
|
||||
tags: Map<String, Boolean>,
|
||||
): GMCharacterItemUio? {
|
||||
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
||||
val isPlayer = campaign.characters.firstOrNull {
|
||||
it == character.characterSheetId
|
||||
} != null
|
||||
// get all characterInstanceId from the npcs list corresponding to this CharacterSheet if any
|
||||
val isNpc = campaign.npcs.firstOrNull {
|
||||
it == character.characterSheetId
|
||||
} != null
|
||||
// Filter process : Name.
|
||||
if (filter.isNotEmpty()) {
|
||||
val normalizedName = character.name.unAccent()
|
||||
// If the filter is not empty and the character is not
|
||||
val nameHighlight = normalizedName.contains(other = filter, ignoreCase = true)
|
||||
if (nameHighlight.not()) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
// Tag filter process : Player.
|
||||
if (tags[PLAYER_ID] == true && isPlayer.not()) {
|
||||
return null
|
||||
}
|
||||
// Tag filter process : Npc.
|
||||
if (tags[NPC_ID] == true && isNpc.not()) {
|
||||
return null
|
||||
}
|
||||
// Build the call tag list.
|
||||
val previewTagsList = buildList {
|
||||
if (isPlayer) {
|
||||
add(
|
||||
GMTagItemUio(
|
||||
id = PLAYER_ID,
|
||||
label = getString(Res.string.game_master__character_tag__character),
|
||||
highlight = tags[PLAYER_ID] ?: false,
|
||||
)
|
||||
)
|
||||
}
|
||||
if (isNpc) {
|
||||
add(
|
||||
GMTagItemUio(
|
||||
id = NPC_ID,
|
||||
label = getString(Res.string.game_master__character_tag__npc),
|
||||
highlight = tags[NPC_ID] ?: false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
// build the cell action list
|
||||
val actions = buildList {
|
||||
add(Action.DisplayPortrait)
|
||||
when {
|
||||
isPlayer -> add(Action.RemoveFromGroup)
|
||||
isNpc -> add(Action.RemoveFromNpc)
|
||||
else -> {
|
||||
add(Action.AddToGroup)
|
||||
add(Action.AddToNpc)
|
||||
}
|
||||
}
|
||||
}
|
||||
// return the cell UIO.
|
||||
return GMCharacterItemUio(
|
||||
characterSheetId = character.characterSheetId,
|
||||
name = character.name,
|
||||
level = character.level,
|
||||
tags = previewTagsList,
|
||||
actions = actions,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun convertToGMTagItemUio(
|
||||
id: String?,
|
||||
highlight: Boolean,
|
||||
): GMTagItemUio? {
|
||||
return when (id) {
|
||||
PLAYER_ID -> GMTagItemUio(
|
||||
id = id,
|
||||
label = getString(Res.string.game_master__character_tag__character),
|
||||
highlight = highlight,
|
||||
)
|
||||
|
||||
NPC_ID -> GMTagItemUio(
|
||||
id = id,
|
||||
label = getString(Res.string.game_master__character_tag__npc),
|
||||
highlight = highlight,
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.PointerMatcher
|
||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.onClick
|
||||
import androidx.compose.material.DropdownMenu
|
||||
|
|
@ -28,9 +27,10 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.pointer.PointerButton
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTag
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__add_to_group
|
||||
|
|
@ -54,7 +54,7 @@ data class GMCharacterItemUio(
|
|||
val characterSheetId: String,
|
||||
val name: String,
|
||||
val level: Int,
|
||||
val tags: List<GMTagItemUio>,
|
||||
val tags: List<GMTagUio>,
|
||||
val actions: List<Action>,
|
||||
) {
|
||||
@Stable
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class GMCharacterFactory {
|
||||
|
||||
companion object {
|
||||
const val PLAYER_ID = "PLAYER"
|
||||
const val NPC_ID = "NPC"
|
||||
}
|
||||
|
||||
fun filterCharacter(
|
||||
campaign: Campaign,
|
||||
characters: List<CharacterSheetPreview>,
|
||||
unAccentFilter: String,
|
||||
selectedTagId: String?,
|
||||
): List<CharacterSheetPreview> {
|
||||
return characters.filter {
|
||||
val matchName = it.name.unAccent().contains(
|
||||
other = unAccentFilter,
|
||||
ignoreCase = true,
|
||||
)
|
||||
val matchTag = when (selectedTagId) {
|
||||
null -> true
|
||||
PLAYER_ID -> campaign.characters.contains(it.characterSheetId)
|
||||
NPC_ID -> campaign.npcs.contains(it.characterSheetId)
|
||||
else -> false
|
||||
}
|
||||
matchName && matchTag
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun convertToGMCharacterPreviewUio(
|
||||
campaign: Campaign,
|
||||
characters: List<CharacterSheetPreview>,
|
||||
tagIdMap: List<GMTagUio>,
|
||||
): List<GMCharacterItemUio> {
|
||||
return characters.map {
|
||||
convertToGMCharacterPreviewUio(
|
||||
campaign = campaign,
|
||||
character = it,
|
||||
tagIdMap = tagIdMap,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun convertToGMCharacterPreviewUio(
|
||||
campaign: Campaign,
|
||||
character: CharacterSheetPreview,
|
||||
tagIdMap: List<GMTagUio>,
|
||||
): GMCharacterItemUio {
|
||||
val isPlayer = campaign.characters.contains(character.characterSheetId)
|
||||
val isNpc = campaign.npcs.contains(character.characterSheetId)
|
||||
|
||||
// Build the call tag list.
|
||||
val tags = tagIdMap.filter {
|
||||
when (it.id) {
|
||||
PLAYER_ID -> isPlayer
|
||||
NPC_ID -> isNpc
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
// build the cell action list
|
||||
val actions = buildList {
|
||||
add(Action.DisplayPortrait)
|
||||
when {
|
||||
isPlayer -> add(Action.RemoveFromGroup)
|
||||
isNpc -> add(Action.RemoveFromNpc)
|
||||
else -> {
|
||||
add(Action.AddToGroup)
|
||||
add(Action.AddToNpc)
|
||||
}
|
||||
}
|
||||
}
|
||||
// return the cell UIO.
|
||||
return GMCharacterItemUio(
|
||||
characterSheetId = character.characterSheetId,
|
||||
name = character.name,
|
||||
level = character.level,
|
||||
tags = tags,
|
||||
actions = actions,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun convertToGMTagItemUio(
|
||||
id: String?,
|
||||
highlight: Boolean,
|
||||
): GMTagUio? {
|
||||
return when (id) {
|
||||
PLAYER_ID -> GMTagUio(
|
||||
id = id,
|
||||
label = getString(Res.string.game_master__character_tag__character),
|
||||
highlight = highlight,
|
||||
)
|
||||
|
||||
NPC_ID -> GMTagUio(
|
||||
id = id,
|
||||
label = getString(Res.string.game_master__character_tag__npc),
|
||||
highlight = highlight,
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -44,10 +44,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetai
|
|||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacter
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -163,7 +161,7 @@ fun GMCharacterContent(
|
|||
padding: Dp = 8.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
tags: State<List<GMTagUio>>,
|
||||
characters: State<List<GMCharacterItemUio>>,
|
||||
onTag: (String) -> Unit,
|
||||
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -6,20 +6,20 @@ 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.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character__filter
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class GMCharacterViewModel(
|
||||
|
|
@ -27,41 +27,53 @@ class GMCharacterViewModel(
|
|||
private val campaignRepository: CampaignRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
private val factory: GMCharacterFactory,
|
||||
private val tagFactory: GMTagFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _filter = MutableStateFlow("")
|
||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||
private val filterValue = MutableStateFlow("")
|
||||
|
||||
val filter = LwaTextFieldUio(
|
||||
enable = true,
|
||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||
valueFlow = _filter,
|
||||
valueFlow = filterValue,
|
||||
isError = MutableStateFlow(false),
|
||||
placeHolderFlow = MutableStateFlow(null),
|
||||
onValueChange = { _filter.value = it },
|
||||
onValueChange = { filterValue.value = it },
|
||||
)
|
||||
|
||||
private val _tags = MutableStateFlow(
|
||||
mapOf(
|
||||
GMCharacterFactory.PLAYER_ID to false,
|
||||
GMCharacterFactory.NPC_ID to false
|
||||
val tags = combine(
|
||||
characterSheetRepository.tagsFlow,
|
||||
selectedTagId,
|
||||
) { tags, selectedTagId ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
tags = tags.values,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
)
|
||||
val tags = _tags.map { it: Map<String, Boolean> ->
|
||||
it.mapNotNull { (id, highlight) ->
|
||||
factory.convertToGMTagItemUio(id = id, highlight = highlight)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
val characters = combine(
|
||||
campaignRepository.campaignFlow,
|
||||
characterSheetRepository.characterSheetPreviewFlow,
|
||||
filter.valueFlow,
|
||||
_tags,
|
||||
factory::convertToGMCharacterPreviewUio,
|
||||
).stateIn(
|
||||
filter.valueFlow.map { it.unAccent() },
|
||||
tags,
|
||||
selectedTagId,
|
||||
) { campaign, characters, unAccentFilter, tagIdMap, selectedTagId ->
|
||||
factory.convertToGMCharacterPreviewUio(
|
||||
characters = factory.filterCharacter(
|
||||
campaign = campaign,
|
||||
characters = characters,
|
||||
unAccentFilter = unAccentFilter,
|
||||
selectedTagId = selectedTagId,
|
||||
),
|
||||
campaign = campaign,
|
||||
tagIdMap = tagIdMap,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
|
|
@ -106,8 +118,11 @@ class GMCharacterViewModel(
|
|||
fun onTag(
|
||||
id: String,
|
||||
) {
|
||||
_tags.value = _tags.value.toMutableMap().also {
|
||||
it[id] = it.getOrPut(id) { true }.not()
|
||||
selectedTagId.update {
|
||||
when (it) {
|
||||
id -> null
|
||||
else -> id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -28,6 +28,8 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTag
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -41,7 +43,7 @@ fun GMFilterHeader(
|
|||
padding: Dp = 16.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
tags: State<List<GMTagUio>>,
|
||||
onTag: (String) -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -54,7 +56,6 @@ fun GMFilterHeader(
|
|||
field = filter,
|
||||
trailingIcon = {
|
||||
val value = filter.valueFlow.collectAsState()
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
|
|
@ -72,28 +73,37 @@ fun GMFilterHeader(
|
|||
}
|
||||
}
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
lazyListState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = lazyListState,
|
||||
contentPadding = remember(padding, spacing) {
|
||||
PaddingValues(horizontal = padding, vertical = spacing)
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
AnimatedVisibility(
|
||||
visible = tags.value.isNotEmpty(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
items(
|
||||
items = tags.value,
|
||||
) { tag ->
|
||||
GMTag(
|
||||
tag = tag,
|
||||
onTag = { onTag(tag.id) },
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
lazyListState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = lazyListState,
|
||||
contentPadding = remember(padding, spacing) {
|
||||
PaddingValues(
|
||||
horizontal = padding,
|
||||
vertical = spacing,
|
||||
)
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
items(
|
||||
items = tags.value,
|
||||
) { tag ->
|
||||
GMTag(
|
||||
tag = tag,
|
||||
onTag = { onTag(tag.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -19,7 +19,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
|||
|
||||
|
||||
@Stable
|
||||
data class GMTagItemUio(
|
||||
data class GMTagUio(
|
||||
val id: String,
|
||||
val label: String,
|
||||
val highlight: Boolean,
|
||||
|
|
@ -36,7 +36,7 @@ fun GMTag(
|
|||
padding: PaddingValues = GmTagDefault.padding,
|
||||
shape: Shape = CircleShape,
|
||||
elevation: Dp = 2.dp,
|
||||
tag: GMTagItemUio,
|
||||
tag: GMTagUio,
|
||||
onTag: (() -> Unit)? = null,
|
||||
) {
|
||||
val animatedColor = animateColorAsState(
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag
|
||||
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
|
||||
class GMTagFactory {
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
tags: Collection<Tag>,
|
||||
selectedTagId: String?,
|
||||
): List<GMTagUio> {
|
||||
return tags.map { tag ->
|
||||
convertToGMTagItemUio(
|
||||
tag = tag,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
tag: Tag,
|
||||
selectedTagId: String?,
|
||||
): GMTagUio {
|
||||
return GMTagUio(
|
||||
id = tag.id,
|
||||
label = tag.label,
|
||||
highlight = tag.id == selectedTagId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.business
|
|||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
|
||||
import org.junit.Test
|
||||
|
||||
class SkillNormalizerUseCaseText {
|
||||
class SkillUioNormalizerUseCaseText {
|
||||
|
||||
@Test
|
||||
fun testNormalization() {
|
||||
|
|
@ -4,7 +4,7 @@ import com.pixelized.shared.lwa.usecase.SkillStepUseCase
|
|||
import com.pixelized.shared.lwa.usecase.SkillStepUseCase.SkillStep
|
||||
import org.junit.Test
|
||||
|
||||
class SkillStepUseCaseTest {
|
||||
class SkillUioStepUseCaseTest {
|
||||
|
||||
@Test
|
||||
fun testStepForSkill() {
|
||||
Loading…
Add table
Add a link
Reference in a new issue