diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt index a0ec807..093cfab 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt @@ -4,6 +4,7 @@ package com.pixelized.rplexicon.ui.screens.character import android.content.res.Configuration import androidx.activity.compose.BackHandler +import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -42,6 +43,9 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -57,14 +61,21 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalRollOverlay -import com.pixelized.rplexicon.LocalSnack import com.pixelized.rplexicon.NO_WINDOW_INSETS import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog import com.pixelized.rplexicon.ui.composable.error.HandleFetchError import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost +import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Action +import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Alteration +import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Inventory +import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Proficiency +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.* import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio.ID.* +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPagePreview import com.pixelized.rplexicon.ui.screens.character.pages.actions.AttacksViewModel @@ -87,6 +98,14 @@ import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.extentions.lexiconShadow import kotlinx.coroutines.launch + +enum class CharacterHeader(@StringRes val label: Int) { + Action(R.string.character_sheet_tab_actions), + Alteration(R.string.character_sheet_tab_alteration), + Inventory(R.string.character_sheet_tab_inventory), + Proficiency(R.string.character_sheet_tab_proficiency), +} + @OptIn( ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, @@ -103,7 +122,6 @@ fun CharacterSheetScreen( skillViewModel: SkillsViewModel = hiltViewModel(), alterationsViewModel: AlterationViewModel = hiltViewModel(), ) { - val snack = LocalSnack.current val screen = LocalScreenNavHost.current val overlay = LocalRollOverlay.current val scope = rememberCoroutineScope() @@ -115,14 +133,9 @@ fun CharacterSheetScreen( refreshing = false, onRefresh = { scope.launch { viewModel.update(force = true) } }, ) - val pagerState = rememberPagerState { - val haveSheet = proficiencyViewModel.sheet.value != null - val haveAction = attacksViewModel.attacks.value.isNotEmpty() - val haveSpell = spellsViewModel.spells.value.isNotEmpty() - val haveAlteration = alterationsViewModel.alterations.value.isNotEmpty() - val haveInventory = true - haveSheet.toInt() + (haveAction || haveSpell).toInt() + haveAlteration.toInt() + haveInventory.toInt() - } + val tabs = rememberHeaderTabsState() + val pagerState = rememberPagerState { tabs.value.size } + Surface( modifier = Modifier.fillMaxSize(), ) { @@ -132,18 +145,21 @@ fun CharacterSheetScreen( sheetState = sheetState, refreshState = refresh, name = viewModel.character, - onRefresh = { - scope.launch { viewModel.update(force = true) } - }, - onFullRefresh = { - scope.launch { viewModel.update(force = true, full = true) } - }, + tabs = tabs, + header = headerViewModel.header, onBack = { screen.popBackStack() }, onTab = { scope.launch { pagerState.animateScrollToPage(it) } }, + onHitPoint = headerViewModel::toggleHitPointDialog, + onRefresh = { + scope.launch { viewModel.update(force = true) } + }, + onFullRefresh = { + scope.launch { viewModel.update(force = true, full = true) } + }, loader = { Loader( modifier = Modifier.align(Alignment.TopCenter), @@ -159,7 +175,6 @@ fun CharacterSheetScreen( actions = { ActionPage( sheetState = sheetState, - headerViewModel = headerViewModel, attacksViewModel = attacksViewModel, objectsViewModel = objectsViewModel, spellsViewModel = spellsViewModel, @@ -189,6 +204,12 @@ fun CharacterSheetScreen( }, ) + HandleHitPointEditDialog( + dialog = headerViewModel.hitPointDialog, + onDismissRequest = headerViewModel::toggleHitPointDialog, + onConfirm = headerViewModel::applyHitPointChange, + ) + HandleFetchError( errors = viewModel.error, ) @@ -217,6 +238,9 @@ private fun CharacterSheetContent( onRefresh: () -> Unit, onFullRefresh: () -> Unit, name: String, + tabs: State>, + header: State, + onHitPoint: () -> Unit, onBack: () -> Unit, onTab: (Int) -> Unit, loader: @Composable BoxScope.() -> Unit, @@ -276,19 +300,28 @@ private fun CharacterSheetContent( } }, content = { - Box( + Column( modifier = Modifier .fillMaxSize() .navigationBarsPadding(), ) { - Column { + Column( + modifier = Modifier + .zIndex(1f) + .lexiconShadow(), + ) { + CharacterSheetHeader( + modifier = Modifier.fillMaxWidth(), + header = header, + onHitPoint = onHitPoint, + ) PagerHeader( - modifier = Modifier - .zIndex(1f) - .lexiconShadow(), pagerState = pagerState, + tabs = tabs, onTab = onTab, ) + } + Box { HorizontalPager( modifier = Modifier .fillMaxWidth() @@ -297,16 +330,16 @@ private fun CharacterSheetContent( beyondBoundsPageCount = 0, verticalAlignment = Alignment.Top, pageContent = { page -> - when (page) { - 0 -> inventory() - 1 -> proficiencies() - 2 -> actions() - 3 -> alterations() + when (tabs.value[page]) { + Action -> actions() + Alteration -> alterations() + Inventory -> inventory() + Proficiency -> proficiencies() } } ) + loader() } - loader() } } ) @@ -320,41 +353,78 @@ private fun CharacterSheetContent( private fun PagerHeader( modifier: Modifier = Modifier, pagerState: PagerState, - tabs: List = headers(), + tabs: State>, onTab: (Int) -> Unit, ) { - ScrollableTabRow( - modifier = modifier, - selectedTabIndex = pagerState.currentPage, - divider = { }, - ) { - tabs.forEachIndexed { index, tab -> - Tab( - selected = pagerState.currentPage == index, - selectedContentColor = MaterialTheme.colorScheme.onSurface, - unselectedContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), - onClick = { onTab(index) }, - ) { - Text( - modifier = Modifier.padding(all = 8.dp), - style = MaterialTheme.typography.labelSmall, - text = tab, - ) + if (tabs.value.isNotEmpty()) { + ScrollableTabRow( + modifier = modifier, + selectedTabIndex = pagerState.currentPage, + edgePadding = 16.dp, + divider = { }, + ) { + tabs.value.forEachIndexed { index, tab -> + Tab( + selected = pagerState.currentPage == index, + selectedContentColor = MaterialTheme.colorScheme.onSurface, + unselectedContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + onClick = { onTab(index) }, + ) { + Text( + modifier = Modifier.padding(all = 8.dp), + style = MaterialTheme.typography.labelSmall, + text = stringResource(id = tab.label), + ) + } } } } } -@Stable @Composable -private fun headers(): List { - val inventory = stringResource(id = R.string.character_sheet_tab_inventory) - val proficiency = stringResource(id = R.string.character_sheet_tab_proficiency) - val actions = stringResource(id = R.string.character_sheet_tab_actions) - val alteration = stringResource(id = R.string.character_sheet_tab_alteration) - return remember { - listOf(inventory, proficiency, actions, alteration) +@Stable +private fun rememberHeaderTabsState( + proficiencyViewModel: ProficiencyViewModel = hiltViewModel(), + attacksViewModel: AttacksViewModel = hiltViewModel(), + inventoryViewModel: InventoryViewModel = hiltViewModel(), + spellsViewModel: SpellsViewModel = hiltViewModel(), + skillViewModel: SkillsViewModel = hiltViewModel(), + alterationsViewModel: AlterationViewModel = hiltViewModel(), +): State> { + val headers = remember { + derivedStateOf { + mutableListOf().apply { + addAll( + when { + proficiencyViewModel.sheet.value != null -> listOf(Proficiency) + else -> emptyList() + } + ) + addAll( + when { + attacksViewModel.attacks.value.isNotEmpty() || + spellsViewModel.spells.value.isNotEmpty() || + skillViewModel.skills.value.isNotEmpty() -> listOf(Action) + + else -> emptyList() + } + ) + addAll( + when { + alterationsViewModel.alterations.value.isNotEmpty() -> listOf(Alteration) + else -> emptyList() + } + ) + addAll( + when { + inventoryViewModel.inventory.value.isNotEmpty() -> listOf(Inventory) + else -> emptyList() + } + ) + } + } } + return headers } @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @@ -366,23 +436,20 @@ private fun CharacterScreenPreview( ) { LexiconTheme { Surface { - val sheetState = rememberModalBottomSheetState( - initialValue = when (preview == 3) { - true -> ModalBottomSheetValue.Expanded - else -> ModalBottomSheetValue.Hidden - }, - ) CharacterSheetContent( modifier = Modifier.fillMaxSize(), - sheetState = sheetState, + sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden), pagerState = rememberPagerState(initialPage = preview) { 4 }, refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }), name = "Brulkhai", + header = rememberCharacterHeaderStatePreview(), + tabs = rememberHeaderPreview(), onBack = { }, onTab = { }, onRefresh = { }, onFullRefresh = { }, loader = { }, + onHitPoint = { }, proficiencies = { ProficiencyPreview() }, actions = { ActionPagePreview() }, alterations = { AlterationPagePreview() }, @@ -393,7 +460,18 @@ private fun CharacterScreenPreview( } } -private fun Boolean.toInt(): Int = if (this) 1 else 0 +@Composable +@Stable +private fun rememberHeaderPreview(): State> = remember { + mutableStateOf( + listOf( + Proficiency, + Action, + Alteration, + Inventory, + ) + ) +} private class CharacterScreenPreviewProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf(0, 1, 2, 3) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt index f1b1977..f9ef4f3 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt @@ -32,7 +32,7 @@ data class CharacterSheetHeaderUio( @Composable fun CharacterSheetHeader( modifier: Modifier = Modifier, - padding : PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + padding : PaddingValues = PaddingValues(start = 16.dp, end = 16.dp, bottom = 4.dp), header: State, onHitPoint : () -> Unit, ) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt index c55d749..f871754 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt @@ -36,8 +36,8 @@ data class LabelPointUio( fun LabelPoint( modifier: Modifier = Modifier, labelStyle: TextStyle = MaterialTheme.typography.labelSmall, - valueStyle: TextStyle = MaterialTheme.typography.headlineMedium, - maxStyle: TextStyle = MaterialTheme.typography.titleMedium, + valueStyle: TextStyle = MaterialTheme.typography.headlineSmall, + maxStyle: TextStyle = MaterialTheme.typography.titleSmall, label: LabelPointUio, onClick: (() -> Unit)? = null, ) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt index 726665b..a5542bf 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt @@ -4,9 +4,9 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -24,7 +24,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog import com.pixelized.rplexicon.ui.composable.edit.HandleSkillEditDialog import com.pixelized.rplexicon.ui.composable.edit.HandleSpellEditDialog import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost @@ -41,10 +40,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHead import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio import com.pixelized.rplexicon.ui.screens.character.composable.actions.rememberTokenListStatePreview -import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader -import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberAttackListStatePreview -import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberObjectListStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellListStatePreview import com.pixelized.rplexicon.ui.theme.LexiconTheme @@ -54,7 +50,6 @@ import kotlinx.coroutines.launch @Composable fun ActionPage( sheetState: ModalBottomSheetState, - headerViewModel: HeaderViewModel = hiltViewModel(), attacksViewModel: AttacksViewModel = hiltViewModel(), objectsViewModel: ObjectsViewModel = hiltViewModel(), spellsViewModel: SpellsViewModel = hiltViewModel(), @@ -66,12 +61,10 @@ fun ActionPage( ActionsPageContent( modifier = Modifier.fillMaxSize(), - header = headerViewModel.header, attacks = attacksViewModel.attacks, objects = objectsViewModel.objects, tokens = skillViewModel.skills, spells = spellsViewModel.spells, - onHitPoint = headerViewModel::toggleHitPointDialog, onAttackHit = { id -> attacksViewModel.onHitRoll(id)?.let { overlay.prepareRoll(diceThrow = it) @@ -84,7 +77,7 @@ fun ActionPage( overlay.showOverlay() } }, - onObject = { + onObject = { id -> }, onUseObject = { id -> @@ -108,7 +101,7 @@ fun ActionPage( }, onSpell = { spell -> screen.navigateToSpellDetail( - character = headerViewModel.character, + character = spellsViewModel.characterName, spell = spell, ) }, @@ -131,12 +124,6 @@ fun ActionPage( }, ) - HandleHitPointEditDialog( - dialog = headerViewModel.hitPointDialog, - onDismissRequest = headerViewModel::toggleHitPointDialog, - onConfirm = headerViewModel::applyHitPointChange, - ) - HandleSpellEditDialog( dialog = spellsViewModel.spellEditDialog, onDismissRequest = spellsViewModel::hideSpellEditDialog, @@ -160,12 +147,10 @@ fun ActionPage( fun ActionsPageContent( modifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), - header: State, attacks: State>, objects: State>, tokens: State>, spells: State>>>, - onHitPoint: () -> Unit, onAttackHit: (id: String) -> Unit, onAttackDamage: (id: String) -> Unit, onObject: (id: String) -> Unit, @@ -182,14 +167,9 @@ fun ActionsPageContent( Column( modifier = modifier, ) { - CharacterSheetHeader( - modifier = Modifier.fillMaxWidth(), - header = header, - onHitPoint = onHitPoint, - ) - LazyColumn( state = lazyListState, + contentPadding = PaddingValues(vertical = 8.dp), ) { if (attacks.value.isNotEmpty()) { stickyHeader { @@ -262,9 +242,6 @@ fun ActionsPageContent( onCast = onCast, ) } - items(count = 1) { - Spacer(modifier = Modifier.height(height = 16.dp)) - } } } } @@ -278,12 +255,10 @@ fun ActionPagePreview() { Surface { ActionsPageContent( modifier = Modifier.fillMaxSize(), - header = rememberCharacterHeaderStatePreview(), attacks = rememberAttackListStatePreview(), objects = rememberObjectListStatePreview(), tokens = rememberTokenListStatePreview(), spells = rememberSpellListStatePreview(), - onHitPoint = { }, onAttackHit = { }, onAttackDamage = { }, onObject = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt index 93c7b47..e5ec6a2 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt @@ -44,10 +44,6 @@ class ObjectsViewModel @Inject constructor( } } - fun onObject(name: String) { - - } - fun onUse(name: String): DiceThrow? { val item = objectsRepository.find(character = character, item = name) return item?.let { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsViewModel.kt index c4348b2..8b02897 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsViewModel.kt @@ -45,9 +45,9 @@ class SpellsViewModel @Inject constructor( spellFactory: SpellUioFactory, savedStateHandle: SavedStateHandle, ) : AndroidViewModel(application) { - private val characterName = savedStateHandle.characterSheetArgument.name private var character: CharacterSheet? = null private var characterFire: CharacterSheetFire? = null + val characterName = savedStateHandle.characterSheetArgument.name private val _editDialog = mutableStateOf(null) val spellEditDialog: State get() = _editDialog