Make landing page dynamic based on the character sheets

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-05 16:08:39 +02:00
parent ddfc9a4cc6
commit 71b0556a74
16 changed files with 240 additions and 146 deletions

View file

@ -13,7 +13,7 @@ object AlterationSortUseCase {
}.thenByDescending {
PLAYER == it
}.thenByDescending {
sheet?.characterClass?.any { clazz -> clazz.value == it }
sheet?.characterClass?.any { clazz -> clazz.id == it }
}.thenByDescending {
sheet?.race == it
}.thenByDescending {

View file

@ -1,6 +1,7 @@
package com.pixelized.rplexicon.data.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.pixelized.rplexicon.R
data class CharacterSheet(
@ -57,45 +58,84 @@ data class CharacterSheet(
val others: List<String>, // others masteries
) {
val isWarlock: Boolean get() = characterClass.contains(Class.WARLOCK)
val resource: String? get() = characterClass.firstNotNullOfOrNull { it.resource }
val resource: String? get() = characterClass.firstNotNullOfOrNull { it.resourceId }
enum class Class(
val value: String,
val resource: String? = null,
val label: Int? = null,
val id: String,
@StringRes
val label: Int,
val resourceId: String? = null,
@StringRes
val resourceLabel: Int? = null,
@DrawableRes val icon: Int,
) {
BARBARIAN(
value = "Barbare",
resource = "Rage",
label = R.string.character_sheet_title_rage,
icon = R.drawable.ic_class_barbarian_24
id = "Barbare",
label = R.string.class_barbarian,
resourceId = "Rage",
resourceLabel = R.string.character_sheet_title_rage,
icon = R.drawable.icbg_class_barbarian,
),
BARD(
value = "Barde",
resource = "Inspiration bardique",
label = R.string.character_sheet_title_inspiration,
icon = R.drawable.ic_class_bard_24
id = "Barde",
label = R.string.class_bard,
resourceId = "Inspiration bardique",
resourceLabel = R.string.character_sheet_title_inspiration,
icon = R.drawable.icbg_class_bard,
),
CLERIC(
value = "Clerc",
resource = "Conduit divin",
label = R.string.character_sheet_title_conduit,
icon = R.drawable.ic_class_cleric_24
id = "Clerc",
label = R.string.class_cleric,
resourceId = "Conduit divin",
resourceLabel = R.string.character_sheet_title_conduit,
icon = R.drawable.icbg_class_cleric,
),
DRUID(
value = "Druide",
resource = "Forme sauvage",
label = R.string.character_sheet_title_wild_shape,
id = "Druide",
label = R.string.class_druid,
resourceId = "Forme sauvage",
resourceLabel = R.string.character_sheet_title_wild_shape,
icon = R.drawable.ic_class_druid_24,
),
FIGHTER(value = "Guerrier", icon = R.drawable.ic_class_fighter_24),
MONK(value = "Moine", icon = R.drawable.ic_class_monk_24),
PALADIN(value = "Paladin", icon = R.drawable.ic_class_paladin_24),
RANGER(value = "Rodeur", icon = R.drawable.ic_class_ranger_24),
ROGUE(value = "Roublard", icon = R.drawable.ic_class_rogue_24),
SORCERER(value = "Ensorceleur", icon = R.drawable.ic_class_sorcerer_24),
WARLOCK(value = "Occultiste", icon = R.drawable.ic_class_warlock_24),
WIZARD(value = "Magicien", icon = R.drawable.ic_class_wizard_24),
FIGHTER(
id = "Guerrier",
label = R.string.class_fighter,
icon = R.drawable.ic_class_fighter_24,
),
MONK(
id = "Moine",
label = R.string.class_monk,
icon = R.drawable.ic_class_monk_24,
),
PALADIN(
id = "Paladin",
label = R.string.class_paladin,
icon = R.drawable.ic_class_paladin_24,
),
RANGER(
id = "Rodeur",
label = R.string.class_ranger,
icon = R.drawable.icbg_class_ranger,
),
ROGUE(
id = "Roublard",
label = R.string.class_rogue,
icon = R.drawable.ic_class_rogue_24,
),
SORCERER(
id = "Ensorceleur",
label = R.string.class_sorcerer,
icon = R.drawable.ic_class_sorcerer_24,
),
WARLOCK(
id = "Occultiste",
label = R.string.class_warlock,
icon = R.drawable.icbg_class_warlock,
),
WIZARD(
id = "Magicien",
label = R.string.class_wizard,
icon = R.drawable.ic_class_wizard_24,
),
}
}

View file

@ -51,7 +51,7 @@ class AlterationParser @Inject constructor(
characterSheets
.filter { // check if the alteration is applicable to the character
alteration.target.let { target ->
target == ALL || target == it.name || target == it.race || it.characterClass.any { it.value == target }
target == ALL || target == it.name || target == it.race || it.characterClass.any { it.id == target }
}
}
.forEach { sheet -> // check the default alteration state for that character

View file

@ -12,18 +12,18 @@ class CharacterClassParser @Inject constructor() {
?.map { it.trim() }
?.mapNotNull {
when (it) {
CharacterSheet.Class.BARBARIAN.value -> CharacterSheet.Class.BARBARIAN
CharacterSheet.Class.BARD.value -> CharacterSheet.Class.BARD
CharacterSheet.Class.CLERIC.value -> CharacterSheet.Class.CLERIC
CharacterSheet.Class.DRUID.value -> CharacterSheet.Class.DRUID
CharacterSheet.Class.FIGHTER.value -> CharacterSheet.Class.FIGHTER
CharacterSheet.Class.MONK.value -> CharacterSheet.Class.MONK
CharacterSheet.Class.PALADIN.value -> CharacterSheet.Class.PALADIN
CharacterSheet.Class.RANGER.value -> CharacterSheet.Class.RANGER
CharacterSheet.Class.ROGUE.value -> CharacterSheet.Class.ROGUE
CharacterSheet.Class.SORCERER.value -> CharacterSheet.Class.SORCERER
CharacterSheet.Class.WARLOCK.value -> CharacterSheet.Class.WARLOCK
CharacterSheet.Class.WIZARD.value -> CharacterSheet.Class.WIZARD
CharacterSheet.Class.BARBARIAN.id -> CharacterSheet.Class.BARBARIAN
CharacterSheet.Class.BARD.id -> CharacterSheet.Class.BARD
CharacterSheet.Class.CLERIC.id -> CharacterSheet.Class.CLERIC
CharacterSheet.Class.DRUID.id -> CharacterSheet.Class.DRUID
CharacterSheet.Class.FIGHTER.id -> CharacterSheet.Class.FIGHTER
CharacterSheet.Class.MONK.id -> CharacterSheet.Class.MONK
CharacterSheet.Class.PALADIN.id -> CharacterSheet.Class.PALADIN
CharacterSheet.Class.RANGER.id -> CharacterSheet.Class.RANGER
CharacterSheet.Class.ROGUE.id -> CharacterSheet.Class.ROGUE
CharacterSheet.Class.SORCERER.id -> CharacterSheet.Class.SORCERER
CharacterSheet.Class.WARLOCK.id -> CharacterSheet.Class.WARLOCK
CharacterSheet.Class.WIZARD.id -> CharacterSheet.Class.WIZARD
else -> null
}
}

View file

@ -18,10 +18,10 @@ class CharacterSheetHeaderUioFactory @Inject constructor(
sheetHeaderData: HeaderViewModel.SheetHeaderData?,
fireHeaderData: HeaderViewModel.FireHeaderData?,
): CharacterSheetHeaderUio {
val classWithResource = sheetHeaderData?.characterClass?.firstOrNull { it.resource != null }
val classWithResource = sheetHeaderData?.characterClass?.firstOrNull { it.resourceId != null }
val favoriteSkill = skillRepository.find(
character = character,
skill = classWithResource?.resource
skill = classWithResource?.resourceId
)
return CharacterSheetHeaderUio(
initiative = LabelPointUio(
@ -47,10 +47,10 @@ class CharacterSheetHeaderUioFactory @Inject constructor(
value = "$it",
)
},
resource = if (classWithResource?.label != null && favoriteSkill?.amount != null) {
resource = if (classWithResource?.resourceLabel != null && favoriteSkill?.amount != null) {
ResourcePointUio(
id = favoriteSkill.name,
label = classWithResource.label,
label = classWithResource.resourceLabel,
value = fireHeaderData?.resource ?: 0,
max = favoriteSkill.amount,
)

View file

@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.landing
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.DrawableRes
import androidx.annotation.FloatRange
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -42,7 +43,7 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class LandingItemUio(
@DrawableRes val icon: Int,
@DrawableRes val icon: Int?,
val title: String?,
val subTitle: String?,
)
@ -54,6 +55,9 @@ fun LandingItem(
paddings: PaddingValues = PaddingValues(),
imagePadding: PaddingValues = PaddingValues(all = 16.dp),
shape: Shape = remember { RoundedCornerShape(size = 8.dp) },
backgroundFilter: ColorFilter? = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
@FloatRange(from = 0.0, to = 1.0) backgroundGradientFrom: Float = 0.5f,
@FloatRange(from = 0.0, to = 1.0) backgroundGradientTo: Float = 1f,
rotation: Float = 0f,
alpha: Float = 0.6f,
onClick: () -> Unit,
@ -66,23 +70,30 @@ fun LandingItem(
.then(other = modifier),
contentAlignment = Alignment.BottomCenter
) {
Image(
modifier = Modifier
.matchParentSize()
.padding(paddingValues = imagePadding)
.rotate(degrees = rotation),
painter = painterResource(id = item.icon),
alpha = alpha,
contentScale = ContentScale.FillHeight,
alignment = Alignment.TopCenter,
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
contentDescription = null,
)
item.icon?.let {
Image(
modifier = Modifier
.matchParentSize()
.padding(paddingValues = imagePadding)
.rotate(degrees = rotation),
painter = painterResource(id = it),
alpha = alpha,
contentScale = ContentScale.Fit,
alignment = Alignment.TopCenter,
colorFilter = backgroundFilter,
contentDescription = null,
)
}
Box(
modifier = Modifier
.matchParentSize()
.background(brush = rememberBackgroundGradient())
.background(
brush = rememberBackgroundGradient(
from = backgroundGradientFrom,
to = backgroundGradientTo,
)
)
)
Column(
@ -118,13 +129,16 @@ fun LandingItem(
}
@Composable
private fun rememberBackgroundGradient(): Brush {
private fun rememberBackgroundGradient(
@FloatRange(from = 0.0, to = 1.0) from: Float = 0.5f,
@FloatRange(from = 0.0, to = 1.0) to: Float = 1f,
): Brush {
val colorScheme = MaterialTheme.colorScheme
return remember {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = 0.5f),
colorScheme.surface.copy(alpha = 1.0f),
colorScheme.surface.copy(alpha = from),
colorScheme.surface.copy(alpha = to),
)
)
}

View file

@ -30,6 +30,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
@ -44,6 +48,7 @@ import androidx.compose.ui.text.style.TextAlign
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 com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.rememberBackgroundGradient
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
@ -59,7 +64,9 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
import com.pixelized.rplexicon.utilitary.extentions.modifier.scrollOffset
@Composable
fun LandingScreen() {
fun LandingScreen(
viewModel: LandingViewModel = hiltViewModel(),
) {
val screen = LocalScreenNavHost.current
Surface(
@ -75,7 +82,10 @@ fun LandingScreen() {
end = 16.dp,
bottom = 16.dp,
),
onCharacter = { screen.navigateToCharacterSheet(name = it) },
characters = viewModel.landing,
onCharacter = { character ->
character.title?.let { screen.navigateToCharacterSheet(name = it) }
},
onSearch = {
screen.navigateToSearch(
enableLexicon = true,
@ -103,13 +113,22 @@ private fun LandingContent(
bottom = 16.dp,
),
sectionPadding: Dp = 32.dp,
onCharacter: (String) -> Unit,
characters: State<List<LandingItemUio>>,
onCharacter: (LandingItemUio) -> Unit,
onSearch: () -> Unit,
onGameMaster: () -> Unit,
onLexicon: () -> Unit,
onQuest: () -> Unit,
onMap: () -> Unit,
) {
val charactersSection = remember {
derivedStateOf {
characters.value
.groupBy { characters.value.indexOf(it) / 3 }
.values.toList()
}
}
Box(
modifier = modifier.verticalScroll(scrollState),
) {
@ -174,86 +193,28 @@ private fun LandingContent(
text = stringResource(id = R.string.landing__caterogy__character),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
stringResource(id = R.string.landing__character_brulkhai).let { character ->
LandingItem(
modifier = Modifier
.height(128.dp)
.weight(1f),
imagePadding = PaddingValues(all = 8.dp),
item = LandingItemUio(
icon = R.drawable.ic_class_barbarian_24,
title = character,
subTitle = stringResource(id = R.string.class_barbarian),
),
onClick = { onCharacter(character) },
)
charactersSection.value.forEach { charactersGroup ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
charactersGroup.forEach { character ->
LandingItem(
modifier = Modifier
.aspectRatio(0.8f)
.weight(1f),
imagePadding = PaddingValues(bottom = 8.dp),
item = character,
backgroundFilter = null,
backgroundGradientFrom = 0f,
backgroundGradientTo = 0.5f,
onClick = { onCharacter(character) },
)
}
repeat(3 - charactersGroup.size) {
Spacer(modifier = Modifier.weight(1f))
}
}
stringResource(id = R.string.landing__character_leandre).let { character ->
LandingItem(
modifier = Modifier
.height(128.dp)
.weight(1f),
imagePadding = PaddingValues(all = 8.dp),
item = LandingItemUio(
icon = R.drawable.ic_class_cleric_24,
title = character,
subTitle = stringResource(id = R.string.class_cleric),
),
onClick = { onCharacter(character) },
)
}
stringResource(id = R.string.landing__character_nelia).let { character ->
LandingItem(
modifier = Modifier
.height(128.dp)
.weight(1f),
imagePadding = PaddingValues(all = 8.dp),
item = LandingItemUio(
icon = R.drawable.ic_class_ranger_24,
title = character,
subTitle = stringResource(id = R.string.class_ranger),
),
onClick = { onCharacter(character) },
)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
stringResource(id = R.string.landing__character_tigrane).let { character ->
LandingItem(
modifier = Modifier
.height(128.dp)
.weight(1f),
imagePadding = PaddingValues(all = 8.dp),
item = LandingItemUio(
icon = R.drawable.ic_class_warlock_24,
title = character,
subTitle = stringResource(id = R.string.class_warlock),
),
onClick = { onCharacter(character) },
)
}
stringResource(id = R.string.landing__character_unathana).let { character ->
LandingItem(
modifier = Modifier
.height(128.dp)
.weight(1f),
imagePadding = PaddingValues(all = 8.dp),
item = LandingItemUio(
icon = R.drawable.ic_class_bard_24,
title = character,
subTitle = stringResource(id = R.string.class_bard),
),
onClick = { onCharacter(character) },
)
}
Spacer(modifier = Modifier.weight(1f))
}
Text(
@ -392,6 +353,37 @@ private fun LandingPreview() {
Surface {
LandingContent(
modifier = Modifier.fillMaxSize(),
characters = remember {
mutableStateOf(
listOf(
LandingItemUio(
icon = R.drawable.icbg_class_barbarian,
title = "Brulkhai",
subTitle = "Barbarian, Warrior",
),
LandingItemUio(
icon = R.drawable.icbg_class_cleric,
title = "Léandre",
subTitle = "Cleric",
),
LandingItemUio(
icon = R.drawable.icbg_class_ranger,
title = "Nelia",
subTitle = "Ranger, Druid",
),
LandingItemUio(
icon = R.drawable.icbg_class_warlock,
title = "Tigrane",
subTitle = "Warlock",
),
LandingItemUio(
icon = R.drawable.icbg_class_bard,
title = "Unathana",
subTitle = "Bard",
),
)
)
},
onCharacter = { },
onSearch = { },
onGameMaster = { },

View file

@ -0,0 +1,48 @@
package com.pixelized.rplexicon.ui.screens.landing
import android.app.Application
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.utilitary.extentions.context
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class LandingViewModel @Inject constructor(
private val sheetRepository: CharacterSheetRepository,
application: Application,
) : AndroidViewModel(application = application) {
private val _landing = mutableStateOf<List<LandingItemUio>>(emptyList())
val landing: State<List<LandingItemUio>> = _landing
init {
viewModelScope.launch(Dispatchers.Default) {
sheetRepository.data.collectLatest { sheets ->
val landing = sheets.mapNotNull { entry ->
val icon = entry.value.characterClass.firstOrNull()?.icon
val title = entry.key
val subTitle = entry.value.characterClass
.map { context.getString(it.label) }
.joinToString(separator = ", ") { it }
LandingItemUio(
icon = icon,
title = title,
subTitle = subTitle,
)
}
withContext(Dispatchers.Main) {
_landing.value = landing
}
}
}
}
}

View file

@ -546,7 +546,7 @@ class SummaryFactory @Inject constructor(
} ?: none()
// Update spells
val skill = sheet.characterClass.firstOrNull()?.resource?.let { skill ->
val skill = sheet.characterClass.firstOrNull()?.resourceId?.let { skill ->
fire?.skills?.get(skill)
}?.let { skill ->
label(label = "$skill")

View file

@ -146,7 +146,7 @@ private object ResourcesCache {
"Transe" to R.drawable.icbg_focused_conjuration,
"Attaque supplémentaire" to R.drawable.icbg_extra_attack,
"Apanage de la Noblesse" to R.drawable.icbg_natures_ward,
"Druidique" to R.drawable.icbg_class_druid_badge,
"Druidique" to R.drawable.icbg_class_druid,
"Appel de familier" to R.drawable.icbg_find_familiar_cat,
"Détection de la magie" to R.drawable.icbg_generic_psychic,
"Vision dans le noir (Tieffelin)" to R.drawable.icbg_darkvision,

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB