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 87835a8a..08e54bfe 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,16 +1,17 @@ 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 @@ -18,23 +19,24 @@ 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.platform.LocalDensity +import androidx.compose.ui.graphics.lerp 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 @@ -58,102 +60,93 @@ fun AnimatedTopAppBar( content3Visibility: Boolean? = null, content3NavigationIcon: @Composable () -> Unit = {}, content3Title: @Composable () -> Unit = {}, - content3Actions: @Composable RowScope.() -> Unit = {} + content3Actions: @Composable RowScope.() -> Unit = {}, + + customContent: @Composable ColumnScope.() -> Unit = {} ) { - Box(modifier = Modifier.fillMaxWidth()) { - val density = LocalDensity.current - val statusBarPadding = with(density) { - WindowInsets.statusBars.getTop(density).toDp() - } + 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 + ) - if (containerColor != Color.Transparent) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(64.dp + statusBarPadding) - .background( - if (isTopBarScrolled == true) scrolledContainerColor - else containerColor + Column( + Modifier + .fillMaxWidth() + .background(animatedContainerColor) + ) { + Box(modifier = Modifier.fillMaxWidth()) { + 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 = if (isTopBarScrolled != null) Color.Transparent + else containerColor, + scrolledContainerColor = scrolledContainerColor ) - ) - } + ) + } - val scrollableContainerColor by remember(isTopBarScrolled) { - derivedStateOf { - if (isTopBarScrolled == true) { - scrolledContainerColor - } else { - containerColor + 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 = if (isTopBarScrolled != null) Color.Transparent + else containerColor, + scrolledContainerColor = scrolledContainerColor + ) + ) } } } - - 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 = 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 = 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 = animatedContainerColor, - 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 ab021289..ca0792f1 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,7 +27,6 @@ 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 @@ -35,7 +34,6 @@ 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 @@ -44,7 +42,6 @@ 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 @@ -109,7 +106,7 @@ fun BookInfoScreenRoot(screen: Screen.BookInfo) { ) } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class) @Composable private fun BookInfoScreen( state: State, @@ -120,7 +117,6 @@ private fun BookInfoScreen( onHistoryEvent: (HistoryEvent) -> Unit ) { val context = LocalContext.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val listState = rememberLazyListState() val snackbarState = remember { SnackbarHostState() } val refreshState = rememberPullRefreshState( @@ -179,8 +175,7 @@ private fun BookInfoScreen( .fillMaxSize() .imePadding() .windowInsetsPadding(WindowInsets.navigationBars) - .pullRefresh(refreshState) - .nestedScroll(scrollBehavior.nestedScrollConnection), + .pullRefresh(refreshState), containerColor = MaterialTheme.colorScheme.surface, topBar = { BookInfoTopBar( @@ -189,7 +184,6 @@ 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 300a531d..bcf394e8 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,13 +12,11 @@ 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 @@ -42,7 +40,6 @@ 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]. */ @@ -54,7 +51,6 @@ fun BookInfoTopBar( onLibraryEvent: (LibraryEvent) -> Unit, onHistoryEvent: (HistoryEvent) -> Unit, onNavigate: OnNavigate, - scrollBehavior: TopAppBarScrollBehavior, listState: LazyListState, snackbarState: SnackbarHostState ) { @@ -62,9 +58,9 @@ fun BookInfoTopBar( val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } } AnimatedTopAppBar( - containerColor = Color.Transparent, - scrollBehavior = scrollBehavior, - isTopBarScrolled = null, + containerColor = MaterialTheme.colorScheme.surface.copy(0f), + scrollBehavior = null, + isTopBarScrolled = listState.canScrollBackward, 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 7671ab3e..886b4793 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,12 +33,14 @@ 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 ) { @@ -113,7 +115,7 @@ fun LibraryTabRow( text = tabItem.second.toString(), modifier = Modifier .background( - MaterialTheme.colorScheme.surfaceContainer, + itemCountBackgroundColor, 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 5251e7c5..dd0113e9 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,5 +1,8 @@ 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 @@ -168,12 +171,20 @@ 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