diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt index c136efc..c1bd4ef 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt @@ -1,11 +1,17 @@ package com.bobbyesp.metadator.presentation import android.annotation.SuppressLint +import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBars import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Square import androidx.compose.material3.Icon @@ -19,6 +25,7 @@ import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarHost import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -26,6 +33,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -41,6 +49,7 @@ import com.bobbyesp.metadator.R import com.bobbyesp.metadator.model.ParcelableSong import com.bobbyesp.metadator.presentation.common.LocalDrawerState import com.bobbyesp.metadator.presentation.common.LocalNavController +import com.bobbyesp.metadator.presentation.common.LocalPlayerAwareWindowInsets import com.bobbyesp.metadator.presentation.common.LocalSnackbarHostState import com.bobbyesp.metadator.presentation.common.NavArgs import com.bobbyesp.metadator.presentation.common.Route @@ -95,123 +104,155 @@ fun Navigator() { .fillMaxSize() .background(MaterialTheme.colorScheme.background) ) { + val density = LocalDensity.current + val windowsInsets = WindowInsets.systemBars + + val bottomInset = with(density) { windowsInsets.getBottom(density).toDp() } val mediaPlayerSheetState = rememberDraggableBottomSheetState( dismissedBound = 0.dp, - collapsedBound = CollapsedPlayerHeight, + collapsedBound = bottomInset + CollapsedPlayerHeight, expandedBound = maxHeight, animationSpec = PlayerAnimationSpec, ) - ModalNavigationDrawer( - drawerState = drawerState, - gesturesEnabled = canOpenDrawer, - drawerContent = { - ModalDrawerSheet { - Text( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), - text = stringResource(id = R.string.app_name).uppercase(), - style = MaterialTheme.typography.headlineMedium.copy( - letterSpacing = 4.sp, - ), - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace - ) - routesToNavigate.forEachIndexed { _, route -> - val isSelected = currentRootRoute.value == route.route - NavigationDrawerItem( - label = { - Text(text = route.title?.let { stringResource(id = it) } ?: "") - }, - selected = isSelected, - onClick = { - if (isSelected) { - scope.launch { - drawerState.close() - } - return@NavigationDrawerItem - } else { - navController.navigate(route.route) { - popUpTo(Route.MainHost.route) { - saveState = true + + val targetBottom = if (!mediaPlayerSheetState.isDismissed) { + CollapsedPlayerHeight + bottomInset + } else { + bottomInset + } + + val animatedBottom by animateDpAsState( + targetValue = targetBottom, + label = "Animated bottom insets for player sheet" + ) + + val playerAwareWindowInsets = remember( + bottomInset, + mediaPlayerSheetState.isDismissed, + animatedBottom + ) { + val insetsBottom = animatedBottom + windowsInsets + .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) + .add(WindowInsets(bottom = insetsBottom)) + } + + + CompositionLocalProvider( + LocalPlayerAwareWindowInsets provides playerAwareWindowInsets + ) { + ModalNavigationDrawer( + drawerState = drawerState, + gesturesEnabled = canOpenDrawer, + drawerContent = { + ModalDrawerSheet { + Text( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + text = stringResource(id = R.string.app_name).uppercase(), + style = MaterialTheme.typography.headlineMedium.copy( + letterSpacing = 4.sp, + ), + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace + ) + routesToNavigate.forEachIndexed { _, route -> + val isSelected = currentRootRoute.value == route.route + NavigationDrawerItem( + label = { + Text(text = route.title?.let { stringResource(id = it) } ?: "") + }, + selected = isSelected, + onClick = { + if (isSelected) { + scope.launch { + drawerState.close() + } + return@NavigationDrawerItem + } else { + navController.navigate(route.route) { + popUpTo(Route.MainHost.route) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + scope.launch { + drawerState.close() } - launchSingleTop = true - restoreState = true - } - scope.launch { - drawerState.close() } - } - }, - icon = { - Icon( - imageVector = route.icon ?: Icons.Rounded.Square, - contentDescription = route.title?.let { stringResource(id = it) }) - }, - badge = { - - }, - modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + }, + icon = { + Icon( + imageVector = route.icon ?: Icons.Rounded.Square, + contentDescription = route.title?.let { stringResource(id = it) }) + }, + badge = { + + }, + modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + ) + } + } + }, + ) { + Scaffold(snackbarHost = { + SnackbarHost( + hostState = snackbarHostState + ) { dataReceived -> + Snackbar( + modifier = Modifier, + snackbarData = dataReceived, + containerColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface, ) } - } - }, - ) { - Scaffold(snackbarHost = { - SnackbarHost( - hostState = snackbarHostState - ) { dataReceived -> - Snackbar( - modifier = Modifier, - snackbarData = dataReceived, - containerColor = MaterialTheme.colorScheme.inverseSurface, - contentColor = MaterialTheme.colorScheme.inverseOnSurface, - ) - } - }) { - NavHost( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.Center), - navController = navController, - startDestination = Route.MetadatorNavigator.route, - route = Route.MainHost.route, - ) { - navigation( - startDestination = Route.MetadatorNavigator.Home.route, - route = Route.MetadatorNavigator.route + }) { + NavHost( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center), + navController = navController, + startDestination = Route.MetadatorNavigator.route, + route = Route.MainHost.route, ) { - animatedComposable(Route.MetadatorNavigator.Home.route) { - HomePage(viewModel = mediaStoreViewModel) + navigation( + startDestination = Route.MetadatorNavigator.Home.route, + route = Route.MetadatorNavigator.route + ) { + animatedComposable(Route.MetadatorNavigator.Home.route) { + HomePage(viewModel = mediaStoreViewModel) + } } - } - navigation( - startDestination = Route.MediaplayerNavigator.Mediaplayer.route, - route = Route.MediaplayerNavigator.route - ) { - animatedComposable(Route.MediaplayerNavigator.Mediaplayer.route) { - MediaplayerPage(mediaplayerViewModel, mediaPlayerSheetState) + navigation( + startDestination = Route.MediaplayerNavigator.Mediaplayer.route, + route = Route.MediaplayerNavigator.route + ) { + animatedComposable(Route.MediaplayerNavigator.Mediaplayer.route) { + MediaplayerPage(mediaplayerViewModel, mediaPlayerSheetState) + } } - } - navigation( - startDestination = Route.UtilitiesNavigator.TagEditor.route, - route = Route.UtilitiesNavigator.route - ) { - slideInVerticallyComposable( - route = Route.UtilitiesNavigator.TagEditor.route, - arguments = listOf(navArgument(NavArgs.TagEditorSelectedSong.key) { - type = TagEditorParcelableSongParamType - }) + navigation( + startDestination = Route.UtilitiesNavigator.TagEditor.route, + route = Route.UtilitiesNavigator.route ) { - val parcelableSongParcelable = - it.getParcelable(NavArgs.TagEditorSelectedSong.key) + slideInVerticallyComposable( + route = Route.UtilitiesNavigator.TagEditor.route, + arguments = listOf(navArgument(NavArgs.TagEditorSelectedSong.key) { + type = TagEditorParcelableSongParamType + }) + ) { + val parcelableSongParcelable = + it.getParcelable(NavArgs.TagEditorSelectedSong.key) - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() - ID3MetadataEditorPage( - viewModel = viewModel, - parcelableSong = parcelableSongParcelable!! - ) + ID3MetadataEditorPage( + viewModel = viewModel, + parcelableSong = parcelableSongParcelable!! + ) + } } } } diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/common/CompositionLocals.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/common/CompositionLocals.kt index df44848..a473fda 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/common/CompositionLocals.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/common/CompositionLocals.kt @@ -1,6 +1,7 @@ package com.bobbyesp.metadator.presentation.common import android.os.Build +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue import androidx.compose.material3.SnackbarHostState @@ -50,6 +51,8 @@ val LocalDrawerState = compositionLocalOf { error("No Drawer State has been provided") } val LocalMediaplayerConnection = compositionLocalOf { error("No Media Player Service Connection handler has been provided") } +val LocalPlayerAwareWindowInsets = + compositionLocalOf { error("No WindowInsets provided") } @OptIn(ExperimentalMaterialNavigationApi::class) @Composable diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/MediaplayerSheet.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/MediaplayerSheet.kt index b4af75d..9049381 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/MediaplayerSheet.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/MediaplayerSheet.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -162,7 +163,13 @@ private fun MediaplayerExpandedContent( } Surface( - modifier = modifier.fillMaxSize(), + modifier = modifier + .fillMaxSize() + .padding( + WindowInsets.systemBars + .only(WindowInsetsSides.Horizontal) + .asPaddingValues() + ), color = MaterialTheme.colorScheme.surfaceContainer, ) { when (config.orientation) { @@ -448,7 +455,6 @@ fun MiniplayerContent( playingSong: MediaMetadata, isPlaying: Boolean = false, songProgress: Float = 0f, - imageModifier: Modifier = Modifier, onPlayPause: () -> Unit = {} ) { val transitionState = remember { MutableTransitionState(playingSong) } @@ -479,7 +485,7 @@ fun MiniplayerContent( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ArtworkAsyncImage( - modifier = imageModifier + modifier = Modifier .size(52.dp) .clip(MaterialTheme.shapes.extraSmall), artworkPath = songCardArtworkUri diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerPage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerPage.kt index c619318..b6f3ee8 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerPage.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerPage.kt @@ -1,27 +1,21 @@ package com.bobbyesp.metadator.presentation.pages.mediaplayer -import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.coerceIn import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.bobbyesp.metadator.presentation.common.LocalPlayerAwareWindowInsets import com.bobbyesp.metadator.presentation.components.cards.songs.HorizontalSongCard -import com.bobbyesp.metadator.presentation.components.others.CollapsedPlayerHeight import com.bobbyesp.metadator.presentation.components.others.MediaplayerSheet import com.bobbyesp.ui.components.bottomsheet.draggable.DraggableBottomSheetState import my.nanihadesuka.compose.LazyColumnScrollbar @@ -36,22 +30,14 @@ fun MediaplayerPage( val songs = viewModel.songsFlow.collectAsStateWithLifecycle(initialValue = emptyList()).value - val bottomPadding by animateDpAsState( - targetValue = (if (mediaPlayerSheetState.isDismissed) 0.dp else CollapsedPlayerHeight - 16.dp).coerceIn( - 0.dp, - CollapsedPlayerHeight - ), - label = "MediaplayerPage bottom padding animation" - ) + val insets = LocalPlayerAwareWindowInsets.current Box( modifier = Modifier.fillMaxSize() ) { Scaffold( modifier = Modifier.fillMaxSize(), - contentWindowInsets = WindowInsets.systemBars.add( - WindowInsets(bottom = bottomPadding) - ), + contentWindowInsets = insets, ) { Column( modifier = Modifier