Skip to content

Commit

Permalink
Merge pull request #143 from fmasa/user-assign
Browse files Browse the repository at this point in the history
PC linking + turning PCs to NPCs and vice versa
  • Loading branch information
fmasa authored Aug 2, 2023
2 parents e526d4e + e2697ba commit 2711985
Show file tree
Hide file tree
Showing 16 changed files with 468 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,43 @@ data class CharacterDetailScreen(
rememberScreenModel(arg = party.id)
val userId = LocalUser.current.id
val canAddCharacters = !isGameMaster
val navigation = LocalNavigationTransaction.current

val allCharacters = remember {
if (isGameMaster)
screenModel.allCharacters
else characterPickerScreenModel.allUserCharacters(userId)
}.collectWithLifecycle(null).value

if (allCharacters != null && (allCharacters.isNotEmpty() || canAddCharacters)) {
val unassignedCharacters = characterPickerScreenModel.unassignedPlayerCharacters
.collectWithLifecycle(null).value

if (
allCharacters != null &&
unassignedCharacters != null &&
(allCharacters.isNotEmpty() || canAddCharacters)
) {
var unassignedCharactersDialogOpened by remember { mutableStateOf(false) }

if (unassignedCharactersDialogOpened) {
UnassignedCharacterPickerDialog(
partyId = party.id,
unassignedCharacters = unassignedCharacters,
screenModel = characterPickerScreenModel,
onDismissRequest = { unassignedCharactersDialogOpened = false },
onAssigned = {
navigation.replace(
CharacterDetailScreen(
characterId = it,
comingFromCombat = comingFromCombat,
initialTab = currentTab ?: CharacterTab.values().first(),
)
)
unassignedCharactersDialogOpened = false
}
)
}

var dropdownOpened by remember { mutableStateOf(false) }

Row(
Expand All @@ -178,8 +207,6 @@ data class CharacterDetailScreen(
dropdownOpened,
onDismissRequest = { dropdownOpened = false }
) {
val navigation = LocalNavigationTransaction.current

allCharacters.forEach { otherCharacter ->
key(otherCharacter.id) {
DropdownMenuItem(
Expand All @@ -204,6 +231,18 @@ data class CharacterDetailScreen(
}

if (canAddCharacters) {
if (unassignedCharacters.isNotEmpty()) {
DropdownMenuItem(
onClick = {
dropdownOpened = false
unassignedCharactersDialogOpened = true
},
) {
Icon(Icons.Rounded.Add, null, modifier = Modifier.size(24.dp))
Text(LocalStrings.current.character.buttonLink)
}
}

DropdownMenuItem(
onClick = {
navigation.navigate(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
package cz.frantisekmasa.wfrp_master.common.character

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import cz.frantisekmasa.wfrp_master.common.characterCreation.CharacterCreationScreen
import cz.frantisekmasa.wfrp_master.common.core.auth.LocalUser
import cz.frantisekmasa.wfrp_master.common.core.domain.character.Character
import cz.frantisekmasa.wfrp_master.common.core.domain.character.CharacterType
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyId
import cz.frantisekmasa.wfrp_master.common.core.shared.Resources
import cz.frantisekmasa.wfrp_master.common.core.ui.CharacterAvatar
import cz.frantisekmasa.wfrp_master.common.core.ui.buttons.BackButton
import cz.frantisekmasa.wfrp_master.common.core.ui.cards.CardItem
import cz.frantisekmasa.wfrp_master.common.core.ui.flow.collectWithLifecycle
import cz.frantisekmasa.wfrp_master.common.core.ui.navigation.LocalNavigationTransaction
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.EmptyUI
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.FullScreenProgress
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.ItemIcon
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.Spacing
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.rememberScreenModel
import cz.frantisekmasa.wfrp_master.common.localization.LocalStrings

Expand All @@ -44,19 +58,6 @@ data class CharacterPickerScreen(

val navigation = LocalNavigationTransaction.current

LaunchedEffect(characters) {
when {
characters.isEmpty() -> navigation.replace(
CharacterCreationScreen(partyId, CharacterType.PLAYER_CHARACTER, userId)
)
characters.size == 1 -> navigation.replace(
CharacterDetailScreen(
CharacterId(partyId, characters.first().id)
)
)
}
}

Scaffold(
topBar = {
TopAppBar(
Expand All @@ -65,21 +66,108 @@ data class CharacterPickerScreen(
)
}
) {
Card {
LazyColumn {
items(characters, key = { it.id }) { character ->
CardItem(
name = character.name,
icon = { CharacterAvatar(character.avatarUrl, ItemIcon.Size.Small) },
onClick = {
navigation.replace(
CharacterDetailScreen(
CharacterId(partyId, character.id)
)
when {
characters.size == 1 -> {
LaunchedEffect(Unit) {
navigation.replace(
CharacterDetailScreen(
CharacterId(partyId, characters.first().id)
)
)
}
}

characters.isNotEmpty() -> {
CharacterPicker(characters)
}

else -> {
NoCharacters(screenModel)
}
}
}
}

@Composable
private fun CharacterPicker(characters: List<Character>) {
Card {
val navigation = LocalNavigationTransaction.current

LazyColumn {
items(characters, key = { it.id }) { character ->
CardItem(
name = character.name,
icon = { CharacterAvatar(character.avatarUrl, ItemIcon.Size.Small) },
onClick = {
navigation.replace(
CharacterDetailScreen(
CharacterId(partyId, character.id)
)
}
)
}
)
}
}
}
}

@Composable
private fun NoCharacters(screenModel: CharacterPickerScreenModel) {
val unassignedCharacters =
screenModel.unassignedPlayerCharacters.collectWithLifecycle(null).value

if (unassignedCharacters == null) {
FullScreenProgress()
return
}

val navigation = LocalNavigationTransaction.current
var unassignedCharactersDialogVisible by remember { mutableStateOf(false) }

if (unassignedCharactersDialogVisible) {
UnassignedCharacterPickerDialog(
partyId = partyId,
unassignedCharacters = unassignedCharacters,
screenModel = screenModel,
onDismissRequest = { unassignedCharactersDialogVisible = false },
onAssigned = {
navigation.replace(CharacterDetailScreen(it))
}
)
}

Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(Spacing.bodyPadding)
) {
EmptyUI(
text = LocalStrings.current.character.messages.noCharacterInParty,
icon = Resources.Drawable.Character,
subText = if (unassignedCharacters.isNotEmpty())
LocalStrings.current.character.messages.unassignedCharactersExist
else null,
)

if (unassignedCharacters.isNotEmpty()) {
OutlinedButton(onClick = { unassignedCharactersDialogVisible = true }) {
Text(LocalStrings.current.character.buttonLink.uppercase())
}

val userId = LocalUser.current.id
Button(
onClick = {
navigation.replace(
CharacterCreationScreen(
partyId,
CharacterType.PLAYER_CHARACTER,
userId = userId,
)
)
}
) {
Text(LocalStrings.current.character.buttonAdd.uppercase())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ import cz.frantisekmasa.wfrp_master.common.core.domain.character.CharacterReposi
import cz.frantisekmasa.wfrp_master.common.core.domain.character.CharacterType
import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map

class CharacterPickerScreenModel(
private val partyId: PartyId,
private val characters: CharacterRepository,
) : ScreenModel {

private val playerCharacters = characters.inParty(partyId, CharacterType.PLAYER_CHARACTER)

val unassignedPlayerCharacters = playerCharacters.map {
it.filter { character -> character.userId == null }
}

suspend fun assignCharacter(character: Character, userId: UserId) {
characters.save(partyId, character.assignToUser(userId))
}

fun allUserCharacters(userId: UserId): Flow<List<Character>> {
return characters.inParty(partyId, CharacterType.PLAYER_CHARACTER).map {
return playerCharacters.map {
it.filter { character ->
character.userId == userId || character.id == userId.toString()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyRepository
import cz.frantisekmasa.wfrp_master.common.core.utils.right
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map

class CharacterScreenModel(
private val characterId: CharacterId,
Expand All @@ -31,6 +33,10 @@ class CharacterScreenModel(

private val party = partyRepository.getLive(characterId.partyId).right()

val isGameMaster = party
.map { it.gameMasterId == userProvider.userId }
.distinctUntilChanged()

val allCareers: Flow<List<Career>> = careerCompendium.liveForParty(characterId.partyId)
.combine(party) { careers, party ->
if (userProvider.userId == party.gameMasterId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cz.frantisekmasa.wfrp_master.common.character

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.AlertDialog
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.ListItem
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import cz.frantisekmasa.wfrp_master.common.core.auth.LocalUser
import cz.frantisekmasa.wfrp_master.common.core.domain.character.Character
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyId
import cz.frantisekmasa.wfrp_master.common.core.ui.CharacterAvatar
import cz.frantisekmasa.wfrp_master.common.core.ui.dialogs.DialogTitle
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.ItemIcon
import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.Spacing
import cz.frantisekmasa.wfrp_master.common.core.utils.launchLogged
import cz.frantisekmasa.wfrp_master.common.localization.LocalStrings
import kotlinx.coroutines.Dispatchers

@Composable
fun UnassignedCharacterPickerDialog(
partyId: PartyId,
unassignedCharacters: List<Character>,
screenModel: CharacterPickerScreenModel,
onDismissRequest: () -> Unit,
onAssigned: (CharacterId) -> Unit,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
buttons = {},
text = {
val coroutineScope = rememberCoroutineScope()
val (saving, setSaving) = remember { mutableStateOf(false) }

if (saving) {
LinearProgressIndicator(Modifier.fillMaxWidth())
return@AlertDialog
}

Column {
DialogTitle(LocalStrings.current.character.titleSelectCharacter)

Spacer(Modifier.height(Spacing.large))

LazyColumn {
items(unassignedCharacters, key = { it.id }) { character ->
val userId = LocalUser.current.id

ListItem(
modifier = Modifier
.fillMaxWidth()
.clickable {
coroutineScope.launchLogged(Dispatchers.IO) {
setSaving(true)
screenModel.assignCharacter(character, userId)
onAssigned(CharacterId(partyId, character.id))
}
},
icon = { CharacterAvatar(character.avatarUrl, ItemIcon.Size.Small) },
text = { Text(character.name) },
)
}
}
}
}
)
}
Loading

0 comments on commit 2711985

Please sign in to comment.