Small UI adjustment over some screens.

This commit is contained in:
Thomas Andres Gomez 2023-09-19 10:16:15 +02:00
parent c8d6bfe4ef
commit 2e4c87c052
4 changed files with 194 additions and 168 deletions

View file

@ -22,11 +22,13 @@ class CharacterSheetParser @Inject constructor() {
// prepare a hash on the structure & function to get index in it. // prepare a hash on the structure & function to get index in it.
lateinit var structure: Map<String, Int> lateinit var structure: Map<String, Int>
fun List<*>.parseInt(key: String): Int =
(getOrNull(structure.getValue(key)) as? String)?.toIntOrNull() ?: 0
// declare helper method to parse String
fun List<*>.parseString(key: String): String? = fun List<*>.parseString(key: String): String? =
(getOrNull(structure.getValue(key)) as? String) (getOrNull(structure.getValue(key)) as? String)?.takeIf { it.isNotEmpty() }
// declare helper method to parse Int
fun List<*>.parseInt(key: String): Int? = parseString(key = key)?.toIntOrNull()
// parse the sheet // parse the sheet
return sheet?.mapIndexedNotNull { index, item -> return sheet?.mapIndexedNotNull { index, item ->
@ -43,39 +45,39 @@ class CharacterSheetParser @Inject constructor() {
name = name, name = name,
hitPoint = item.parseString(HIT_POINT) ?: "1", hitPoint = item.parseString(HIT_POINT) ?: "1",
maxHitPoint = item.parseString(MAX_HIT_POINT) ?: "1", maxHitPoint = item.parseString(MAX_HIT_POINT) ?: "1",
armorClass = item.parseString(ARMOR_CLASS) ?: "0", armorClass = item.parseString(ARMOR_CLASS) ?: "10",
speed = item.parseInt(SPEED), speed = item.parseInt(SPEED) ?: 10,
proficiency = item.parseInt(MASTERY), proficiency = item.parseInt(MASTERY) ?: 2,
strength = item.parseInt(STRENGTH), strength = item.parseInt(STRENGTH) ?: 10,
dexterity = item.parseInt(DEXTERITY), dexterity = item.parseInt(DEXTERITY) ?: 10,
constitution = item.parseInt(CONSTITUTION), constitution = item.parseInt(CONSTITUTION) ?: 10,
intelligence = item.parseInt(INTELLIGENCE), intelligence = item.parseInt(INTELLIGENCE) ?: 10,
wisdom = item.parseInt(WISDOM), wisdom = item.parseInt(WISDOM) ?: 10,
charisma = item.parseInt(CHARISMA), charisma = item.parseInt(CHARISMA) ?: 10,
strengthSavingThrows = item.parseInt(STRENGTH_SAVING_THROW), strengthSavingThrows = item.parseInt(STRENGTH_SAVING_THROW) ?: 0,
dexteritySavingThrows = item.parseInt(DEXTERITY_SAVING_THROW), dexteritySavingThrows = item.parseInt(DEXTERITY_SAVING_THROW) ?: 0,
constitutionSavingThrows = item.parseInt(CONSTITUTION_SAVING_THROW), constitutionSavingThrows = item.parseInt(CONSTITUTION_SAVING_THROW) ?: 0,
intelligenceSavingThrows = item.parseInt(INTELLIGENCE_SAVING_THROW), intelligenceSavingThrows = item.parseInt(INTELLIGENCE_SAVING_THROW) ?: 0,
wisdomSavingThrows = item.parseInt(WISDOM_SAVING_THROW), wisdomSavingThrows = item.parseInt(WISDOM_SAVING_THROW) ?: 0,
charismaSavingThrows = item.parseInt(CHARISMA_SAVING_THROW), charismaSavingThrows = item.parseInt(CHARISMA_SAVING_THROW) ?: 0,
acrobatics = item.parseInt(ACROBATICS), acrobatics = item.parseInt(ACROBATICS) ?: 0,
animalHandling = item.parseInt(ANIMAL_HANDLING), animalHandling = item.parseInt(ANIMAL_HANDLING) ?: 0,
arcana = item.parseInt(ARCANA), arcana = item.parseInt(ARCANA) ?: 0,
athletics = item.parseInt(ATHLETICS), athletics = item.parseInt(ATHLETICS) ?: 0,
deception = item.parseInt(DECEPTION), deception = item.parseInt(DECEPTION) ?: 0,
history = item.parseInt(HISTORY), history = item.parseInt(HISTORY) ?: 0,
insight = item.parseInt(INSIGHT), insight = item.parseInt(INSIGHT) ?: 0,
intimidation = item.parseInt(INTIMIDATION), intimidation = item.parseInt(INTIMIDATION) ?: 0,
investigation = item.parseInt(INVESTIGATION), investigation = item.parseInt(INVESTIGATION) ?: 0,
medicine = item.parseInt(MEDICINE), medicine = item.parseInt(MEDICINE) ?: 0,
nature = item.parseInt(NATURE), nature = item.parseInt(NATURE) ?: 0,
perception = item.parseInt(PERCEPTION), perception = item.parseInt(PERCEPTION) ?: 0,
performance = item.parseInt(PERFORMANCE), performance = item.parseInt(PERFORMANCE) ?: 0,
persuasion = item.parseInt(PERSUASION), persuasion = item.parseInt(PERSUASION) ?: 0,
religion = item.parseInt(RELIGION), religion = item.parseInt(RELIGION) ?: 0,
sleightOfHand = item.parseInt(SLEIGHT_OF_HAND), sleightOfHand = item.parseInt(SLEIGHT_OF_HAND) ?: 0,
stealth = item.parseInt(STEALTH), stealth = item.parseInt(STEALTH) ?: 0,
survival = item.parseInt(SURVIVAL), survival = item.parseInt(SURVIVAL) ?: 0,
) )
} else { } else {
null null

View file

@ -0,0 +1,66 @@
package com.pixelized.rplexicon.ui.screens.character
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.screens.character.composable.Action
import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio
import com.pixelized.rplexicon.ui.screens.character.composable.Counter
import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
@Composable
fun ActionsPages(
modifier: Modifier = Modifier,
actions: State<List<ActionsUio>>,
counter: State<List<CounterUio>>,
alterations: State<List<String>>,
onHit: (id: String) -> Unit,
onDamage: (id: String) -> Unit,
) {
Column(
modifier = modifier,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
counter.value.forEach {
Counter(
counter = it,
)
}
actions.value.forEach {
Action(
action = it,
onHit = onHit,
onDamage = onDamage,
)
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.ddBorder(
inner = remember { RoundedCornerShape(size = 8.dp) },
outline = remember { CutCornerShape(size = 16.dp) },
)
.padding(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
alterations.value.forEach {
Text(
text = it
)
}
}
}
}

View file

@ -2,19 +2,21 @@ package com.pixelized.rplexicon.ui.screens.character
import android.content.res.Configuration import android.content.res.Configuration
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -25,7 +27,6 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -40,13 +41,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@ -55,16 +51,13 @@ import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.Loader import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.screens.character.composable.Action
import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio
import com.pixelized.rplexicon.ui.screens.character.composable.Counter
import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio
import com.pixelized.rplexicon.ui.screens.character.composable.LabelPoint import com.pixelized.rplexicon.ui.screens.character.composable.LabelPoint
import com.pixelized.rplexicon.ui.screens.character.composable.LabelPointUio import com.pixelized.rplexicon.ui.screens.character.composable.LabelPointUio
import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio
import com.pixelized.rplexicon.ui.screens.character.composable.StatUio import com.pixelized.rplexicon.ui.screens.character.composable.StatUio
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Stable @Stable
@ -78,7 +71,7 @@ data class CharacterSheetUio(
val proficiencies: List<ProficiencyUio>, val proficiencies: List<ProficiencyUio>,
) )
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun CharacterSheetScreen( fun CharacterSheetScreen(
viewModel: CharacterSheetViewModel = hiltViewModel(), viewModel: CharacterSheetViewModel = hiltViewModel(),
@ -92,7 +85,7 @@ fun CharacterSheetScreen(
onRefresh = { scope.launch { viewModel.update() } }, onRefresh = { scope.launch { viewModel.update() } },
) )
Box( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { ) {
viewModel.sheet.value?.let { viewModel.sheet.value?.let {
@ -101,8 +94,15 @@ fun CharacterSheetScreen(
.fillMaxSize() .fillMaxSize()
.systemBarsPadding(), .systemBarsPadding(),
state = rememberScrollState(), state = rememberScrollState(),
refreshState = refresh, pagerState = rememberPagerState {
if (viewModel.actions.value.isNotEmpty() || viewModel.counter.value.isNotEmpty()) {
2
} else {
1
}
},
refreshing = viewModel.isLoading, refreshing = viewModel.isLoading,
refreshState = refresh,
onRefresh = { scope.launch { viewModel.update() } }, onRefresh = { scope.launch { viewModel.update() } },
sheet = it, sheet = it,
actions = viewModel.actions, actions = viewModel.actions,
@ -177,11 +177,16 @@ fun CharacterSheetScreen(
} }
} }
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(
ExperimentalMaterial3Api::class,
ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class,
)
@Composable @Composable
private fun CharacterSheetContent( private fun CharacterSheetContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: ScrollState, state: ScrollState,
pagerState: PagerState,
refreshState: PullRefreshState, refreshState: PullRefreshState,
refreshing: State<Boolean>, refreshing: State<Boolean>,
onRefresh: () -> Unit, onRefresh: () -> Unit,
@ -202,7 +207,6 @@ private fun CharacterSheetContent(
contentWindowInsets = NO_WINDOW_INSETS, contentWindowInsets = NO_WINDOW_INSETS,
topBar = { topBar = {
TopAppBar( TopAppBar(
modifier = Modifier.shadow(elevation = 4.dp),
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
@ -228,110 +232,55 @@ private fun CharacterSheetContent(
}, },
) { paddingValues -> ) { paddingValues ->
Surface( Surface(
modifier = Modifier modifier = Modifier.padding(paddingValues = paddingValues),
.pullRefresh(refreshState)
.verticalScroll(state = state)
.padding(paddingValues = paddingValues),
) { ) {
Column( Column {
verticalArrangement = Arrangement.spacedBy(16.dp), Surface(
) { modifier = Modifier.shadow(elevation = 4.dp),
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
alignment = Alignment.CenterHorizontally
),
) { ) {
LabelPoint(label = sheet.armorClass)
LabelPoint(label = sheet.hitPoint)
LabelPoint(label = sheet.speed)
}
ProficiencyPage(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
sheet = sheet,
onInitiative = onInitiative,
onStats = onStats,
onProficiencies = onProficiencies,
)
if (actions.value.isNotEmpty() || counter.value.isNotEmpty()) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp), .padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
alignment = Alignment.CenterHorizontally
),
) { ) {
Image( LabelPoint(label = sheet.armorClass)
modifier = Modifier.graphicsLayer { rotationY = 180f }, LabelPoint(label = sheet.hitPoint)
painter = painterResource(id = R.drawable.art_clip_1), LabelPoint(label = sheet.speed)
contentScale = ContentScale.FillWidth,
alignment = Alignment.Center,
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
contentDescription = null,
)
Text(
modifier = Modifier
.weight(weight = 1f, fill = false)
.padding(horizontal = 8.dp),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(R.string.character_sheet_title_actions),
)
Image(
painter = painterResource(id = R.drawable.art_clip_1),
contentScale = ContentScale.FillWidth,
alignment = Alignment.Center,
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
contentDescription = null,
)
}
Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
counter.value.forEach {
Counter(
counter = it,
)
}
actions.value.forEach {
Action(
action = it,
onHit = onHit,
onDamage = onDamage,
)
}
} }
} }
if (alterations.value.isNotEmpty()) { HorizontalPager(
Column( modifier = Modifier
modifier = Modifier .fillMaxWidth()
.padding(horizontal = 16.dp) .pullRefresh(refreshState)
.fillMaxWidth() .verticalScroll(state = state),
.ddBorder( state = pagerState,
inner = remember { RoundedCornerShape(size = 8.dp) }, contentPadding = PaddingValues(all = 16.dp),
outline = remember { CutCornerShape(size = 16.dp) }, pageSpacing = 8.dp,
) verticalAlignment = Alignment.Top,
.padding(all = 16.dp), ) {
verticalArrangement = Arrangement.spacedBy(8.dp), when (it) {
) { 0 -> ProficiencyPage(
alterations.value.forEach { modifier = Modifier.fillMaxWidth(),
Text( sheet = sheet,
text = it onInitiative = onInitiative,
) onStats = onStats,
} onProficiencies = onProficiencies,
)
1 -> ActionsPages(
modifier = Modifier.fillMaxWidth(),
actions = actions,
counter = counter,
alterations = alterations,
onHit = onHit,
onDamage = onDamage,
)
} }
} }
} }
@ -351,10 +300,10 @@ private fun CharacterSheetContent(
} }
} }
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, heightDp = 2000) @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, heightDp = 2000) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun CharacterScreenPreview() { private fun CharacterScreenPreview() {
LexiconTheme { LexiconTheme {
val bru = remember { val bru = remember {
@ -540,6 +489,7 @@ private fun CharacterScreenPreview() {
CharacterSheetContent( CharacterSheetContent(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
state = rememberScrollState(), state = rememberScrollState(),
pagerState = rememberPagerState { 1 },
refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }), refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }),
refreshing = remember { mutableStateOf(false) }, refreshing = remember { mutableStateOf(false) },
onRefresh = { }, onRefresh = { },

View file

@ -108,7 +108,6 @@ private fun SearchScreenContent(
containerColor = Color.Transparent, containerColor = Color.Transparent,
topBar = { topBar = {
TopAppBar( TopAppBar(
modifier = Modifier.shadow(elevation = 4.dp),
windowInsets = NO_WINDOW_INSETS, windowInsets = NO_WINDOW_INSETS,
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
@ -128,7 +127,7 @@ private fun SearchScreenContent(
modifier = Modifier.padding(paddingValues = paddingValues), modifier = Modifier.padding(paddingValues = paddingValues),
header = { header = {
SearchBox( SearchBox(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.shadow(elevation = 4.dp),
form = form, form = form,
) )
}, },
@ -161,27 +160,36 @@ private fun SearchBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
form: SearchFormUio, form: SearchFormUio,
) { ) {
Column( Surface(
modifier = modifier, modifier = modifier,
verticalArrangement = Arrangement.spacedBy(2.dp),
) { ) {
TextField( Column(
modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(2.dp),
field = form.search,
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
DropDownField( TextField(
modifier = Modifier.weight(1f), modifier = Modifier
field = form.gender, .fillMaxWidth()
.padding(horizontal = 16.dp),
field = form.search,
) )
DropDownField( Row(
modifier = Modifier.weight(1f), modifier = Modifier.padding(horizontal = 16.dp),
field = form.race, horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
DropDownField(
modifier = Modifier.weight(weight = 1f, fill = true),
field = form.gender,
)
DropDownField(
modifier = Modifier.weight(weight = 1f, fill = true),
field = form.race,
)
}
Divider(
modifier = Modifier.padding(top = 16.dp),
color = MaterialTheme.lexicon.colorScheme.placeholder,
) )
} }
Divider(modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
} }
} }