Fix crash on home & add indicator on CharacterScreen

This commit is contained in:
Thomas Andres Gomez 2023-09-27 16:23:27 +02:00
parent e28d9a5dad
commit 5f8a218ac3
19 changed files with 186 additions and 75 deletions

View file

@ -0,0 +1,127 @@
package com.pixelized.rplexicon.ui.composable
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Composable
fun IndicatorStep(
modifier: Modifier = Modifier,
count: Int,
selectedIndex: Int?,
defaultSize: Dp = 4.dp,
selectedWidth: Dp = 16.dp,
indicatorSpacing: Dp = 8.dp,
defaultColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.35f),
selectedColor: Color = MaterialTheme.colorScheme.primary,
) {
Row(modifier = modifier) {
repeat(count) { index ->
val transition = rememberIndicatorTransition(
selected = selectedIndex == index,
defaultSize = defaultSize,
selectedWidth = selectedWidth,
defaultColor = defaultColor,
selectedColor = selectedColor,
)
Box(
modifier = Modifier
.size(size = transition.size)
.background(color = transition.color, shape = CircleShape)
)
if (index != count - 1) {
Spacer(modifier = Modifier.width(indicatorSpacing))
}
}
}
}
@Composable
private fun rememberIndicatorTransition(
selected: Boolean,
defaultSize: Dp,
selectedWidth: Dp,
defaultColor: Color,
selectedColor: Color,
): IndicatorStyle {
val transition = updateTransition(
label = "IndicatorStep selection transition",
targetState = selected
)
val color = transition.animateColor(
label = "IndicatorStep background animation",
targetValueByState = {
when (it) {
true -> selectedColor
else -> defaultColor
}
}
)
val width by transition.animateDp(
label = "IndicatorStep size animation",
targetValueByState = {
when (it) {
true -> selectedWidth
else -> defaultSize
}
}
)
return remember {
IndicatorStyle(
color = color,
size = derivedStateOf {
DpSize(width = width, height = defaultSize)
}
)
}
}
@Stable
class IndicatorStyle(
color: State<Color>,
size: State<DpSize>,
) {
val color: Color by color
val size: DpSize by size
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun IndicatorStepPreview() {
LexiconTheme {
Surface {
IndicatorStep(
modifier = Modifier.padding(all = 4.dp),
count = 3,
selectedIndex = 0,
)
}
}
}

View file

@ -48,9 +48,6 @@ val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
fun HomeNavHost(
navHostController: NavHostController = rememberNavController(),
bottomBarItems: List<BottomBarItem> = rememberBottomBarItems(navHostController),
lexiconListState: LazyListState,
questListState: LazyListState,
locationListState: LazyListState,
startDestination: String = LEXICON_LIST_ROUTE,
) {
CompositionLocalProvider(
@ -108,9 +105,9 @@ fun HomeNavHost(
navController = navHostController,
startDestination = startDestination,
) {
composableLexicon(lazyListState = lexiconListState)
composableQuests(lazyListState = questListState)
composableLocations(lazyListState = locationListState)
composableLexicon()
composableQuests()
composableLocations()
}
}
}

View file

@ -41,11 +41,7 @@ fun ScreenNavHost(
startDestination = startDestination,
) {
composableAuthentication()
composableHome(
lexiconListState = lexiconListState,
questListState = questListState,
locationListState = locationListState
)
composableHome()
composableLexiconDetail()
composableLexiconSearch()
composableQuestDetail()

View file

@ -12,16 +12,12 @@ private const val ROUTE = "lexicon"
const val LEXICON_LIST_ROUTE = ROUTE
fun NavGraphBuilder.composableLexicon(
lazyListState: LazyListState,
) {
fun NavGraphBuilder.composableLexicon() {
animatedComposable(
route = LEXICON_LIST_ROUTE,
animation = NavigationAnimation.Fade,
) {
LexiconScreen(
lazyListState = lazyListState
)
LexiconScreen()
}
}

View file

@ -12,16 +12,12 @@ private const val ROUTE = "locations"
const val LOCATION_LIST_ROUTE = ROUTE
fun NavGraphBuilder.composableLocations(
lazyListState: LazyListState,
) {
fun NavGraphBuilder.composableLocations() {
animatedComposable(
route = LOCATION_LIST_ROUTE,
animation = NavigationAnimation.Fade,
) {
LocationScreen(
lazyListState = lazyListState,
)
LocationScreen()
}
}

View file

@ -12,16 +12,12 @@ private const val ROUTE = "quests"
const val QUEST_LIST_ROUTE = ROUTE
fun NavGraphBuilder.composableQuests(
lazyListState: LazyListState,
) {
fun NavGraphBuilder.composableQuests() {
animatedComposable(
route = QUEST_LIST_ROUTE,
animation = NavigationAnimation.Fade,
) {
QuestListScreen(
lazyListState = lazyListState,
)
QuestListScreen()
}
}

View file

@ -1,6 +1,5 @@
package com.pixelized.rplexicon.ui.navigation.screens
import androidx.compose.foundation.lazy.LazyListState
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
@ -12,20 +11,12 @@ private const val ROUTE = "home"
const val HOME_ROUTE = ROUTE
fun NavGraphBuilder.composableHome(
lexiconListState: LazyListState,
questListState: LazyListState,
locationListState: LazyListState,
) {
fun NavGraphBuilder.composableHome() {
animatedComposable(
route = HOME_ROUTE,
animation = NavigationAnimation.Push,
) {
HomeNavHost(
lexiconListState = lexiconListState,
questListState = questListState,
locationListState = locationListState
)
HomeNavHost()
}
}

View file

@ -89,17 +89,18 @@ fun CharacterSheetScreen(
refreshing = false,
onRefresh = { scope.launch { viewModel.update(force = true) } },
)
val pagerState = rememberPagerState {
val haveSheet = proficiencyViewModel.sheet.value != null
val haveAction = attackViewModel.attacks.value.isNotEmpty()
val haveSpell = spellsViewModel.spells.value.isNotEmpty()
(if (haveSheet) 1 else 0) + (if (haveAction || haveSpell) 1 else 0)
}
Surface(
modifier = Modifier.fillMaxSize(),
) {
CharacterSheetContent(
modifier = Modifier.fillMaxSize(),
pagerState = rememberPagerState {
val haveSheet = proficiencyViewModel.sheet.value != null
val haveAction = attackViewModel.attacks.value.isNotEmpty()
val haveSpell = spellsViewModel.spells.value.isNotEmpty()
(if (haveSheet) 1 else 0) + (if (haveAction || haveSpell) 1 else 0)
},
pagerState = pagerState,
sheetState = sheetState,
refreshState = refresh,
onRefresh = {
@ -111,6 +112,7 @@ fun CharacterSheetScreen(
header = {
CharacterSheetHeader(
modifier = Modifier.zIndex(1f),
pagerState = pagerState,
header = viewModel.header,
)
},
@ -223,7 +225,9 @@ private fun CharacterSheetContent(
header()
Box(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
) {
HorizontalPager(
modifier = Modifier

View file

@ -2,10 +2,14 @@ package com.pixelized.rplexicon.ui.screens.character.composable.character
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.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@ -14,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.composable.IndicatorStep
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@ -24,32 +29,45 @@ data class CharacterSheetHeaderUio(
val speed: LabelPointUio,
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CharacterSheetHeader(
modifier: Modifier = Modifier,
pagerState: PagerState,
header: State<CharacterSheetHeaderUio?>,
) {
Surface(
modifier = modifier,
shadowElevation = 4.dp,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
alignment = Alignment.CenterHorizontally
),
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
header.value?.armorClass?.let { LabelPoint(label = it) }
header.value?.hitPoint?.let { LabelPoint(label = it) }
header.value?.speed?.let { LabelPoint(label = it) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
alignment = Alignment.CenterHorizontally
),
) {
header.value?.armorClass?.let { LabelPoint(label = it) }
header.value?.hitPoint?.let { LabelPoint(label = it) }
header.value?.speed?.let { LabelPoint(label = it) }
}
IndicatorStep(
modifier = Modifier.padding(vertical = 4.dp),
count = pagerState.pageCount,
selectedIndex = pagerState.currentPage,
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
@ -57,7 +75,8 @@ fun CharacterSheetHeaderPreview() {
LexiconTheme {
Surface {
CharacterSheetHeader(
header = rememberCharacterHeaderStatePreview()
header = rememberCharacterHeaderStatePreview(),
pagerState = rememberPagerState { 2 },
)
}
}

View file

@ -37,7 +37,6 @@ fun LabelPoint(
label: LabelPointUio,
) {
Column(
modifier = modifier.padding(all = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {

View file

@ -14,8 +14,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State

View file

@ -2,7 +2,6 @@ package com.pixelized.rplexicon.ui.screens.character.pages.chooser
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
@ -14,7 +13,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLevelItem

View file

@ -52,11 +52,11 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun LexiconScreen(
viewModel: LexiconViewModel = hiltViewModel(),
lazyListState: LazyListState,
viewModel: LexiconViewModel = hiltViewModel()
) {
val scope = rememberCoroutineScope()
val screen = LocalScreenNavHost.current
val lazyListState = rememberLazyListState()
val refresh = rememberPullRefreshState(
refreshing = false,

View file

@ -38,9 +38,9 @@ import kotlinx.coroutines.launch
@Composable
fun LocationScreen(
viewModel: LocationViewModel = hiltViewModel(),
lazyListState: LazyListState,
) {
val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
val screen = LocalScreenNavHost.current
val refresh = rememberPullRefreshState(

View file

@ -5,7 +5,6 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.pixelized.rplexicon.repository.data.LocationRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure

View file

@ -38,10 +38,10 @@ import kotlinx.coroutines.launch
@Composable
fun QuestListScreen(
viewModel: QuestListViewModel = hiltViewModel(),
lazyListState: LazyListState,
) {
val scope = rememberCoroutineScope()
val screen = LocalScreenNavHost.current
val lazyListState = rememberLazyListState()
val refresh = rememberPullRefreshState(
refreshing = false,

View file

@ -5,7 +5,6 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.pixelized.rplexicon.repository.data.QuestRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure

View file

@ -4,13 +4,10 @@ import android.app.Application
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Alteration
import com.pixelized.rplexicon.model.CharacterSheet
import com.pixelized.rplexicon.model.Roll
import com.pixelized.rplexicon.repository.data.AlterationRepository
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio

View file

@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface