diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt index 08e54bfe..87835a8a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt @@ -1,17 +1,16 @@ package ua.acclorite.book_story.presentation.components +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.statusBars import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -19,24 +18,23 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable +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.graphics.lerp +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R /** - * Animated top app bar. - * Has up to 3 different top bars inside it. Follow [TopAppBar] for more info. + * Animated top app bar. Has up to 3 different top bars inside it. Follow [TopAppBar] for more info. * - * @param containerColor Container color of the TopBar. - * @param scrolledContainerColor Scrolled container color of the TopBar. - * @param scrollBehavior [TopAppBarScrollBehavior]. * @param isTopBarScrolled Whether isScrolled state should be forced or not. * @param content1Visibility Whether first top app bar should be shown. * @param content2Visibility Whether second top app bar should be shown. * @param content3Visibility Whether third top app bar should be shown. - * @param customContent Custom content below [TopAppBar]. */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -60,93 +58,102 @@ fun AnimatedTopAppBar( content3Visibility: Boolean? = null, content3NavigationIcon: @Composable () -> Unit = {}, content3Title: @Composable () -> Unit = {}, - content3Actions: @Composable RowScope.() -> Unit = {}, - - customContent: @Composable ColumnScope.() -> Unit = {} + content3Actions: @Composable RowScope.() -> Unit = {} ) { - val animatedContainerColor = lerp( - containerColor, - scrolledContainerColor, - animateFloatAsState( - if (isTopBarScrolled == true) 1f else 0f, - animationSpec = spring( - stiffness = Spring.StiffnessMediumLow - ), - label = stringResource(id = R.string.top_app_bar_anim_content_desc) - ).value - ) + Box(modifier = Modifier.fillMaxWidth()) { + val density = LocalDensity.current + val statusBarPadding = with(density) { + WindowInsets.statusBars.getTop(density).toDp() + } - Column( - Modifier - .fillMaxWidth() - .background(animatedContainerColor) - ) { - Box(modifier = Modifier.fillMaxWidth()) { + if (containerColor != Color.Transparent) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(64.dp + statusBarPadding) + .background( + if (isTopBarScrolled == true) scrolledContainerColor + else containerColor + ) + ) + } + + val scrollableContainerColor by remember(isTopBarScrolled) { + derivedStateOf { + if (isTopBarScrolled == true) { + scrolledContainerColor + } else { + containerColor + } + } + } + + val animatedContainerColor by animateColorAsState( + targetValue = scrollableContainerColor, + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + label = stringResource(id = R.string.top_app_bar_anim_content_desc) + ) + + CustomAnimatedVisibility( + visible = content1Visibility ?: true, + enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)), + exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)) + ) { + TopAppBar( + windowInsets = WindowInsets.statusBars, + navigationIcon = content1NavigationIcon, + title = content1Title, + actions = content1Actions, + scrollBehavior = scrollBehavior, + modifier = Modifier.fillMaxWidth(), + colors = TopAppBarDefaults.topAppBarColors( + containerColor = animatedContainerColor, + scrolledContainerColor = scrolledContainerColor + ) + ) + } + + if (content2Visibility != null) { CustomAnimatedVisibility( - visible = content1Visibility ?: true, + visible = content2Visibility, enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)), exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)) ) { TopAppBar( windowInsets = WindowInsets.statusBars, - navigationIcon = content1NavigationIcon, - title = content1Title, - actions = content1Actions, + navigationIcon = content2NavigationIcon, + title = content2Title, + actions = content2Actions, scrollBehavior = scrollBehavior, modifier = Modifier.fillMaxWidth(), colors = TopAppBarDefaults.topAppBarColors( - containerColor = if (isTopBarScrolled != null) Color.Transparent - else containerColor, + containerColor = animatedContainerColor, scrolledContainerColor = scrolledContainerColor ) ) } + } - if (content2Visibility != null) { - CustomAnimatedVisibility( - visible = content2Visibility, - enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)), - exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)) - ) { - TopAppBar( - windowInsets = WindowInsets.statusBars, - navigationIcon = content2NavigationIcon, - title = content2Title, - actions = content2Actions, - scrollBehavior = scrollBehavior, - modifier = Modifier.fillMaxWidth(), - colors = TopAppBarDefaults.topAppBarColors( - containerColor = if (isTopBarScrolled != null) Color.Transparent - else containerColor, - scrolledContainerColor = scrolledContainerColor - ) + if (content3Visibility != null) { + CustomAnimatedVisibility( + visible = content3Visibility, + enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)), + exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)) + ) { + TopAppBar( + windowInsets = WindowInsets.statusBars, + navigationIcon = content3NavigationIcon, + title = content3Title, + actions = content3Actions, + scrollBehavior = scrollBehavior, + modifier = Modifier.fillMaxWidth(), + colors = TopAppBarDefaults.topAppBarColors( + containerColor = animatedContainerColor, + scrolledContainerColor = scrolledContainerColor ) - } - } - - if (content3Visibility != null) { - CustomAnimatedVisibility( - visible = content3Visibility, - enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)), - exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)) - ) { - TopAppBar( - windowInsets = WindowInsets.statusBars, - navigationIcon = content3NavigationIcon, - title = content3Title, - actions = content3Actions, - scrollBehavior = scrollBehavior, - modifier = Modifier.fillMaxWidth(), - colors = TopAppBarDefaults.topAppBarColors( - containerColor = if (isTopBarScrolled != null) Color.Transparent - else containerColor, - scrolledContainerColor = scrolledContainerColor - ) - ) - } + ) } } - customContent() } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt index ca0792f1..ab021289 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon @@ -34,6 +35,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -42,6 +44,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -106,7 +109,7 @@ fun BookInfoScreenRoot(screen: Screen.BookInfo) { ) } -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable private fun BookInfoScreen( state: State, @@ -117,6 +120,7 @@ private fun BookInfoScreen( onHistoryEvent: (HistoryEvent) -> Unit ) { val context = LocalContext.current + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val listState = rememberLazyListState() val snackbarState = remember { SnackbarHostState() } val refreshState = rememberPullRefreshState( @@ -175,7 +179,8 @@ private fun BookInfoScreen( .fillMaxSize() .imePadding() .windowInsetsPadding(WindowInsets.navigationBars) - .pullRefresh(refreshState), + .pullRefresh(refreshState) + .nestedScroll(scrollBehavior.nestedScrollConnection), containerColor = MaterialTheme.colorScheme.surface, topBar = { BookInfoTopBar( @@ -184,6 +189,7 @@ private fun BookInfoScreen( onLibraryEvent = onLibraryEvent, onHistoryEvent = onHistoryEvent, onNavigate = onNavigate, + scrollBehavior = scrollBehavior, listState = listState, snackbarState = snackbarState ) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt index bcf394e8..300a531d 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoTopBar.kt @@ -12,11 +12,13 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import ua.acclorite.book_story.R @@ -40,6 +42,7 @@ import ua.acclorite.book_story.presentation.ui.Transitions * @param onLibraryEvent [LibraryEvent] callback. * @param onHistoryEvent [HistoryEvent] callback. * @param onNavigate Navigator callback. + * @param scrollBehavior [TopAppBarScrollBehavior]. * @param listState [LazyListState]. * @param snackbarState [SnackbarHostState]. */ @@ -51,6 +54,7 @@ fun BookInfoTopBar( onLibraryEvent: (LibraryEvent) -> Unit, onHistoryEvent: (HistoryEvent) -> Unit, onNavigate: OnNavigate, + scrollBehavior: TopAppBarScrollBehavior, listState: LazyListState, snackbarState: SnackbarHostState ) { @@ -58,9 +62,9 @@ fun BookInfoTopBar( val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } } AnimatedTopAppBar( - containerColor = MaterialTheme.colorScheme.surface.copy(0f), - scrollBehavior = null, - isTopBarScrolled = listState.canScrollBackward, + containerColor = Color.Transparent, + scrollBehavior = scrollBehavior, + isTopBarScrolled = null, content1NavigationIcon = { if ( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt index 886b4793..7671ab3e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt @@ -33,14 +33,12 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent * Tab row, either scrollable or static, depends on screen width. * * @param onEvent Event used to Scroll to Page. - * @param itemCountBackgroundColor Item count background color. * @param books The list of all books. * @param pagerState [PagerState]. */ @Composable fun LibraryTabRow( onEvent: (LibraryEvent) -> Unit, - itemCountBackgroundColor: Color, books: List, pagerState: PagerState ) { @@ -115,7 +113,7 @@ fun LibraryTabRow( text = tabItem.second.toString(), modifier = Modifier .background( - itemCountBackgroundColor, + MaterialTheme.colorScheme.surfaceContainer, MaterialTheme.shapes.medium ) .padding(horizontal = 8.dp, vertical = 4.dp), diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTopBar.kt index dd0113e9..5251e7c5 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTopBar.kt @@ -1,8 +1,5 @@ package ua.acclorite.book_story.presentation.screens.library.components -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.spring import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -171,20 +168,12 @@ fun LibraryTopBar( }, content3Actions = { MoreDropDown() - }, - customContent = { - LibraryTabRow( - onEvent = onEvent, - books = state.value.categorizedBooks, - itemCountBackgroundColor = animateColorAsState( - if (state.value.hasSelectedItems) MaterialTheme.colorScheme.surfaceContainerHighest - else MaterialTheme.colorScheme.surfaceContainer, - animationSpec = spring(stiffness = Spring.StiffnessMediumLow), - label = stringResource(id = R.string.top_app_bar_anim_content_desc) - ).value, - pagerState = pagerState - ) } ) + LibraryTabRow( + onEvent = onEvent, + books = state.value.categorizedBooks, + pagerState = pagerState + ) } } \ No newline at end of file