From fe4fcb25a384feef33e2b0af24cea0b9880342ff Mon Sep 17 00:00:00 2001 From: Gautam Shorewala <123801344+GautamCoder4019k@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:03:44 +0530 Subject: [PATCH 1/5] feat: made the ui adaptive for orientation changes --- .../ui/navigation/SideNavigationBar.kt | 300 ++++++++++++++++++ .../BrainzPlayerBackDropScreen.kt | 12 +- .../ui/screens/explore/ExploreScreen.kt | 61 ++-- .../android/ui/screens/main/MainActivity.kt | 133 +++++--- 4 files changed, 424 insertions(+), 82 deletions(-) create mode 100644 app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt new file mode 100644 index 00000000..7f5f2196 --- /dev/null +++ b/app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt @@ -0,0 +1,300 @@ +package org.listenbrainz.android.ui.navigation + +import androidx.compose.foundation.background +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContent +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.BackdropScaffoldState +import androidx.compose.material.BackdropValue +import androidx.compose.material.Icon +import androidx.compose.material.NavigationRail +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.SkipNext +import androidx.compose.material.icons.rounded.SkipPrevious +import androidx.compose.material.rememberBackdropScaffoldState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.NavigationRailItemDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import coil.compose.AsyncImage +import kotlinx.coroutines.launch +import org.listenbrainz.android.R +import org.listenbrainz.android.model.AppNavigationItem +import org.listenbrainz.android.model.Song +import org.listenbrainz.android.ui.components.CustomSeekBar +import org.listenbrainz.android.ui.components.PlayPauseIcon +import org.listenbrainz.android.ui.screens.brainzplayer.ui.components.basicMarquee +import org.listenbrainz.android.ui.theme.ListenBrainzTheme +import org.listenbrainz.android.util.BrainzPlayerExtensions.toSong +import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel + +@Composable +fun SideNavigationBar( + modifier: Modifier = Modifier, navController: NavController = rememberNavController(), + backgroundColor: Color = ListenBrainzTheme.colorScheme.nav, + backdropScaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed), + scrollToTop: () -> Unit, + username: String?, + viewModel: BrainzPlayerViewModel = hiltViewModel() +) { + val items = listOf( + AppNavigationItem.Feed, + AppNavigationItem.Explore, + AppNavigationItem.BrainzPlayer, + AppNavigationItem.Profile + ) + + val currentlyPlayingSong = + viewModel.currentlyPlayingSong.collectAsStateWithLifecycle().value.toSong + NavigationRail( + modifier = modifier + .width(200.dp) + .background(backgroundColor) + .statusBarsPadding(), + backgroundColor = backgroundColor, + contentColor = MaterialTheme.colorScheme.onSurface, + elevation = 0.dp + ) { + val coroutineScope = rememberCoroutineScope() + items.forEach { item -> + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + val selected = + currentDestination?.route?.startsWith("${item.route}/") == true || currentDestination?.route == item.route + NavigationRailItem( + modifier = Modifier, + icon = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .windowInsetsPadding(WindowInsets.safeContent) + .fillMaxWidth() + ) { + Icon( + painterResource(id = selected + .takeIf { it } + ?.let { item.iconSelected } + ?: item.iconUnselected), + modifier = Modifier + .size(24.dp) + .padding(vertical = 4.dp), + contentDescription = item.title, + tint = MaterialTheme.colorScheme.onSurface + ) + Text( + text = item.title, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(), + ) + } + + }, + alwaysShowLabel = false, + selected = selected, + colors = NavigationRailItemDefaults.colors( + indicatorColor = backgroundColor, + ), + onClick = { + coroutineScope.launch { + if (selected) { + scrollToTop() + } + backdropScaffoldState.reveal() + + when (item.route) { + AppNavigationItem.Profile.route -> { + navController.navigate(AppNavigationItem.Profile.route + if (!username.isNullOrBlank()) "/${username}" else "") { + // Avoid building large backstack + popUpTo(navController.graph.findStartDestination().id) { + if (username.isNullOrBlank()) { + inclusive = true + } + + saveState = true + } + // Avoid copies + launchSingleTop = true + // Restore previous state + restoreState = true + } + } + + else -> navController.navigate(item.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + // Avoid copies + launchSingleTop = true + // Restore previous state + restoreState = true + } + } + } + } + ) + } + Spacer(modifier = Modifier.weight(1f)) + BrainzNavigationMiniPlayer( + backdropScaffoldState = backdropScaffoldState, + currentlyPlayingSong = currentlyPlayingSong, + ) + } +} + +@Composable +fun BrainzNavigationMiniPlayer( + modifier: Modifier = Modifier, + backdropScaffoldState: BackdropScaffoldState, + currentlyPlayingSong: Song, + viewModel: BrainzPlayerViewModel = hiltViewModel() +) { + val coroutineScope = rememberCoroutineScope() + Column( + modifier = modifier + .fillMaxWidth() + .clickable { + coroutineScope.launch { + // Click anywhere to open the front layer. + backdropScaffoldState.conceal() + } + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box { + val progress by viewModel.progress.collectAsState() + CustomSeekBar( + modifier = Modifier + .height(10.dp) + .fillMaxWidth(), + progress = progress, + onValueChange = { newProgress -> + viewModel.onSeek(newProgress) + viewModel.onSeeked() + }, + remainingProgressColor = ListenBrainzTheme.colorScheme.hint + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + ) { + Column { + Row { + Box( + modifier = Modifier + .padding(start = 5.dp, end = 5.dp) + .height(45.dp) + .width(45.dp) + ) { + AsyncImage( + modifier = Modifier + .matchParentSize() + .clip(shape = RoundedCornerShape(8.dp)) + .graphicsLayer { clip = true }, + model = currentlyPlayingSong.albumArt, + contentDescription = "", + error = painterResource( + id = R.drawable.ic_erroralbumart + ), + contentScale = ContentScale.Crop + ) + } + + Text( + text = when { + currentlyPlayingSong.artist == "null" && currentlyPlayingSong.title == "null" -> "" + currentlyPlayingSong.artist == "null" -> currentlyPlayingSong.title + currentlyPlayingSong.title == "null" -> currentlyPlayingSong.artist + else -> currentlyPlayingSong.artist + " - " + currentlyPlayingSong.title + }, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .padding(start = 4.dp, end = 8.dp) + .basicMarquee() + ) + } + val playIcon by viewModel.playButton.collectAsState() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Icon( + imageVector = Icons.Rounded.SkipPrevious, + contentDescription = "", + Modifier + .size(35.dp) + .clickable { + viewModel.skipToPreviousSong() + }, + tint = MaterialTheme.colorScheme.onSurface + ) + + PlayPauseIcon( + playIcon, + viewModel, + Modifier.size(35.dp), + tint = MaterialTheme.colorScheme.onSurface + ) + + Icon( + imageVector = Icons.Rounded.SkipNext, + contentDescription = "", + Modifier + .size(35.dp) + .clickable { viewModel.skipToNextSong() }, + tint = MaterialTheme.colorScheme.onSurface + ) + } + } + + + } + } +} + +@Preview +@Composable +private fun SideNavigationBarPreview() { + SideNavigationBar(scrollToTop = {}, username = "") +} \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt index afb5ee36..b56de7aa 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt @@ -1,6 +1,7 @@ package org.listenbrainz.android.ui.screens.brainzplayer +import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateDpAsState @@ -70,6 +71,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -118,6 +120,7 @@ fun BrainzPlayerBackDropScreen( var maxDelta by rememberSaveable { mutableFloatStateOf(0F) } + val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE val repeatMode by brainzPlayerViewModel.repeatMode.collectAsStateWithLifecycle() val context = LocalContext.current val defaultBackgroundColor = ListenBrainzTheme.colorScheme.background @@ -125,10 +128,11 @@ fun BrainzPlayerBackDropScreen( /** 56.dp is default bottom navigation height */ val headerHeight by animateDpAsState( - targetValue = if (currentlyPlayingSong.title == "null" && currentlyPlayingSong.artist == "null") - 56.dp - else - 56.dp + ListenBrainzTheme.sizes.brainzPlayerPeekHeight + targetValue = if (isLandscape) 0.dp else + if (currentlyPlayingSong.title == "null" && currentlyPlayingSong.artist == "null") + 56.dp + else + 56.dp + ListenBrainzTheme.sizes.brainzPlayerPeekHeight ) LaunchedEffect(currentlyPlayingSong, isDarkThemeEnabled) { brainzPlayerViewModel.updateBackgroundColorForPlayer( diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/explore/ExploreScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/explore/ExploreScreen.kt index 862d8ce9..b1579c91 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/explore/ExploreScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/explore/ExploreScreen.kt @@ -11,9 +11,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text @@ -40,34 +40,41 @@ fun ExploreScreen() { modifier = Modifier.fillMaxSize(), color = ListenBrainzTheme.colorScheme.background ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, + + LazyVerticalGrid( + columns = GridCells.Adaptive(300.dp), modifier = Modifier - .verticalScroll(rememberScrollState()) .fillMaxWidth() ) { - ExploreScreenCard( - nextActivity = YearInMusic23Activity::class.java, - iconId = R.drawable.yim23_explore_tile, - title = "Your Year in Music 2023", - subTitle = "Review" - ) + item { + ExploreScreenCard( + nextActivity = YearInMusic23Activity::class.java, + iconId = R.drawable.yim23_explore_tile, + title = "Your Year in Music 2023", + subTitle = "Review" + ) + } + item { + ExploreScreenCard( + nextActivity = YearInMusicActivity::class.java, + iconId = R.drawable.yim2022, + title = "Your Year in Music 2022", + subTitle = "Review" + ) + } // Yim Card - ExploreScreenCard( - nextActivity = YearInMusicActivity::class.java, - iconId = R.drawable.yim2022, - title = "Your Year in Music 2022", - subTitle = "Review" - ) - + + item { + ExploreScreenCard( + nextActivity = NewsBrainzActivity::class.java, + iconId = R.drawable.all_projects, + title = "News", + subTitle = stringResource(id = R.string.news_card) + ) + } // NewsBrainz Card - ExploreScreenCard( - nextActivity = NewsBrainzActivity::class.java, - iconId = R.drawable.all_projects, - title = "News", - subTitle = stringResource(id = R.string.news_card) - ) - + + } } } @@ -110,7 +117,7 @@ private fun ExploreScreenCard( modifier = Modifier .fillMaxWidth() ) - + Spacer(modifier = Modifier.height(12.dp)) Text( @@ -134,7 +141,7 @@ private fun ExploreScreenCard( color = ListenBrainzTheme.colorScheme.listenText.copy(alpha = 0.7f), style = MaterialTheme.typography.caption ) - + Spacer(modifier = Modifier.height(12.dp)) } } diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt index 1a9df59b..da7df678 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt @@ -1,5 +1,6 @@ package org.listenbrainz.android.ui.screens.main +import android.content.res.Configuration import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult @@ -8,16 +9,16 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.captionBar import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material.BackdropValue import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Surface import androidx.compose.material.rememberBackdropScaffoldState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -35,7 +36,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -52,6 +54,7 @@ import org.listenbrainz.android.service.ListenSubmissionService import org.listenbrainz.android.ui.components.DialogLB import org.listenbrainz.android.ui.navigation.AppNavigation import org.listenbrainz.android.ui.navigation.BottomNavigationBar +import org.listenbrainz.android.ui.navigation.SideNavigationBar import org.listenbrainz.android.ui.navigation.TopBar import org.listenbrainz.android.ui.screens.brainzplayer.BrainzPlayerBackDropScreen import org.listenbrainz.android.ui.screens.search.BrainzPlayerSearchScreen @@ -112,15 +115,20 @@ class MainActivity : ComponentActivity() { isGrantedPerms = PermissionStatus.GRANTED.name dashBoardViewModel.setPermissionsPreference(PermissionStatus.GRANTED.name) } + else -> { - isGrantedPerms = when(isGrantedPerms){ + isGrantedPerms = when (isGrantedPerms) { PermissionStatus.NOT_REQUESTED.name -> { PermissionStatus.DENIED_ONCE.name } + PermissionStatus.DENIED_ONCE.name -> { PermissionStatus.DENIED_TWICE.name } - else -> {PermissionStatus.DENIED_TWICE.name} + + else -> { + PermissionStatus.DENIED_TWICE.name + } } dashBoardViewModel.setPermissionsPreference(isGrantedPerms) } @@ -133,7 +141,7 @@ class MainActivity : ComponentActivity() { } } - when(isGrantedPerms){ + when (isGrantedPerms) { PermissionStatus.DENIED_ONCE.name -> { DialogLB( options = arrayOf("Grant"), @@ -147,6 +155,7 @@ class MainActivity : ComponentActivity() { onDismiss = {} ) } + PermissionStatus.DENIED_TWICE.name -> { DialogLB( title = "Permissions required", @@ -163,7 +172,8 @@ class MainActivity : ComponentActivity() { } val navController = rememberNavController() - val backdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed) + val backdropScaffoldState = + rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed) var scrollToTopState by remember { mutableStateOf(false) } val snackbarState = remember { SnackbarHostState() } val searchBarState = rememberSearchBarState() @@ -171,9 +181,12 @@ class MainActivity : ComponentActivity() { val scope = rememberCoroutineScope() val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination - val username by dashBoardViewModel.usernameFlow.collectAsStateWithLifecycle(initialValue = null) + val username by dashBoardViewModel.usernameFlow.collectAsStateWithLifecycle( + initialValue = null + ) val brainzPlayerViewModel: BrainzPlayerViewModel by viewModels() - + val isLandScape = + LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE val isBackdropInitialised by remember { derivedStateOf { val currentOffset = runCatching { @@ -191,7 +204,8 @@ class MainActivity : ComponentActivity() { val playerHeight = ListenBrainzTheme.sizes.brainzPlayerPeekHeight.toPx() LaunchedEffect(isBackdropInitialised) { if (isBackdropInitialised) { - maxOffset = maxOf(maxOffset, backdropScaffoldState.requireOffset() - playerHeight) + maxOffset = + maxOf(maxOffset, backdropScaffoldState.requireOffset() - playerHeight) println(maxOffset) } } @@ -200,12 +214,14 @@ class MainActivity : ComponentActivity() { derivedStateOf { brainzPlayerViewModel.playerBackGroundColor.copy( alpha = runCatching { - 1 - (backdropScaffoldState.requireOffset() / maxOffset).coerceIn(0f, 1f) + 1 - (backdropScaffoldState.requireOffset() / maxOffset).coerceIn( + 0f, + 1f + ) }.getOrElse { 0f } ) } } - Scaffold( modifier = Modifier .fillMaxSize() @@ -213,7 +229,9 @@ class MainActivity : ComponentActivity() { .background(desiredBackgroundColor), topBar = { TopBar( - modifier = Modifier.statusBarsPadding(), + modifier = Modifier + .statusBarsPadding() + .padding(start = if (isLandScape) 200.dp else 0.dp), navController = navController, searchBarState = when (currentDestination?.route) { AppNavigationItem.BrainzPlayer.route -> brainzplayerSearchBarState @@ -222,12 +240,13 @@ class MainActivity : ComponentActivity() { ) }, bottomBar = { - BottomNavigationBar( - navController = navController, - backdropScaffoldState = backdropScaffoldState, - scrollToTop = { scrollToTopState = true }, - username = username - ) + if (!isLandScape) + BottomNavigationBar( + navController = navController, + backdropScaffoldState = backdropScaffoldState, + scrollToTop = { scrollToTopState = true }, + username = username + ) }, snackbarHost = { SnackbarHost( @@ -246,44 +265,56 @@ class MainActivity : ComponentActivity() { containerColor = Color.Transparent, contentWindowInsets = WindowInsets.captionBar ) { - if (isGrantedPerms == PermissionStatus.GRANTED.name) { - BrainzPlayerBackDropScreen( - modifier = Modifier.navigationBarsPadding(), - backdropScaffoldState = backdropScaffoldState, - paddingValues = it, - brainzPlayerViewModel = brainzPlayerViewModel - ) { - AppNavigation( + Row { + if (isLandScape) { + SideNavigationBar( navController = navController, - scrollRequestState = scrollToTopState, - onScrollToTop = { scrollToTop -> - scope.launch { - if (scrollToTopState){ - scrollToTop() - scrollToTopState = false - } - } - }, - snackbarState = snackbarState, + backdropScaffoldState = backdropScaffoldState, + scrollToTop = { scrollToTopState = true }, + username = username ) } + if (isGrantedPerms == PermissionStatus.GRANTED.name) { + BrainzPlayerBackDropScreen( + modifier = Modifier.navigationBarsPadding(), + backdropScaffoldState = backdropScaffoldState, + paddingValues = it, + brainzPlayerViewModel = brainzPlayerViewModel + ) { + AppNavigation( + navController = navController, + scrollRequestState = scrollToTopState, + onScrollToTop = { scrollToTop -> + scope.launch { + if (scrollToTopState) { + scrollToTop() + scrollToTopState = false + } + } + }, + snackbarState = snackbarState, + ) + } + } } - } - when(currentDestination?.route) { - AppNavigationItem.BrainzPlayer.route -> BrainzPlayerSearchScreen( - isActive = brainzplayerSearchBarState.isActive, - deactivate = brainzplayerSearchBarState::deactivate, - ) - else -> UserSearchScreen( - isActive = searchBarState.isActive, - deactivate = searchBarState::deactivate, - goToUserPage = { username -> - searchBarState.deactivate() - navController.navigate("${AppNavigationItem.Profile.route}/$username") - } - ) + when (currentDestination?.route) { + AppNavigationItem.BrainzPlayer.route -> BrainzPlayerSearchScreen( + isActive = brainzplayerSearchBarState.isActive, + deactivate = brainzplayerSearchBarState::deactivate, + ) + + else -> UserSearchScreen( + isActive = searchBarState.isActive, + deactivate = searchBarState::deactivate, + goToUserPage = { username -> + searchBarState.deactivate() + navController.navigate("${AppNavigationItem.Profile.route}/$username") + } + ) + } } + } } } From 63a68fe22e281fde45d92559dc221d966607a48d Mon Sep 17 00:00:00 2001 From: Gautam Shorewala <123801344+GautamCoder4019k@users.noreply.github.com> Date: Sun, 9 Feb 2025 21:13:42 +0530 Subject: [PATCH 2/5] feat:made adaptive navigationBar and MiniPlayer --- .../ui/navigation/AdaptiveNavigationBar.kt | 236 ++++++++++++++ .../ui/navigation/BottomNavigationBar.kt | 139 -------- .../ui/navigation/SideNavigationBar.kt | 300 ------------------ .../BrainzPlayerBackDropScreen.kt | 1 + .../android/ui/screens/main/MainActivity.kt | 18 +- .../android/util/SongViewPager.kt | 276 +++++++++------- .../viewmodel/BrainzPlayerViewModel.kt | 4 +- 7 files changed, 418 insertions(+), 556 deletions(-) create mode 100644 app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt delete mode 100644 app/src/main/java/org/listenbrainz/android/ui/navigation/BottomNavigationBar.kt delete mode 100644 app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt new file mode 100644 index 00000000..e3c746ff --- /dev/null +++ b/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt @@ -0,0 +1,236 @@ +package org.listenbrainz.android.ui.navigation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContent +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material.BackdropScaffoldState +import androidx.compose.material.BackdropValue +import androidx.compose.material.BottomNavigation +import androidx.compose.material.BottomNavigationItem +import androidx.compose.material.Icon +import androidx.compose.material.NavigationRail +import androidx.compose.material.Text +import androidx.compose.material.rememberBackdropScaffoldState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.NavigationRailItemDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.launch +import org.listenbrainz.android.R +import org.listenbrainz.android.model.AppNavigationItem +import org.listenbrainz.android.ui.theme.ListenBrainzTheme +import org.listenbrainz.android.util.BrainzPlayerExtensions.toSong +import org.listenbrainz.android.util.SongViewPager +import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel + +@Composable +fun AdaptiveNavigationBar( + modifier: Modifier = Modifier, + navController: NavController = rememberNavController(), + backgroundColor: Color = ListenBrainzTheme.colorScheme.nav, + backdropScaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed), + scrollToTop: () -> Unit, + username: String?, + isLandscape: Boolean, + brainzPlayerViewModel: BrainzPlayerViewModel = hiltViewModel() +) { + val items = listOf( + AppNavigationItem.Feed, + AppNavigationItem.Explore, + AppNavigationItem.BrainzPlayer, + AppNavigationItem.Profile + ) + val coroutineScope = rememberCoroutineScope() + + @Composable + fun NavigationContent( + item: AppNavigationItem, + selected: Boolean, + onItemClick: () -> Unit, + isLandscape: Boolean, + scope: RowScope? + ) { + val navIcon = @Composable { + Icon( + painter = painterResource( + id = if (selected) item.iconSelected else item.iconUnselected + ), + modifier = Modifier + .size(24.dp) + .padding(vertical = 4.dp), + contentDescription = item.title, + tint = MaterialTheme.colorScheme.onSurface + ) + } + + val navLabel = @Composable { + Text( + text = item.title, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding() + ) + } + + if (isLandscape) { + NavigationRailItem( + icon = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .windowInsetsPadding(WindowInsets.safeContent) + .fillMaxWidth() + ) { + navIcon() + navLabel() + } + }, + alwaysShowLabel = false, + selected = selected, + colors = NavigationRailItemDefaults.colors( + indicatorColor = backgroundColor + ), + onClick = onItemClick + ) + } else { + scope?.let { + it.BottomNavigationItem( + icon = { navIcon() }, + label = { navLabel() }, + selectedContentColor = MaterialTheme.colorScheme.onSurface, + unselectedContentColor = colorResource(id = R.color.gray), + alwaysShowLabel = true, + selected = selected, + onClick = onItemClick, + modifier = Modifier.navigationBarsPadding() + ) + } + + } + } + + //composable with common navigation logic + @Composable + fun CommonNavigationLogic(scope: RowScope? = null) { + items.forEach { item -> + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + val selected = currentDestination?.route?.startsWith("${item.route}/") == true || + currentDestination?.route == item.route + + NavigationContent( + item = item, + selected = selected, + scope = scope, + isLandscape = isLandscape, + onItemClick = { + coroutineScope.launch { + if (selected) { + scrollToTop() + } + // A quick way to navigate to back layer content. + backdropScaffoldState.reveal() + + when (item.route) { + AppNavigationItem.Profile.route -> { + val profileRoute = AppNavigationItem.Profile.route + + if (!username.isNullOrBlank()) "/${username}" else "" + navController.navigate(profileRoute) { + // Avoid building large backstack + popUpTo(navController.graph.findStartDestination().id) { + if (username.isNullOrBlank()) { + inclusive = true + } + saveState = true + } + // Avoid copies + launchSingleTop = true + // Restore previous state + restoreState = true + } + } + + else -> navController.navigate(item.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + // Avoid copies + launchSingleTop = true + // Restore previous state + restoreState = true + } + } + } + } + ) + } + } + + if (isLandscape) { + val currentlyPlayingSong by brainzPlayerViewModel.currentlyPlayingSong.collectAsStateWithLifecycle() + NavigationRail( + modifier = modifier + .width(200.dp) + .background(backgroundColor) + .statusBarsPadding(), + backgroundColor = backgroundColor, + contentColor = MaterialTheme.colorScheme.onSurface, + elevation = 0.dp + ) { + CommonNavigationLogic() + Spacer(modifier = Modifier.weight(1f)) + if (currentlyPlayingSong.toSong.mediaID != 0L) + SongViewPager( + modifier = modifier, + songList = brainzPlayerViewModel.appPreferences.currentPlayable?.songs + ?: emptyList(), + backdropScaffoldState = backdropScaffoldState, + currentlyPlayingSong = currentlyPlayingSong.toSong, + isLandscape = true + ) + } + } else { + BottomNavigation( + modifier = modifier, + backgroundColor = backgroundColor, + elevation = 0.dp + ) { + CommonNavigationLogic(scope = this) + } + } +} + +@Preview +@Composable +fun AdaptiveNavigationBarPreview() { + AdaptiveNavigationBar( + navController = rememberNavController(), + scrollToTop = {}, + username = "pranavkonidena", + isLandscape = false + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/BottomNavigationBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/BottomNavigationBar.kt deleted file mode 100644 index 14a092fa..00000000 --- a/app/src/main/java/org/listenbrainz/android/ui/navigation/BottomNavigationBar.kt +++ /dev/null @@ -1,139 +0,0 @@ -package org.listenbrainz.android.ui.navigation - -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.BackdropScaffoldState -import androidx.compose.material.BackdropValue -import androidx.compose.material.BottomNavigation -import androidx.compose.material.BottomNavigationItem -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Text -import androidx.compose.material.rememberBackdropScaffoldState -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.NavGraph.Companion.findStartDestination -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import kotlinx.coroutines.launch -import org.listenbrainz.android.R -import org.listenbrainz.android.model.AppNavigationItem -import org.listenbrainz.android.ui.theme.ListenBrainzTheme - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun BottomNavigationBar( - modifier: Modifier = Modifier, - navController: NavController = rememberNavController(), - backgroundColor: Color = ListenBrainzTheme.colorScheme.nav, - backdropScaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed), - scrollToTop: () -> Unit, - username: String?, -) { - val items = listOf( - AppNavigationItem.Feed, - AppNavigationItem.Explore, - AppNavigationItem.BrainzPlayer, - AppNavigationItem.Profile - ) - BottomNavigation( - modifier = modifier, - backgroundColor = backgroundColor, - elevation = 0.dp - ) { - val coroutineScope = rememberCoroutineScope() - items.forEach { item -> - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - val selected = - currentDestination?.route?.startsWith("${item.route}/") == true || currentDestination?.route == item.route - BottomNavigationItem( - modifier = Modifier.navigationBarsPadding(), - icon = { - Icon( - painterResource(id = selected - .takeIf { it } - ?.let { item.iconSelected } - ?: item.iconUnselected), - modifier = Modifier - .size(24.dp) - .padding(vertical = 4.dp), - contentDescription = item.title, - tint = MaterialTheme.colorScheme.onSurface - ) - }, - label = { - Text( - text = item.title, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(), - ) - }, - selectedContentColor = MaterialTheme.colorScheme.onSurface, - unselectedContentColor = colorResource(id = R.color.gray), - alwaysShowLabel = true, - selected = selected, - onClick = { - coroutineScope.launch { - if (selected) { - scrollToTop() - } - - // A quick way to navigate to back layer content. - backdropScaffoldState.reveal() - - when (item.route) { - AppNavigationItem.Profile.route -> { - navController.navigate(AppNavigationItem.Profile.route + if (!username.isNullOrBlank()) "/${username}" else "") { - // Avoid building large backstack - popUpTo(navController.graph.findStartDestination().id){ - if (username.isNullOrBlank()) { - inclusive = true - } - - saveState = true - } - // Avoid copies - launchSingleTop = true - // Restore previous state - restoreState = true - } - } - - else -> navController.navigate(item.route) { - popUpTo(navController.graph.findStartDestination().id){ - saveState = true - } - // Avoid copies - launchSingleTop = true - // Restore previous state - restoreState = true - } - } - } - } - ) - } - } -} - - -@OptIn(ExperimentalMaterialApi::class) -@Preview -@Composable -fun BottomNavigationBarPreview() { - BottomNavigationBar( - navController = rememberNavController(), - scrollToTop = {}, - username = "pranavkonidena") -} \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt deleted file mode 100644 index 7f5f2196..00000000 --- a/app/src/main/java/org/listenbrainz/android/ui/navigation/SideNavigationBar.kt +++ /dev/null @@ -1,300 +0,0 @@ -package org.listenbrainz.android.ui.navigation - -import androidx.compose.foundation.background -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.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeContent -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.BackdropScaffoldState -import androidx.compose.material.BackdropValue -import androidx.compose.material.Icon -import androidx.compose.material.NavigationRail -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.SkipNext -import androidx.compose.material.icons.rounded.SkipPrevious -import androidx.compose.material.rememberBackdropScaffoldState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationRailItem -import androidx.compose.material3.NavigationRailItemDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController -import androidx.navigation.NavGraph.Companion.findStartDestination -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import coil.compose.AsyncImage -import kotlinx.coroutines.launch -import org.listenbrainz.android.R -import org.listenbrainz.android.model.AppNavigationItem -import org.listenbrainz.android.model.Song -import org.listenbrainz.android.ui.components.CustomSeekBar -import org.listenbrainz.android.ui.components.PlayPauseIcon -import org.listenbrainz.android.ui.screens.brainzplayer.ui.components.basicMarquee -import org.listenbrainz.android.ui.theme.ListenBrainzTheme -import org.listenbrainz.android.util.BrainzPlayerExtensions.toSong -import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel - -@Composable -fun SideNavigationBar( - modifier: Modifier = Modifier, navController: NavController = rememberNavController(), - backgroundColor: Color = ListenBrainzTheme.colorScheme.nav, - backdropScaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed), - scrollToTop: () -> Unit, - username: String?, - viewModel: BrainzPlayerViewModel = hiltViewModel() -) { - val items = listOf( - AppNavigationItem.Feed, - AppNavigationItem.Explore, - AppNavigationItem.BrainzPlayer, - AppNavigationItem.Profile - ) - - val currentlyPlayingSong = - viewModel.currentlyPlayingSong.collectAsStateWithLifecycle().value.toSong - NavigationRail( - modifier = modifier - .width(200.dp) - .background(backgroundColor) - .statusBarsPadding(), - backgroundColor = backgroundColor, - contentColor = MaterialTheme.colorScheme.onSurface, - elevation = 0.dp - ) { - val coroutineScope = rememberCoroutineScope() - items.forEach { item -> - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - val selected = - currentDestination?.route?.startsWith("${item.route}/") == true || currentDestination?.route == item.route - NavigationRailItem( - modifier = Modifier, - icon = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .windowInsetsPadding(WindowInsets.safeContent) - .fillMaxWidth() - ) { - Icon( - painterResource(id = selected - .takeIf { it } - ?.let { item.iconSelected } - ?: item.iconUnselected), - modifier = Modifier - .size(24.dp) - .padding(vertical = 4.dp), - contentDescription = item.title, - tint = MaterialTheme.colorScheme.onSurface - ) - Text( - text = item.title, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(), - ) - } - - }, - alwaysShowLabel = false, - selected = selected, - colors = NavigationRailItemDefaults.colors( - indicatorColor = backgroundColor, - ), - onClick = { - coroutineScope.launch { - if (selected) { - scrollToTop() - } - backdropScaffoldState.reveal() - - when (item.route) { - AppNavigationItem.Profile.route -> { - navController.navigate(AppNavigationItem.Profile.route + if (!username.isNullOrBlank()) "/${username}" else "") { - // Avoid building large backstack - popUpTo(navController.graph.findStartDestination().id) { - if (username.isNullOrBlank()) { - inclusive = true - } - - saveState = true - } - // Avoid copies - launchSingleTop = true - // Restore previous state - restoreState = true - } - } - - else -> navController.navigate(item.route) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - // Avoid copies - launchSingleTop = true - // Restore previous state - restoreState = true - } - } - } - } - ) - } - Spacer(modifier = Modifier.weight(1f)) - BrainzNavigationMiniPlayer( - backdropScaffoldState = backdropScaffoldState, - currentlyPlayingSong = currentlyPlayingSong, - ) - } -} - -@Composable -fun BrainzNavigationMiniPlayer( - modifier: Modifier = Modifier, - backdropScaffoldState: BackdropScaffoldState, - currentlyPlayingSong: Song, - viewModel: BrainzPlayerViewModel = hiltViewModel() -) { - val coroutineScope = rememberCoroutineScope() - Column( - modifier = modifier - .fillMaxWidth() - .clickable { - coroutineScope.launch { - // Click anywhere to open the front layer. - backdropScaffoldState.conceal() - } - }, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Box { - val progress by viewModel.progress.collectAsState() - CustomSeekBar( - modifier = Modifier - .height(10.dp) - .fillMaxWidth(), - progress = progress, - onValueChange = { newProgress -> - viewModel.onSeek(newProgress) - viewModel.onSeeked() - }, - remainingProgressColor = ListenBrainzTheme.colorScheme.hint - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Box( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - ) { - Column { - Row { - Box( - modifier = Modifier - .padding(start = 5.dp, end = 5.dp) - .height(45.dp) - .width(45.dp) - ) { - AsyncImage( - modifier = Modifier - .matchParentSize() - .clip(shape = RoundedCornerShape(8.dp)) - .graphicsLayer { clip = true }, - model = currentlyPlayingSong.albumArt, - contentDescription = "", - error = painterResource( - id = R.drawable.ic_erroralbumart - ), - contentScale = ContentScale.Crop - ) - } - - Text( - text = when { - currentlyPlayingSong.artist == "null" && currentlyPlayingSong.title == "null" -> "" - currentlyPlayingSong.artist == "null" -> currentlyPlayingSong.title - currentlyPlayingSong.title == "null" -> currentlyPlayingSong.artist - else -> currentlyPlayingSong.artist + " - " + currentlyPlayingSong.title - }, - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .padding(start = 4.dp, end = 8.dp) - .basicMarquee() - ) - } - val playIcon by viewModel.playButton.collectAsState() - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - Icon( - imageVector = Icons.Rounded.SkipPrevious, - contentDescription = "", - Modifier - .size(35.dp) - .clickable { - viewModel.skipToPreviousSong() - }, - tint = MaterialTheme.colorScheme.onSurface - ) - - PlayPauseIcon( - playIcon, - viewModel, - Modifier.size(35.dp), - tint = MaterialTheme.colorScheme.onSurface - ) - - Icon( - imageVector = Icons.Rounded.SkipNext, - contentDescription = "", - Modifier - .size(35.dp) - .clickable { viewModel.skipToNextSong() }, - tint = MaterialTheme.colorScheme.onSurface - ) - } - } - - - } - } -} - -@Preview -@Composable -private fun SideNavigationBarPreview() { - SideNavigationBar(scrollToTop = {}, username = "") -} \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt index ea71b1ad..eceb0126 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt @@ -187,6 +187,7 @@ fun BrainzPlayerBackDropScreen( dynamicBackground = brainzPlayerViewModel.playerBackGroundColor ) val songList = brainzPlayerViewModel.appPreferences.currentPlayable?.songs ?: listOf() + SongViewPager( modifier = Modifier.graphicsLayer { alpha = diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt index 2f09a4a5..de5f741f 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt @@ -45,23 +45,19 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.listenbrainz.android.application.App import org.listenbrainz.android.model.AppNavigationItem import org.listenbrainz.android.model.PermissionStatus -import org.listenbrainz.android.service.ListenSubmissionService import org.listenbrainz.android.ui.components.DialogLB +import org.listenbrainz.android.ui.navigation.AdaptiveNavigationBar import org.listenbrainz.android.ui.navigation.AppNavigation -import org.listenbrainz.android.ui.navigation.BottomNavigationBar -import org.listenbrainz.android.ui.navigation.SideNavigationBar import org.listenbrainz.android.ui.navigation.TopBar import org.listenbrainz.android.ui.screens.brainzplayer.BrainzPlayerBackDropScreen import org.listenbrainz.android.ui.screens.search.BrainzPlayerSearchScreen import org.listenbrainz.android.ui.screens.search.UserSearchScreen import org.listenbrainz.android.ui.screens.search.rememberSearchBarState import org.listenbrainz.android.ui.theme.ListenBrainzTheme -import org.listenbrainz.android.util.Utils.isServiceRunning import org.listenbrainz.android.util.Utils.openAppSystemSettings import org.listenbrainz.android.util.Utils.toPx import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel @@ -241,11 +237,13 @@ class MainActivity : ComponentActivity() { }, bottomBar = { if (!isLandScape) - BottomNavigationBar( + AdaptiveNavigationBar( navController = navController, backdropScaffoldState = backdropScaffoldState, scrollToTop = { scrollToTopState = true }, - username = username + username = username, + isLandscape = isLandScape, + brainzPlayerViewModel = brainzPlayerViewModel ) }, snackbarHost = { @@ -267,11 +265,13 @@ class MainActivity : ComponentActivity() { ) { Row { if (isLandScape) { - SideNavigationBar( + AdaptiveNavigationBar( navController = navController, backdropScaffoldState = backdropScaffoldState, scrollToTop = { scrollToTopState = true }, - username = username + username = username, + isLandscape = isLandScape, + brainzPlayerViewModel = brainzPlayerViewModel ) } if (isGrantedPerms == PermissionStatus.GRANTED.name) { diff --git a/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt b/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt index c1fab60d..713a83eb 100644 --- a/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt +++ b/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt @@ -2,7 +2,6 @@ package org.listenbrainz.android.util import android.graphics.drawable.BitmapDrawable import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -18,7 +17,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BackdropScaffoldState import androidx.compose.material.BackdropValue @@ -38,7 +36,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed @@ -47,6 +44,7 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -61,6 +59,7 @@ import coil.ImageLoader import coil.compose.AsyncImage import coil.request.ImageRequest import coil.request.SuccessResult +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.listenbrainz.android.R import org.listenbrainz.android.model.Song @@ -70,14 +69,14 @@ import org.listenbrainz.android.ui.screens.brainzplayer.ui.components.basicMarqu import org.listenbrainz.android.ui.theme.ListenBrainzTheme import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel -@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable fun SongViewPager( modifier: Modifier = Modifier, backdropScaffoldState: BackdropScaffoldState, currentlyPlayingSong: Song, songList: List, - viewModel: BrainzPlayerViewModel = hiltViewModel() + viewModel: BrainzPlayerViewModel = hiltViewModel(), + isLandscape: Boolean = false ) { if (songList.isEmpty()) return @@ -88,70 +87,104 @@ fun SongViewPager( .indexOfFirst { it.mediaID == currentlyPlayingSong.mediaID } .takeIf { it != -1 } ?: 0 ) { songList.size } - - LaunchedEffect(pagerState.currentPage) { - val newSong = songList[pagerState.currentPage] - if ( - !newSong.isNothing() - && newSong != currentlyPlayingSong - && pagerState.currentPage != pagerState.settledPage - ) { - try { - viewModel.playOrToggleSong(newSong) - } catch (e: Exception) { - Log.e(e) + if (!isLandscape) { + LaunchedEffect(pagerState.currentPage) { + val newSong = songList[pagerState.currentPage] + if ( + !newSong.isNothing() + && newSong != currentlyPlayingSong + && pagerState.currentPage != pagerState.settledPage + ) { + try { + viewModel.playOrToggleSong(newSong) + } catch (e: Exception) { + Log.e(e) + } } } - } - LaunchedEffect(viewModel.appPreferences.currentPlayable?.currentSongIndex) { - pagerState.scrollToPage( - viewModel.appPreferences.currentPlayable?.currentSongIndex ?: 0 - ) + LaunchedEffect(viewModel.appPreferences.currentPlayable?.currentSongIndex) { + pagerState.scrollToPage( + viewModel.appPreferences.currentPlayable?.currentSongIndex ?: 0 + ) + } + LaunchedEffect(pagerState.currentPage) { + viewModel.handleSongChangeFromPager(pagerState.currentPage) + } } - LaunchedEffect(pagerState.currentPage) { - viewModel.handleSongChangeFromPager(pagerState.currentPage) + if (isLandscape) { + PlayerContent( + coroutineScope = coroutineScope, + backdropScaffoldState = backdropScaffoldState, + viewModel = viewModel, + pagerState = pagerState, + currentlyPlayingSong = currentlyPlayingSong, + isLandscape = true + ) + } else { + HorizontalPager( + state = pagerState, + modifier = modifier + .fillMaxWidth() + .background(viewModel.playerBackGroundColor) + ) { + PlayerContent( + coroutineScope = coroutineScope, + backdropScaffoldState = backdropScaffoldState, + viewModel = viewModel, + pagerState = pagerState, + currentlyPlayingSong = currentlyPlayingSong, + isLandscape = false + ) + } } +} - HorizontalPager( - state = pagerState, +@Composable +fun PlayerContent( + modifier: Modifier = Modifier, + coroutineScope: CoroutineScope, + backdropScaffoldState: BackdropScaffoldState, + viewModel: BrainzPlayerViewModel, + isLandscape: Boolean, + currentlyPlayingSong: Song, + pagerState: PagerState +) { + Column( modifier = modifier .fillMaxWidth() - .background(viewModel.playerBackGroundColor) + .clickable { + coroutineScope.launch { + // Click anywhere to open the front layer. + backdropScaffoldState.conceal() + } + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { - Column( + Box { + val progress by viewModel.progress.collectAsState() + CustomSeekBar( + modifier = Modifier + .height(10.dp) + .fillMaxWidth(), + progress = progress, + onValueChange = { newProgress -> + viewModel.onSeek(newProgress) + viewModel.onSeeked() + }, + remainingProgressColor = ListenBrainzTheme.colorScheme.hint + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Box( modifier = Modifier .fillMaxWidth() - .clickable { - coroutineScope.launch { - // Click anywhere to open the front layer. - backdropScaffoldState.conceal() - } - }, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + .height(if (isLandscape) 80.dp else 56.dp) ) { - Box { - val progress by viewModel.progress.collectAsState() - CustomSeekBar( - modifier = Modifier - .height(10.dp) - .fillMaxWidth(), - progress = progress, - onValueChange = { newProgress -> - viewModel.onSeek(newProgress) - viewModel.onSeeked() - }, - remainingProgressColor = ListenBrainzTheme.colorScheme.hint - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Box( - modifier = Modifier - .fillMaxWidth() - .height(56.dp) - ) { - Row { + val playIcon by viewModel.playButton.collectAsState() + Column { + Row(verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier .padding(start = 5.dp, end = 5.dp) @@ -171,69 +204,100 @@ fun SongViewPager( contentScale = ContentScale.Crop ) } - val playIcon by viewModel.playButton.collectAsState() Column( modifier = Modifier .fillMaxWidth() - .padding(end = 35.dp), + .padding(end = if (!isLandscape) 35.dp else 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - Icon( - imageVector = Icons.Rounded.SkipPrevious, - contentDescription = "", - Modifier - .size(35.dp) - .clickable { - coroutineScope.launch { - pagerState.animateScrollToPage( - (pagerState.currentPage - 1).coerceAtLeast(0) - ) - } - viewModel.skipToPreviousSong() - }, - tint = MaterialTheme.colorScheme.onSurface + if (!isLandscape) + PlayerControls( + playIcon = playIcon, + viewModel = viewModel, + pagerState = pagerState, + coroutineScope = coroutineScope ) + SongInfo(currentlyPlayingSong = currentlyPlayingSong) + } + } + if (isLandscape) { + PlayerControls( + coroutineScope = coroutineScope, + pagerState = null, + viewModel = viewModel, + playIcon = playIcon + ) + } - PlayPauseIcon( - playIcon, - viewModel, - Modifier.size(35.dp), - tint = MaterialTheme.colorScheme.onSurface - ) + } + } + } +} - Icon( - imageVector = Icons.Rounded.SkipNext, - contentDescription = "", - Modifier - .size(35.dp) - .clickable { viewModel.skipToNextSong() }, - tint = MaterialTheme.colorScheme.onSurface +@Composable +fun PlayerControls( + playIcon: ImageVector, + viewModel: BrainzPlayerViewModel, + pagerState: PagerState?, + coroutineScope: CoroutineScope +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Icon( + imageVector = Icons.Rounded.SkipPrevious, + contentDescription = "", + modifier = Modifier + .size(35.dp) + .clickable { + if (pagerState != null) { + coroutineScope.launch { + pagerState.animateScrollToPage( + (pagerState.currentPage - 1).coerceAtLeast(0) ) } - Text( - text = when { - currentlyPlayingSong.artist == "null" && currentlyPlayingSong.title == "null" -> "" - currentlyPlayingSong.artist == "null" -> currentlyPlayingSong.title - currentlyPlayingSong.title == "null" -> currentlyPlayingSong.artist - else -> currentlyPlayingSong.artist + " - " + currentlyPlayingSong.title - }, - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.basicMarquee() - ) } - } - } - } + viewModel.skipToPreviousSong() + }, + tint = MaterialTheme.colorScheme.onSurface + ) + + PlayPauseIcon( + playIcon, + viewModel, + Modifier.size(35.dp), + tint = MaterialTheme.colorScheme.onSurface + ) + + Icon( + imageVector = Icons.Rounded.SkipNext, + contentDescription = "", + Modifier + .size(35.dp) + .clickable { viewModel.skipToNextSong() }, + tint = MaterialTheme.colorScheme.onSurface + ) } } +@Composable +fun SongInfo(currentlyPlayingSong: Song) { + Text( + text = when { + currentlyPlayingSong.artist == "null" && currentlyPlayingSong.title == "null" -> "" + currentlyPlayingSong.artist == "null" -> currentlyPlayingSong.title + currentlyPlayingSong.title == "null" -> currentlyPlayingSong.artist + else -> currentlyPlayingSong.artist + " - " + currentlyPlayingSong.title + }, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.basicMarquee() + ) +} + @Composable fun Modifier.dynamicBackgroundFromAlbumArt( albumArtUrl: String?, diff --git a/app/src/main/java/org/listenbrainz/android/viewmodel/BrainzPlayerViewModel.kt b/app/src/main/java/org/listenbrainz/android/viewmodel/BrainzPlayerViewModel.kt index 0b53aeeb..2457b8d1 100644 --- a/app/src/main/java/org/listenbrainz/android/viewmodel/BrainzPlayerViewModel.kt +++ b/app/src/main/java/org/listenbrainz/android/viewmodel/BrainzPlayerViewModel.kt @@ -246,7 +246,7 @@ class BrainzPlayerViewModel @Inject constructor( if (isPrepared && mediaItem.mediaID == currentlyPlayingSong.value.toSong.mediaID) { playbackState.value.let { playbackState -> when { - playbackState.isPlaying -> if (toggle) brainzPlayerServiceConnection.transportControls?.pause() + playbackState.isPlaying -> if (toggle) brainzPlayerServiceConnection.transportControls?.pause() else {} playbackState.isPlayEnabled -> { mediaItem.lastListenedTo = System.currentTimeMillis() viewModelScope.launch { songRepository.updateSong(mediaItem) } @@ -266,7 +266,7 @@ class BrainzPlayerViewModel @Inject constructor( brainzPlayerServiceConnection.transportControls?.playFromMediaId(mediaItem.mediaID.toString(), null) ?: return playbackState.value.let { playbackState -> when { - playbackState.isPlaying -> if (!toggle) brainzPlayerServiceConnection.transportControls?.pause() + playbackState.isPlaying -> if (!toggle) brainzPlayerServiceConnection.transportControls?.pause() else {} playbackState.isPlayEnabled -> brainzPlayerServiceConnection.transportControls?.play() else -> Unit } From 4b42f5ceb0188a5abb5e53b02a43fc58bc80d328 Mon Sep 17 00:00:00 2001 From: Gautam Shorewala <123801344+GautamCoder4019k@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:02:44 +0530 Subject: [PATCH 3/5] bug: top bar not becoming invisible when we click on search --- .../android/ui/navigation/TopBar.kt | 129 ++++++++++-------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/TopBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/TopBar.kt index 0ccc4513..9254b84f 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/navigation/TopBar.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/navigation/TopBar.kt @@ -4,6 +4,9 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.net.Uri +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text @@ -34,73 +37,79 @@ fun TopBar( backgroundColor: Color = Color.Transparent, context: Context = LocalContext.current, ) { - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - val title: String = currentDestination?.route?.let { - when (it) { - AppNavigationItem.Feed.route -> AppNavigationItem.Feed.title - AppNavigationItem.BrainzPlayer.route -> AppNavigationItem.BrainzPlayer.title - AppNavigationItem.Explore.route -> AppNavigationItem.Explore.title - "${AppNavigationItem.Profile.route}/{username}" -> AppNavigationItem.Profile.title - AppNavigationItem.Settings.route -> AppNavigationItem.Settings.title - AppNavigationItem.About.route -> AppNavigationItem.About.title - "${AppNavigationItem.Artist.route}/{mbid}" -> AppNavigationItem.Artist.title - "${AppNavigationItem.Album.route}/{mbid}" -> AppNavigationItem.Album.title - else -> "" - } - } ?: "ListenBrainz" - TopAppBar( - modifier = modifier, - title = { Text(text = title) }, - navigationIcon = { - IconButton(onClick = { - context.startActivity( - Intent( - Intent.ACTION_VIEW, - Uri.parse("https://listenbrainz.org") - ) - ) - }) { - Icon( - painterResource(id = R.drawable.ic_listenbrainz_logo_icon), - "ListenBrainz", - tint = Color.Unspecified - ) - } - }, - backgroundColor = backgroundColor, - contentColor = MaterialTheme.colorScheme.onSurface, - elevation = 0.dp, - actions = { - IconButton(onClick = { searchBarState.activate() }) { - Icon( - painterResource(id = R.drawable.ic_search), - contentDescription = "Search users" - ) + AnimatedVisibility( + visible = !searchBarState.isActive, + enter = fadeIn(), + exit = fadeOut() + ) { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + val title: String = currentDestination?.route?.let { + when (it) { + AppNavigationItem.Feed.route -> AppNavigationItem.Feed.title + AppNavigationItem.BrainzPlayer.route -> AppNavigationItem.BrainzPlayer.title + AppNavigationItem.Explore.route -> AppNavigationItem.Explore.title + "${AppNavigationItem.Profile.route}/{username}" -> AppNavigationItem.Profile.title + AppNavigationItem.Settings.route -> AppNavigationItem.Settings.title + AppNavigationItem.About.route -> AppNavigationItem.About.title + "${AppNavigationItem.Artist.route}/{mbid}" -> AppNavigationItem.Artist.title + "${AppNavigationItem.Album.route}/{mbid}" -> AppNavigationItem.Album.title + else -> "" } + } ?: "ListenBrainz" - IconButton(onClick = { - if (navBackStackEntry?.destination?.route == AppNavigationItem.Settings.route) { - navController.popBackStack() - } else { - navController.navigate(AppNavigationItem.Settings.route) { - // Avoid building large backstack - popUpTo(AppNavigationItem.Feed.route) { - saveState = true + TopAppBar( + modifier = modifier, + title = { Text(text = title) }, + navigationIcon = { + IconButton(onClick = { + context.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse("https://listenbrainz.org") + ) + ) + }) { + Icon( + painterResource(id = R.drawable.ic_listenbrainz_logo_icon), + "ListenBrainz", + tint = Color.Unspecified + ) + } + }, + backgroundColor = backgroundColor, + contentColor = MaterialTheme.colorScheme.onSurface, + elevation = 0.dp, + actions = { + IconButton(onClick = { searchBarState.activate() }) { + Icon( + painterResource(id = R.drawable.ic_search), + contentDescription = "Search users" + ) + } + + IconButton(onClick = { + if (navBackStackEntry?.destination?.route == AppNavigationItem.Settings.route) { + navController.popBackStack() + } else { + navController.navigate(AppNavigationItem.Settings.route) { + // Avoid building large backstack + popUpTo(AppNavigationItem.Feed.route) { + saveState = true + } + // Avoid copies + launchSingleTop = true + // Restore previous state + restoreState = true } - // Avoid copies - launchSingleTop = true - // Restore previous state - restoreState = true } + }) { + Icon(painterResource(id = R.drawable.ic_settings), "Settings") } - }) { - Icon(painterResource(id = R.drawable.ic_settings), "Settings") } - } - ) - + ) + } } @Preview From 2f732e79b83bceefe99ddb658f7ac6b1e15ece63 Mon Sep 17 00:00:00 2001 From: Jasjeet Singh <98077881+07jasjeet@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:59:02 +0530 Subject: [PATCH 4/5] Fix front layer peeking in landscape --- .../ui/navigation/AdaptiveNavigationBar.kt | 4 ++- .../BrainzPlayerBackDropScreen.kt | 29 ++++++++++--------- .../android/ui/screens/main/MainActivity.kt | 7 +++-- .../android/util/SongViewPager.kt | 6 ++-- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt index e3c746ff..edba6252 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt @@ -12,7 +12,9 @@ import androidx.compose.foundation.layout.safeContent import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.BackdropScaffoldState import androidx.compose.material.BackdropValue import androidx.compose.material.BottomNavigation @@ -194,7 +196,7 @@ fun AdaptiveNavigationBar( val currentlyPlayingSong by brainzPlayerViewModel.currentlyPlayingSong.collectAsStateWithLifecycle() NavigationRail( modifier = modifier - .width(200.dp) + .widthIn(max = 200.dp) .background(backgroundColor) .statusBarsPadding(), backgroundColor = backgroundColor, diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt index eceb0126..6854da47 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/brainzplayer/BrainzPlayerBackDropScreen.kt @@ -102,6 +102,8 @@ import org.listenbrainz.android.ui.theme.ListenBrainzTheme import org.listenbrainz.android.ui.theme.onScreenUiModeIsDark import org.listenbrainz.android.util.BrainzPlayerExtensions.toSong import org.listenbrainz.android.util.SongViewPager +import org.listenbrainz.android.util.Utils.getNavigationBarHeight +import org.listenbrainz.android.util.Utils.getStatusBarHeight import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel import org.listenbrainz.android.viewmodel.PlaylistViewModel import java.util.Locale @@ -115,6 +117,7 @@ fun BrainzPlayerBackDropScreen( backdropScaffoldState: BackdropScaffoldState, brainzPlayerViewModel: BrainzPlayerViewModel = viewModel(), paddingValues: PaddingValues, + isLandscape: Boolean = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE, backLayerContent: @Composable () -> Unit ) { val isShuffled by brainzPlayerViewModel.isShuffled.collectAsStateWithLifecycle() @@ -123,16 +126,11 @@ fun BrainzPlayerBackDropScreen( var maxDelta by rememberSaveable { mutableFloatStateOf(0F) } - val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE val repeatMode by brainzPlayerViewModel.repeatMode.collectAsStateWithLifecycle() val context = LocalContext.current val defaultBackgroundColor = ListenBrainzTheme.colorScheme.background val isDarkThemeEnabled = onScreenUiModeIsDark() - LaunchedEffect(currentlyPlayingSong) { - println(currentlyPlayingSong) - } - val isNothingPlaying = remember(currentlyPlayingSong) { currentlyPlayingSong.title == "null" && currentlyPlayingSong.artist == "null" @@ -188,15 +186,18 @@ fun BrainzPlayerBackDropScreen( ) val songList = brainzPlayerViewModel.appPreferences.currentPlayable?.songs ?: listOf() - SongViewPager( - modifier = Modifier.graphicsLayer { - alpha = - (backdropScaffoldState.requireOffset() / (maxDelta - headerHeight.toPx())) - }, - songList = songList, - backdropScaffoldState = backdropScaffoldState, - currentlyPlayingSong = currentlyPlayingSong - ) + if (!isLandscape) { + SongViewPager( + modifier = Modifier.graphicsLayer { + alpha = + (backdropScaffoldState.requireOffset() / (maxDelta - headerHeight.toPx())) + }, + songList = songList, + backdropScaffoldState = backdropScaffoldState, + currentlyPlayingSong = currentlyPlayingSong, + isLandscape = false + ) + } }) } diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt index de5f741f..16a6d53a 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt @@ -270,16 +270,17 @@ class MainActivity : ComponentActivity() { backdropScaffoldState = backdropScaffoldState, scrollToTop = { scrollToTopState = true }, username = username, - isLandscape = isLandScape, + isLandscape = true, brainzPlayerViewModel = brainzPlayerViewModel ) } if (isGrantedPerms == PermissionStatus.GRANTED.name) { BrainzPlayerBackDropScreen( - modifier = Modifier.navigationBarsPadding(), + modifier = Modifier.then(if (!isLandScape) Modifier.navigationBarsPadding() else Modifier), backdropScaffoldState = backdropScaffoldState, paddingValues = it, - brainzPlayerViewModel = brainzPlayerViewModel + brainzPlayerViewModel = brainzPlayerViewModel, + isLandscape = isLandScape, ) { AppNavigation( navController = navController, diff --git a/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt b/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt index 713a83eb..a728bcbc 100644 --- a/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt +++ b/app/src/main/java/org/listenbrainz/android/util/SongViewPager.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState @@ -76,7 +77,7 @@ fun SongViewPager( currentlyPlayingSong: Song, songList: List, viewModel: BrainzPlayerViewModel = hiltViewModel(), - isLandscape: Boolean = false + isLandscape: Boolean ) { if (songList.isEmpty()) return @@ -349,6 +350,7 @@ fun SongViewPagerPreview() { SongViewPager( songList = listOf(), currentlyPlayingSong = Song.preview(), - backdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed) + backdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed), + isLandscape = false ) } From f596ab655098c45b82528d18ca6ec665d95c35b8 Mon Sep 17 00:00:00 2001 From: Jasjeet Singh <98077881+07jasjeet@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:04:21 +0530 Subject: [PATCH 5/5] Use dimension for nav rail width --- .../android/ui/navigation/AdaptiveNavigationBar.kt | 3 ++- .../org/listenbrainz/android/ui/screens/main/MainActivity.kt | 4 +++- app/src/main/res/values/dimen.xml | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/values/dimen.xml diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt index edba6252..a19adc49 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/navigation/AdaptiveNavigationBar.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -196,7 +197,7 @@ fun AdaptiveNavigationBar( val currentlyPlayingSong by brainzPlayerViewModel.currentlyPlayingSong.collectAsStateWithLifecycle() NavigationRail( modifier = modifier - .widthIn(max = 200.dp) + .widthIn(max = dimensionResource(R.dimen.navigation_rail_width)) .background(backgroundColor) .statusBarsPadding(), backgroundColor = backgroundColor, diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt index 16a6d53a..4b491a84 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt @@ -37,6 +37,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.ViewModelProvider @@ -62,6 +63,7 @@ import org.listenbrainz.android.util.Utils.openAppSystemSettings import org.listenbrainz.android.util.Utils.toPx import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel import org.listenbrainz.android.viewmodel.DashBoardViewModel +import org.listenbrainz.android.R @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -227,7 +229,7 @@ class MainActivity : ComponentActivity() { TopBar( modifier = Modifier .statusBarsPadding() - .padding(start = if (isLandScape) 200.dp else 0.dp), + .padding(start = if (isLandScape) dimensionResource(R.dimen.navigation_rail_width) else 0.dp), navController = navController, searchBarState = when (currentDestination?.route) { AppNavigationItem.BrainzPlayer.route -> brainzplayerSearchBarState diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml new file mode 100644 index 00000000..8a9054c5 --- /dev/null +++ b/app/src/main/res/values/dimen.xml @@ -0,0 +1,4 @@ + + + 200dp + \ No newline at end of file