From 039f124f8851b842eac62e2af5ace6c7078534cd Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Fri, 5 Jul 2024 15:50:26 +0200 Subject: [PATCH] Refactor TopAppBar into Toolbar to avoid weird Behavior. --- .../rplexicon/ui/composable/Toolbar.kt | 43 ++++ .../adventure/book/AdventureBooksScreen.kt | 4 +- .../chapter/AdventureStoriesScreen.kt | 6 +- .../adventure/detail/AdventureDetailScreen.kt | 110 ++++++-- .../screens/character/CharacterSheetScreen.kt | 4 +- .../lexicon/detail/LexiconDetailScreen.kt | 243 ++++++++++++++---- .../ui/screens/lexicon/list/LexiconScreen.kt | 4 +- .../location/detail/LocationDetailScreen.kt | 5 +- .../screens/location/list/LocationScreen.kt | 4 +- .../screens/quest/detail/QuestDetailScreen.kt | 4 +- .../ui/screens/quest/list/QuestListScreen.kt | 4 +- .../rplexicon/ui/screens/rolls/RollOverlay.kt | 9 +- .../ui/screens/settings/SettingsScreen.kt | 6 +- .../ui/screens/spell/SpellDetailScreen.kt | 6 +- 14 files changed, 341 insertions(+), 111 deletions(-) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/composable/Toolbar.kt diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/Toolbar.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/Toolbar.kt new file mode 100644 index 0000000..0c278c5 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/Toolbar.kt @@ -0,0 +1,43 @@ +package com.pixelized.rplexicon.ui.composable + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Toolbar( + modifier: Modifier = Modifier, + title: @Composable () -> Unit, + navigationIcon: @Composable () -> Unit = {}, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, + backgroundColor: Color = MaterialTheme.colorScheme.surface, + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + ), + scrollBehavior: TopAppBarScrollBehavior? = null +) { + Surface( + modifier = modifier, + color = backgroundColor, + ) { + TopAppBar( + title = title, + navigationIcon = navigationIcon, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt index 801c6f5..d6e906e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt @@ -24,7 +24,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf @@ -39,6 +38,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToAdventureChapters @@ -100,7 +100,7 @@ private fun AdventureListContent( modifier = modifier, topBar = { val shadow = rememberAnimatedShadow(gridState) - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = shadow.value), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureStoriesScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureStoriesScreen.kt index 075933b..8ccd6e3 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureStoriesScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureStoriesScreen.kt @@ -22,12 +22,10 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow @@ -37,11 +35,11 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToAdventureDetail import com.pixelized.rplexicon.ui.theme.LexiconTheme -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @@ -94,7 +92,7 @@ private fun AdventureChapterContent( modifier = modifier, topBar = { val shadow = rememberAnimatedShadow(lazyListState) - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = shadow.value), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/detail/AdventureDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/detail/AdventureDetailScreen.kt index 72263fc..56d68e2 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/detail/AdventureDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/detail/AdventureDetailScreen.kt @@ -23,9 +23,9 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableFloatState +import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -38,6 +38,7 @@ import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.NestedScrollConnection @@ -50,10 +51,12 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.images.BackgroundImage import com.pixelized.rplexicon.ui.composable.images.rememberBackgroundGradient import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost @@ -96,6 +99,14 @@ private data class TitleLayoutInfo( val height: Float, ) +@Stable +private data class AnimatedValues( + val topAppBarAlpha: State, + val topAppBarColor: State, + val topBarElevation: State, + val backgroundAlpha: State, +) + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun AdventureDetailContent( @@ -115,7 +126,9 @@ private fun AdventureDetailContent( onBack: () -> Unit, onRefresh: () -> Unit, ) { - val nestedScrollOffset = rememberSaveable { mutableFloatStateOf(0f) } + val nestedScrollOffset = rememberSaveable { + mutableFloatStateOf(0f) + } val titleLayoutInfo = rememberSaveable(stateSaver = TitleLayoutInfoSaver) { mutableStateOf(null) } @@ -131,25 +144,10 @@ private fun AdventureDetailContent( } } } - val topAppBarAlpha = remember { - derivedStateOf { - titleLayoutInfo.value?.let { info -> - when { - -nestedScrollOffset.floatValue < info.position -> 0f - -nestedScrollOffset.floatValue > info.position + info.height -> 1f - info.height != 0f -> (-nestedScrollOffset.floatValue - info.position) / info.height - else -> 0f - }.coerceIn(minimumValue = 0f, maximumValue = 1f) - } ?: 0f - } - } - val backgroundAlpha = remember { - derivedStateOf { - titleLayoutInfo.value?.let { info -> - (info.position + nestedScrollOffset.floatValue) / info.position - } ?: 0f - } - } + val animatedValues = rememberAnimatedValues( + nestedScrollOffset = nestedScrollOffset, + titleLayoutInfo = titleLayoutInfo, + ) Box( modifier = modifier, @@ -159,7 +157,7 @@ private fun AdventureDetailContent( modifier = Modifier .fillMaxWidth() .aspectRatio(ratio = 1f) - .alpha(alpha = backgroundAlpha.value) + .alpha(alpha = animatedValues.backgroundAlpha.value) .offset { IntOffset(x = 0, y = nestedScrollOffset.floatValue.toInt() / 2) }, colorFilter = null, background = rememberBackgroundGradient(0f, 0f, 0.1f, 0.7f, 1f), @@ -170,10 +168,9 @@ private fun AdventureDetailContent( Column( modifier = Modifier.nestedScroll(connection = nestedScrollConnexion) ) { - TopAppBar( - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent, - ), + Toolbar( + modifier = Modifier.shadow(elevation = animatedValues.topBarElevation.value), + backgroundColor = animatedValues.topAppBarColor.value, navigationIcon = { IconButton(onClick = onBack) { Icon( @@ -184,7 +181,7 @@ private fun AdventureDetailContent( }, title = { val alpha = animateFloatAsState( - targetValue = topAppBarAlpha.value, + targetValue = animatedValues.topAppBarAlpha.value, label = "TopAppBarLabel", ) Text( @@ -242,6 +239,63 @@ private fun AdventureDetailContent( } } +@Composable +private fun rememberAnimatedValues( + nestedScrollOffset: MutableFloatState, + titleLayoutInfo: MutableState, +): AnimatedValues { + val colorScheme = MaterialTheme.colorScheme + + val topAppBarAlpha = remember(titleLayoutInfo, nestedScrollOffset) { + derivedStateOf { + titleLayoutInfo.value?.let { info -> + when { + -nestedScrollOffset.floatValue < info.position -> 0f + -nestedScrollOffset.floatValue > info.position + info.height -> 1f + info.height != 0f -> (-nestedScrollOffset.floatValue - info.position) / info.height + else -> 0f + }.coerceIn(minimumValue = 0f, maximumValue = 1f) + } ?: 0f + } + } + val backgroundAlpha = remember(titleLayoutInfo, nestedScrollOffset) { + derivedStateOf { + titleLayoutInfo.value?.let { info -> + (info.position + nestedScrollOffset.floatValue) / info.position + }?.coerceIn(minimumValue = 0f, maximumValue = 1f) ?: 0f + } + } + val topAppBarColor = remember(backgroundAlpha, colorScheme) { + derivedStateOf { + when (backgroundAlpha.value) { + 0f -> colorScheme.surface + else -> Color.Transparent + } + } + } + val topBarElevation = remember(backgroundAlpha) { + derivedStateOf { + when (backgroundAlpha.value) { + 0f -> 4.dp + else -> 0.dp + } + } + } + return remember( + topAppBarAlpha, + backgroundAlpha, + topAppBarColor, + topBarElevation, + ) { + AnimatedValues( + topAppBarAlpha = topAppBarAlpha, + backgroundAlpha = backgroundAlpha, + topAppBarColor = topAppBarColor, + topBarElevation = topBarElevation, + ) + } +} + private val TitleLayoutInfoSaver: Saver = run { val positionKey = "position" val heightKey = "height" 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 4f82d7b..99f4b97 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 @@ -36,7 +36,6 @@ import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -60,6 +59,7 @@ import com.pixelized.rplexicon.NO_WINDOW_INSETS import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.KeepOnScreen import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog import com.pixelized.rplexicon.ui.composable.edit.HandleSkillEditDialog import com.pixelized.rplexicon.ui.composable.error.HandleFetchError @@ -279,7 +279,7 @@ private fun CharacterSheetContent( containerColor = Color.Transparent, contentWindowInsets = NO_WINDOW_INSETS, topBar = { - TopAppBar( + Toolbar( navigationIcon = { IconButton(onClick = onBack) { Icon( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt index cb1408c..1f45493 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt @@ -3,9 +3,11 @@ package com.pixelized.rplexicon.ui.screens.lexicon.detail import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow @@ -16,6 +18,9 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.LazyRow @@ -26,20 +31,24 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue 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.alpha import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -47,20 +56,22 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.images.AsyncImage import com.pixelized.rplexicon.ui.composable.images.BackgroundImage import com.pixelized.rplexicon.ui.composable.images.FullScreenImageHandler import com.pixelized.rplexicon.ui.composable.images.FullScreenImageViewModel +import com.pixelized.rplexicon.ui.composable.images.rememberBackgroundGradient import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.annotate import com.pixelized.rplexicon.utilitary.dropCapRegex import com.pixelized.rplexicon.utilitary.extentions.lexicon -import com.pixelized.rplexicon.utilitary.extentions.modifier.scrollOffset import com.pixelized.rplexicon.utilitary.extentions.string.searchCriterion import com.pixelized.rplexicon.utilitary.highlightRegex import com.pixelized.rplexicon.utilitary.styleWith @@ -79,6 +90,14 @@ data class LexiconDetailUio( val tags: String?, ) +@Stable +private data class AnimatedValues( + val topAppBarAlpha: State, + val topAppBarColor: State, + val topBarElevation: State, + val backgroundAlpha: State, +) + @Composable fun LexiconDetailScreen( viewModel: LexiconDetailViewModel = hiltViewModel(), @@ -90,7 +109,9 @@ fun LexiconDetailScreen( modifier = Modifier.fillMaxSize(), ) { LexiconDetailContent( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding(), lexicon = viewModel.character, highlight = viewModel.highlight, haveCharacterSheet = viewModel.haveCharacterSheet, @@ -120,63 +141,42 @@ private fun LexiconDetailContent( val typography = MaterialTheme.lexicon.typography val highlightRegex = remember(highlight) { highlightRegex(terms = highlight.searchCriterion()) } val dropCapRegex = remember { dropCapRegex() } + val paddingTop = + with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.toDp() } + 56.dp + val animatedValues = rememberAnimatedValues( + scrollState = state, + delta = paddingTop, + ) val item = lexicon.value - val backgroundUri = remember(item) { - item?.portrait?.firstOrNull() + val background = remember(item) { + mutableStateOf( + item?.portrait?.firstOrNull() + ) } - Scaffold( + Box( modifier = modifier, - containerColor = Color.Transparent, - topBar = { - TopAppBar( - modifier = Modifier.shadow(elevation = 4.dp), - navigationIcon = { - IconButton(onClick = onBack) { - Icon( - painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24), - contentDescription = null - ) - } - }, - actions = { - AnimatedVisibility(visible = haveCharacterSheet.value) { - IconButton(onClick = { item?.name?.let(onCharacterSheet) }) { - Icon( - painter = painterResource(id = R.drawable.ic_d20_24), - contentDescription = null - ) - } - } - }, - title = { - Text(text = stringResource(id = R.string.lexicon_detail__title)) - }, + ) { + BackgroundPortrait( + background = background, + animatedValues = animatedValues, + state = state, + ) + Column { + AnimatedToolBar( + animatedValues = animatedValues, + onBack = onBack, + haveCharacterSheet = haveCharacterSheet, + item = item, + onCharacterSheet = onCharacterSheet ) - }, - ) { paddingValues -> - Surface( - modifier = Modifier.padding(paddingValues = paddingValues), - ) { - backgroundUri?.let { uri -> - BackgroundImage( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(ratio = 1f) - .scrollOffset(scrollState = state) { -it / 2 }, - model = uri, - ) - } Column( modifier = Modifier - .verticalScroll(state) - .padding( - top = when (backgroundUri) { - null -> 16.dp - else -> MaterialTheme.lexicon.dimens.detailPadding - }, - bottom = 16.dp, + .verticalScroll(state = state) + .backgroundPadding( + background = background, + padding = paddingTop, ), verticalArrangement = Arrangement.spacedBy(space = 4.dp), ) { @@ -360,6 +360,85 @@ private fun LexiconDetailContent( } } +private fun Modifier.backgroundPadding( + background: MutableState, + padding: Dp, +): Modifier { + return this.padding( + top = when (background.value) { + null -> 16.dp + else -> padding + }, + bottom = 16.dp, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AnimatedToolBar( + modifier: Modifier = Modifier, + animatedValues: AnimatedValues, + onBack: () -> Unit, + haveCharacterSheet: State, + item: LexiconDetailUio?, + onCharacterSheet: (String) -> Unit +) { + Toolbar( + modifier = modifier.shadow(elevation = animatedValues.topBarElevation.value), + backgroundColor = animatedValues.topAppBarColor.value, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24), + contentDescription = null + ) + } + }, + actions = { + AnimatedVisibility(visible = haveCharacterSheet.value) { + IconButton(onClick = { item?.name?.let(onCharacterSheet) }) { + Icon( + painter = painterResource(id = R.drawable.ic_d20_24), + contentDescription = null + ) + } + } + }, + title = { + val alpha = animateFloatAsState( + targetValue = animatedValues.topAppBarAlpha.value, + label = "TopAppBarLabel", + ) + Text( + modifier = Modifier.alpha(alpha = alpha.value), + text = stringResource(id = R.string.lexicon_detail__title), + ) + }, + ) +} + +@Composable +private fun BackgroundPortrait( + modifier: Modifier = Modifier, + background: MutableState, + animatedValues: AnimatedValues, + state: ScrollState +) { + background.value?.let { uri -> + BackgroundImage( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(ratio = 1f) + .alpha(alpha = animatedValues.backgroundAlpha.value) + .offset { IntOffset(x = 0, y = -state.value / 2) } + .then(other = modifier), + colorFilter = null, + background = rememberBackgroundGradient(0f, 0f, 0.1f, 0.7f, 1f), + model = uri, + ) + } +} + @Composable private fun rememberPortraitWidth(): Dp { val configuration = LocalConfiguration.current @@ -373,6 +452,64 @@ private fun rememberPortraitWidth(): Dp { } } +@Composable +private fun rememberAnimatedValues( + scrollState: ScrollState, + delta: Dp, +): AnimatedValues { + val density = LocalDensity.current + val deltaPx by remember { + derivedStateOf { + with(density) { delta.toPx() } + } + } + val colorScheme = MaterialTheme.colorScheme + + val backgroundAlpha = remember(scrollState, delta) { + derivedStateOf { + ((deltaPx - scrollState.value.toFloat()) / deltaPx) + .coerceIn(minimumValue = 0f, maximumValue = 1f) + } + } + val topAppBarAlpha = remember(scrollState, delta) { + derivedStateOf { + when (backgroundAlpha.value) { + 0f -> 1f + else -> 0f + } + } + } + val topAppBarColor = remember(backgroundAlpha, colorScheme) { + derivedStateOf { + when (backgroundAlpha.value) { + 0f -> colorScheme.surface + else -> Color.Transparent + } + } + } + val topBarElevation = remember(backgroundAlpha) { + derivedStateOf { + when (backgroundAlpha.value) { + 0f -> 4.dp + else -> 0.dp + } + } + } + return remember( + topAppBarAlpha, + backgroundAlpha, + topAppBarColor, + topBarElevation, + ) { + AnimatedValues( + topAppBarAlpha = topAppBarAlpha, + backgroundAlpha = backgroundAlpha, + topAppBarColor = topAppBarColor, + topBarElevation = topBarElevation, + ) + } +} + @Composable @Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 980) @Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 980) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt index 7eda265..c1b6ce7 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -45,6 +44,7 @@ import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.CategoryHeader import com.pixelized.rplexicon.ui.composable.FloatingActionButton import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.error.HandleFetchError import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost @@ -130,7 +130,7 @@ private fun LexiconScreenContent( containerColor = Color.Transparent, topBar = { val shadow = rememberAnimatedShadow(lazyListState) - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = shadow.value), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt index 7103af4..1b1c58c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt @@ -1,7 +1,6 @@ package com.pixelized.rplexicon.ui.screens.location.detail import android.content.res.Configuration -import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -39,7 +38,6 @@ import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable @@ -76,6 +74,7 @@ import androidx.compose.ui.unit.min import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalSnack import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.images.AsyncImage import com.pixelized.rplexicon.ui.composable.images.BackgroundImage import com.pixelized.rplexicon.ui.composable.images.FullScreenImageHandler @@ -259,7 +258,7 @@ private fun LocationContent( Scaffold( modifier = modifier, topBar = { - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = 4.dp), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt index 77f337e..909266f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/list/LocationScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -43,6 +42,7 @@ import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.CategoryHeader import com.pixelized.rplexicon.ui.composable.FloatingActionButton import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.error.HandleFetchError import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost @@ -121,7 +121,7 @@ private fun LocationContent( containerColor = Color.Transparent, topBar = { val shadow = rememberAnimatedShadow(lazyListState) - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = shadow.value), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt index 5d26b5b..4709b43 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/detail/QuestDetailScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -53,6 +52,7 @@ 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.Toolbar import com.pixelized.rplexicon.ui.composable.images.AsyncImage import com.pixelized.rplexicon.ui.composable.images.BackgroundImage import com.pixelized.rplexicon.ui.composable.images.FullScreenImageHandler @@ -139,7 +139,7 @@ private fun QuestDetailContent( modifier = modifier, containerColor = Color.Transparent, topBar = { - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = 4.dp), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt index fae9f44..6c67e18 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf @@ -46,6 +45,7 @@ import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ui.composable.CategoryHeader import com.pixelized.rplexicon.ui.composable.FloatingActionButton import com.pixelized.rplexicon.ui.composable.Loader +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.error.HandleFetchError import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost @@ -134,7 +134,7 @@ private fun QuestListContent( containerColor = Color.Transparent, topBar = { val shadow = rememberAnimatedShadow(lazyListState) - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = shadow.value), navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt index c693afc..a4f2639 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt @@ -38,8 +38,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -74,9 +72,11 @@ import com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.NO_WINDOW_INSETS import com.pixelized.rplexicon.R import com.pixelized.rplexicon.data.model.DiceThrow +import com.pixelized.rplexicon.isInDarkTheme import com.pixelized.rplexicon.ui.composable.BlurredOverlayHostState import com.pixelized.rplexicon.ui.composable.CategoryHeader import com.pixelized.rplexicon.ui.composable.ModalNavigationDrawer +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItem import com.pixelized.rplexicon.ui.screens.character.composable.dialogs.AlterationDetailDialog import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationGroupUio @@ -85,7 +85,6 @@ import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCard import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio import com.pixelized.rplexicon.ui.screens.rolls.preview.rememberRollAlterations -import com.pixelized.rplexicon.isInDarkTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.extentions.lexicon import kotlinx.coroutines.launch @@ -259,8 +258,8 @@ private fun RollOverlayContent( containerColor = Color.Transparent, contentWindowInsets = NO_WINDOW_INSETS, topBar = { - TopAppBar( - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + Toolbar( + backgroundColor = Color.Transparent, title = { }, actions = { IconButton(onClick = onClose) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt index 24d0a5e..ab6ee09 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/settings/SettingsScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -29,6 +28,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R import com.pixelized.rplexicon.ThemeViewModel +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.rootOption @@ -88,7 +88,7 @@ private fun SettingsContent( modifier = modifier, topBar = { val shadow = rememberAnimatedShadow(scrollState = scrollState) - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = shadow.value), navigationIcon = { IconButton(onClick = onBack) { @@ -105,7 +105,7 @@ private fun SettingsContent( }, content = { paddings -> Surface( - modifier = Modifier.verticalScroll(state = scrollState) + modifier = Modifier.verticalScroll(state = scrollState), ) { Column( modifier = Modifier.padding(paddingValues = paddings), diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt index 0b4e2bd..d865432 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -43,8 +42,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.composable.images.BackgroundImage +import com.pixelized.rplexicon.ui.composable.Toolbar import com.pixelized.rplexicon.ui.composable.error.HandleFetchError +import com.pixelized.rplexicon.ui.composable.images.BackgroundImage import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.annotate @@ -109,7 +109,7 @@ private fun SpellDetailContent( modifier = modifier, containerColor = Color.Transparent, topBar = { - TopAppBar( + Toolbar( modifier = Modifier.shadow(elevation = 4.dp), navigationIcon = { IconButton(onClick = onBack) {