feat(ui): make top bars scroll aware

This commit is contained in:
2026-06-06 10:38:40 +00:00
parent 005b3249ff
commit 0bf968a42b
16 changed files with 157 additions and 58 deletions

View File

@@ -11,17 +11,22 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MediaDetailScaffold( fun MediaDetailScaffold(
imageUrl: String, imageUrl: String,
@@ -29,19 +34,22 @@ fun MediaDetailScaffold(
imageHeight: Dp = 320.dp, imageHeight: Dp = 320.dp,
contentOverlap: Dp = 24.dp, contentOverlap: Dp = 24.dp,
contentSpacing: Dp = 16.dp, contentSpacing: Dp = 16.dp,
topBar: @Composable () -> Unit = {}, topBar: @Composable (scrollBehavior: TopAppBarScrollBehavior) -> Unit = {},
heroContent: @Composable ColumnScope.(Modifier) -> Unit, heroContent: @Composable ColumnScope.(Modifier) -> Unit,
content: @Composable ColumnScope.(Modifier) -> Unit content: @Composable ColumnScope.(Modifier) -> Unit
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive() val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive()
val topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.nestedScroll(topBarScrollBehavior.nestedScrollConnection),
containerColor = scheme.background, containerColor = scheme.background,
topBar = { topBar = {
if (!isHomeMediaTransitionActive) { if (!isHomeMediaTransitionActive) {
topBar() topBar(topBarScrollBehavior)
} }
} }
) { innerPadding -> ) { innerPadding ->
@@ -86,4 +94,3 @@ fun MediaDetailScaffold(
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package hu.bbara.purefin.ui.screen.episode package hu.bbara.purefin.ui.screen.episode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -77,6 +78,7 @@ fun EpisodeScreen(
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun EpisodeScreenInternal( private fun EpisodeScreenInternal(
episode: Episode, episode: Episode,
@@ -89,10 +91,11 @@ private fun EpisodeScreenInternal(
MediaDetailScaffold( MediaDetailScaffold(
imageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), imageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY),
modifier = modifier, modifier = modifier,
topBar = { topBar = { scrollBehavior ->
EpisodeTopBar( EpisodeTopBar(
shortcut = topBarShortcut, shortcut = topBarShortcut,
onBack = onBack, onBack = onBack,
scrollBehavior = scrollBehavior
) )
}, },
heroContent = { _modifier -> heroContent = { _modifier ->

View File

@@ -12,9 +12,11 @@ import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Cast import androidx.compose.material.icons.outlined.Cast
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.VerticalDivider import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -47,10 +49,12 @@ internal sealed interface EpisodeTopBarShortcut {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
internal fun EpisodeTopBar( internal fun EpisodeTopBar(
shortcut: EpisodeTopBarShortcut?, shortcut: EpisodeTopBarShortcut?,
onBack: () -> Unit, onBack: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
DefaultTopBar( DefaultTopBar(
leftActions = { leftActions = {
@@ -93,7 +97,8 @@ internal fun EpisodeTopBar(
contentDescription = "More", contentDescription = "More",
onClick = { }) onClick = { })
}, },
withIcon = false withIcon = false,
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -2,19 +2,23 @@ package hu.bbara.purefin.ui.screen.home
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import hu.bbara.purefin.core.model.LibraryUiModel import hu.bbara.purefin.core.model.LibraryUiModel
import hu.bbara.purefin.core.model.MediaUiModel import hu.bbara.purefin.core.model.MediaUiModel
import hu.bbara.purefin.ui.screen.AppBottomBar import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.HomeContent import hu.bbara.purefin.ui.screen.home.components.HomeContent
import hu.bbara.purefin.ui.screen.home.components.HomeTopBar import hu.bbara.purefin.ui.screen.home.components.HomeTopBar
import hu.bbara.purefin.ui.screen.home.components.rememberDefaultTopBarScrollBehavior
import java.util.UUID import java.util.UUID
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeScreen( fun HomeScreen(
libraries: List<LibraryUiModel>, libraries: List<LibraryUiModel>,
@@ -39,9 +43,12 @@ fun HomeScreen(
onTabSelected: (Int) -> Unit, onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val topBarScrollBehavior = rememberDefaultTopBarScrollBehavior()
Scaffold( Scaffold(
modifier = modifier modifier = modifier
.fillMaxSize(), .fillMaxSize()
.nestedScroll(topBarScrollBehavior.nestedScrollConnection),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
topBar = { topBar = {
@@ -51,7 +58,8 @@ fun HomeScreen(
onCheckForUpdates = onCheckForUpdates, onCheckForUpdates = onCheckForUpdates,
isCheckingForUpdates = isCheckingForUpdates, isCheckingForUpdates = isCheckingForUpdates,
onSettingsClick = onSettingsClick, onSettingsClick = onSettingsClick,
onLogoutClick = onLogoutClick onLogoutClick = onLogoutClick,
scrollBehavior = topBarScrollBehavior
) )
}, },
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },

View File

@@ -7,19 +7,19 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -32,58 +32,58 @@ import hu.bbara.purefin.navigation.LocalNavSharedAnimatedVisibilityScope
import hu.bbara.purefin.navigation.LocalSharedTransitionScope import hu.bbara.purefin.navigation.LocalSharedTransitionScope
import hu.bbara.purefin.ui.common.image.PurefinLogo import hu.bbara.purefin.ui.common.image.PurefinLogo
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberDefaultTopBarScrollBehavior(): TopAppBarScrollBehavior =
TopAppBarDefaults.pinnedScrollBehavior()
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DefaultTopBar( fun DefaultTopBar(
leftActions: (@Composable RowScope.() -> Unit)? = null, leftActions: (@Composable RowScope.() -> Unit)? = null,
rightActions: (@Composable RowScope.() -> Unit)? = null, rightActions: (@Composable RowScope.() -> Unit)? = null,
withIcon: Boolean = true withIcon: Boolean = true,
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
Surface( TopAppBar(
color = Color.Transparent, title = {
contentColor = scheme.onSurface, if (withIcon) {
modifier = Modifier.fillMaxWidth() PurefinLogo(
) { modifier = Modifier.size(56.dp),
Row( )
modifier = Modifier }
.statusBarsPadding() },
.height(84.dp) navigationIcon = {
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (leftActions != null) { if (leftActions != null) {
Row( Row(
modifier = Modifier.padding(start = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
content = leftActions content = leftActions
) )
} }
if (withIcon) { },
Row( actions = {
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = when {
leftActions == null && rightActions != null -> Arrangement.Start
leftActions != null && rightActions == null -> Arrangement.Center
else -> Arrangement.Center
}
) {
PurefinLogo(
modifier = Modifier.size(56.dp),
)
}
}
if (rightActions != null) { if (rightActions != null) {
Row( Row(
modifier = Modifier.padding(end = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
content = rightActions content = rightActions
) )
} }
} },
} colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = scheme.background,
navigationIconContentColor = scheme.onSurface,
titleContentColor = scheme.onSurface,
actionIconContentColor = scheme.onSurface
),
scrollBehavior = scrollBehavior
)
} }
@Composable @Composable

View File

@@ -4,13 +4,16 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeTopBar( fun HomeTopBar(
onSearchClick: () -> Unit, onSearchClick: () -> Unit,
@@ -19,10 +22,12 @@ fun HomeTopBar(
isCheckingForUpdates: Boolean, isCheckingForUpdates: Boolean,
onSettingsClick: () -> Unit, onSettingsClick: () -> Unit,
onLogoutClick: () -> Unit, onLogoutClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
var isProfileMenuExpanded by remember { mutableStateOf(false) } var isProfileMenuExpanded by remember { mutableStateOf(false) }
DefaultTopBar( DefaultTopBar(
scrollBehavior = scrollBehavior,
rightActions = { rightActions = {
DefaultTopBarSearchButton(onClick = onSearchClick) DefaultTopBarSearchButton(onClick = onSearchClick)
DefaultTopBarIconButton( DefaultTopBarIconButton(

View File

@@ -29,6 +29,7 @@ import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Tune import androidx.compose.material.icons.outlined.Tune
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -38,6 +39,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -46,6 +48,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -64,6 +67,7 @@ import hu.bbara.purefin.navigation.LocalNavSharedAnimatedVisibilityScope
import hu.bbara.purefin.navigation.LocalSharedTransitionScope import hu.bbara.purefin.navigation.LocalSharedTransitionScope
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton
import hu.bbara.purefin.ui.screen.home.components.rememberDefaultTopBarScrollBehavior
import hu.bbara.purefin.ui.theme.AppTheme import hu.bbara.purefin.ui.theme.AppTheme
import java.util.UUID import java.util.UUID
@@ -126,7 +130,7 @@ fun SearchScreen(
) )
} }
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
private fun SearchScreenContent( private fun SearchScreenContent(
query: String, query: String,
@@ -142,13 +146,17 @@ private fun SearchScreenContent(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val topBarScrollBehavior = rememberDefaultTopBarScrollBehavior()
Scaffold( Scaffold(
topBar = { SearchHeader( topBar = { SearchHeader(
onBack = onBack onBack = onBack,
scrollBehavior = topBarScrollBehavior
) }, ) },
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.testTag(SearchScreenTag) .testTag(SearchScreenTag)
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.background(scheme.background) .background(scheme.background)
) { innerPadding -> ) { innerPadding ->
Column( Column(
@@ -216,10 +224,13 @@ private fun SearchResults(
} }
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun SearchHeader( private fun SearchHeader(
onBack: () -> Unit, onBack: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
DefaultTopBar( DefaultTopBar(
scrollBehavior = scrollBehavior,
leftActions = { leftActions = {
DefaultTopBarIconButton( DefaultTopBarIconButton(
imageVector = Icons.Outlined.ArrowBack, imageVector = Icons.Outlined.ArrowBack,

View File

@@ -2,15 +2,19 @@ package hu.bbara.purefin.ui.screen.libraries
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import hu.bbara.purefin.core.model.LibraryUiModel import hu.bbara.purefin.core.model.LibraryUiModel
import hu.bbara.purefin.ui.screen.AppBottomBar import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.rememberDefaultTopBarScrollBehavior
import hu.bbara.purefin.ui.screen.libraries.components.LibrariesContent import hu.bbara.purefin.ui.screen.libraries.components.LibrariesContent
import hu.bbara.purefin.ui.screen.libraries.components.LibrariesTopBar import hu.bbara.purefin.ui.screen.libraries.components.LibrariesTopBar
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LibrariesScreen( fun LibrariesScreen(
items: List<LibraryUiModel>, items: List<LibraryUiModel>,
@@ -21,13 +25,18 @@ fun LibrariesScreen(
onTabSelected: (Int) -> Unit, onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val topBarScrollBehavior = rememberDefaultTopBarScrollBehavior()
Scaffold( Scaffold(
modifier = modifier.fillMaxSize(), modifier = modifier
.fillMaxSize()
.nestedScroll(topBarScrollBehavior.nestedScrollConnection),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
topBar = { topBar = {
LibrariesTopBar( LibrariesTopBar(
onSearchClick = onSearchClick, onSearchClick = onSearchClick,
scrollBehavior = topBarScrollBehavior
) )
}, },
bottomBar = { bottomBar = {

View File

@@ -1,14 +1,19 @@
package hu.bbara.purefin.ui.screen.libraries.components package hu.bbara.purefin.ui.screen.libraries.components
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarSearchButton import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarSearchButton
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LibrariesTopBar( fun LibrariesTopBar(
onSearchClick: () -> Unit, onSearchClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
DefaultTopBar( DefaultTopBar(
scrollBehavior = scrollBehavior,
rightActions = { rightActions = {
DefaultTopBarSearchButton(onClick = onSearchClick) DefaultTopBarSearchButton(onClick = onSearchClick)
} }

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -17,6 +18,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@@ -28,8 +30,10 @@ import hu.bbara.purefin.core.model.SeriesUiModel
import hu.bbara.purefin.core.navigation.LibraryDto import hu.bbara.purefin.core.navigation.LibraryDto
import hu.bbara.purefin.ui.common.badge.WatchStateBadge import hu.bbara.purefin.ui.common.badge.WatchStateBadge
import hu.bbara.purefin.ui.common.card.MediaImageCard import hu.bbara.purefin.ui.common.card.MediaImageCard
import hu.bbara.purefin.ui.screen.home.components.rememberDefaultTopBarScrollBehavior
import hu.bbara.purefin.ui.screen.library.components.LibraryTopBar import hu.bbara.purefin.ui.screen.library.components.LibraryTopBar
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LibraryScreen( fun LibraryScreen(
library: LibraryDto, library: LibraryDto,
@@ -41,14 +45,18 @@ fun LibraryScreen(
} }
val libraryItems = viewModel.contents.collectAsStateWithLifecycle() val libraryItems = viewModel.contents.collectAsStateWithLifecycle()
val topBarScrollBehavior = rememberDefaultTopBarScrollBehavior()
Scaffold( Scaffold(
modifier = modifier.testTag(LibraryScreenTag), modifier = modifier
.testTag(LibraryScreenTag)
.nestedScroll(topBarScrollBehavior.nestedScrollConnection),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
topBar = { topBar = {
LibraryTopBar( LibraryTopBar(
onBack = { viewModel.onBack() } onBack = { viewModel.onBack() },
scrollBehavior = topBarScrollBehavior
) )
} }
) { innerPadding -> ) { innerPadding ->

View File

@@ -3,15 +3,20 @@ package hu.bbara.purefin.ui.screen.library.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.FilterAlt import androidx.compose.material.icons.outlined.FilterAlt
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LibraryTopBar( fun LibraryTopBar(
onBack: () -> Unit, onBack: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
DefaultTopBar( DefaultTopBar(
scrollBehavior = scrollBehavior,
leftActions = { leftActions = {
DefaultTopBarIconButton( DefaultTopBarIconButton(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack, imageVector = Icons.AutoMirrored.Outlined.ArrowBack,

View File

@@ -1,5 +1,6 @@
package hu.bbara.purefin.ui.screen.movie package hu.bbara.purefin.ui.screen.movie
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -63,6 +64,7 @@ fun MovieScreen(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun MovieScreenInternal( private fun MovieScreenInternal(
movie: Movie, movie: Movie,
@@ -74,8 +76,11 @@ private fun MovieScreenInternal(
MediaDetailScaffold( MediaDetailScaffold(
imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY), imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY),
modifier = modifier, modifier = modifier,
topBar = { topBar = { scrollBehavior ->
MovieTopBar(onBack = onBack) MovieTopBar(
onBack = onBack,
scrollBehavior = scrollBehavior
)
}, },
heroContent = { _modifier -> heroContent = { _modifier ->
MovieHeroContent( MovieHeroContent(

View File

@@ -11,8 +11,10 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Cast import androidx.compose.material.icons.outlined.Cast
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.VerticalDivider import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -34,9 +36,11 @@ import hu.bbara.purefin.ui.common.media.mediaPlayButtonText
import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
internal fun MovieTopBar( internal fun MovieTopBar(
onBack: () -> Unit, onBack: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
DefaultTopBar( DefaultTopBar(
leftActions = { leftActions = {
@@ -53,7 +57,8 @@ internal fun MovieTopBar(
contentDescription = "More", contentDescription = "More",
onClick = { }) onClick = { })
}, },
withIcon = false withIcon = false,
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -2,6 +2,7 @@ package hu.bbara.purefin.ui.screen.series
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -112,6 +113,7 @@ fun SeriesScreen(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun SeriesScreenInternal( private fun SeriesScreenInternal(
series: Series, series: Series,
@@ -148,8 +150,11 @@ private fun SeriesScreenInternal(
MediaDetailScaffold( MediaDetailScaffold(
imageUrl = ImageUrlBuilder.finishImageUrl(series.imageUrlPrefix, ArtworkKind.PRIMARY), imageUrl = ImageUrlBuilder.finishImageUrl(series.imageUrlPrefix, ArtworkKind.PRIMARY),
modifier = modifier, modifier = modifier,
topBar = { topBar = { scrollBehavior ->
SeriesTopBar(onBack = onBack) SeriesTopBar(
onBack = onBack,
scrollBehavior = scrollBehavior
)
}, },
heroContent = { _modifier -> heroContent = { _modifier ->
SeriesHeroContent( SeriesHeroContent(

View File

@@ -35,6 +35,7 @@ import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -77,10 +78,12 @@ import hu.bbara.purefin.ui.common.media.mediaPlayButtonText
import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress import hu.bbara.purefin.ui.common.media.mediaPlaybackProgress
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
internal fun SeriesTopBar( internal fun SeriesTopBar(
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
DefaultTopBar( DefaultTopBar(
leftActions = { leftActions = {
@@ -95,7 +98,8 @@ internal fun SeriesTopBar(
GhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { }) GhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
} }
}, },
withIcon = false withIcon = false,
scrollBehavior = scrollBehavior
) )
} }

View File

@@ -13,16 +13,19 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -36,6 +39,7 @@ import hu.bbara.purefin.core.settings.StringSetting
import hu.bbara.purefin.core.settings.VoidSetting import hu.bbara.purefin.core.settings.VoidSetting
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton import hu.bbara.purefin.ui.screen.home.components.DefaultTopBarIconButton
import hu.bbara.purefin.ui.screen.home.components.rememberDefaultTopBarScrollBehavior
import hu.bbara.purefin.ui.screen.settings.components.BooleanSettingItem import hu.bbara.purefin.ui.screen.settings.components.BooleanSettingItem
import hu.bbara.purefin.ui.screen.settings.components.DropdownSettingItem import hu.bbara.purefin.ui.screen.settings.components.DropdownSettingItem
import hu.bbara.purefin.ui.screen.settings.components.RangeSettingItem import hu.bbara.purefin.ui.screen.settings.components.RangeSettingItem
@@ -43,6 +47,7 @@ import hu.bbara.purefin.ui.screen.settings.components.ReadOnlySettingItem
import hu.bbara.purefin.ui.screen.settings.components.StringSettingItem import hu.bbara.purefin.ui.screen.settings.components.StringSettingItem
import hu.bbara.purefin.ui.screen.settings.components.VoidSettingItem import hu.bbara.purefin.ui.screen.settings.components.VoidSettingItem
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(), viewModel: SettingsViewModel = hiltViewModel(),
@@ -50,6 +55,7 @@ fun SettingsScreen(
) { ) {
val settingGroups by viewModel.settingGroups.collectAsStateWithLifecycle() val settingGroups by viewModel.settingGroups.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val topBarScrollBehavior = rememberDefaultTopBarScrollBehavior()
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
viewModel.snackbarMessages.collect { message -> viewModel.snackbarMessages.collect { message ->
@@ -58,12 +64,17 @@ fun SettingsScreen(
} }
Scaffold( Scaffold(
modifier = modifier.fillMaxSize(), modifier = modifier
.fillMaxSize()
.nestedScroll(topBarScrollBehavior.nestedScrollConnection),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = { topBar = {
SettingsTopBar(onBack = viewModel::onBack) SettingsTopBar(
onBack = viewModel::onBack,
scrollBehavior = topBarScrollBehavior
)
} }
) { innerPadding -> ) { innerPadding ->
LazyColumn( LazyColumn(
@@ -224,11 +235,14 @@ private fun <T> DropdownSettingOptionItem(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun SettingsTopBar( private fun SettingsTopBar(
onBack: () -> Unit onBack: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
DefaultTopBar( DefaultTopBar(
scrollBehavior = scrollBehavior,
leftActions = { leftActions = {
DefaultTopBarIconButton( DefaultTopBarIconButton(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack, imageVector = Icons.AutoMirrored.Outlined.ArrowBack,