diff --git a/app/src/main/java/ru/stersh/youamp/main/ui/MainActivity.kt b/app/src/main/java/ru/stersh/youamp/main/ui/MainActivity.kt index 439b5265..eab66ca2 100644 --- a/app/src/main/java/ru/stersh/youamp/main/ui/MainActivity.kt +++ b/app/src/main/java/ru/stersh/youamp/main/ui/MainActivity.kt @@ -118,12 +118,6 @@ class MainActivity : ComponentActivity() { }, ) { MainScreen( - bigPlayer = { - PlayerScreen( - onBackClick = { rootNavController.popBackStack() }, - onPlayQueueClick = { rootNavController.navigate(PlayQueue) } - ) - }, miniPlayer = { MiniPlayer( viewModelStoreOwner = viewModelStoreOwner, diff --git a/feature/main/build.gradle b/feature/main/build.gradle index 325cea23..94842031 100644 --- a/feature/main/build.gradle +++ b/feature/main/build.gradle @@ -18,4 +18,5 @@ dependencies { implementation(libs.coil.compose) implementation(libs.navigation.compose) implementation(libs.bundles.koin) + implementation(libs.compose.material3.adaptiveNavigationSuite) } \ No newline at end of file diff --git a/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/ExpandableScaffold.kt b/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/ExpandableScaffold.kt deleted file mode 100644 index a6723258..00000000 --- a/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/ExpandableScaffold.kt +++ /dev/null @@ -1,270 +0,0 @@ -package ru.stersh.youamp.feature.main.ui - -import androidx.compose.animation.core.Animatable -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.draggable -import androidx.compose.foundation.gestures.rememberDraggableState -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.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.launch -import ru.stersh.youamp.core.ui.YouampPlayerTheme -import ru.stersh.youamp.core.ui.toDp -import kotlin.math.max - -class ExpandableScaffoldState { - val expandCollapseAnimator = Animatable(COLLAPSED) - - var bottomBarMaxHeight by mutableFloatStateOf(Float.NaN) - var topBarMaxHeight by mutableFloatStateOf(Float.NaN) - var fullMaxHeight by mutableFloatStateOf(Float.NaN) - var collapsedHeight by mutableFloatStateOf(Float.NaN) - - val playerMinHeight by derivedStateOf { bottomBarMaxHeight + collapsedHeight } - val playerHeight by derivedStateOf { - max(fullMaxHeight * expandCollapseAnimator.value, playerMinHeight) - } - - val topBarOffset by derivedStateOf { - topBarMaxHeight * expandCollapseAnimator.value - } - val bottomBarOffset by derivedStateOf { - bottomBarMaxHeight * expandCollapseAnimator.value - } - - val expandedCrossfade by derivedStateOf { expandCollapseAnimator.value.coerceIn(0f, 1f) } - val collapsedCrossfade by derivedStateOf { 1f - expandedCrossfade } - - suspend fun expand(animate: Boolean = true) { - if (animate) { - expandCollapseAnimator.animateTo(EXPANDED) - } else { - expandCollapseAnimator.snapTo(EXPANDED) - } - } - - suspend fun collapse(animate: Boolean = true) { - if (animate) { - expandCollapseAnimator.animateTo(COLLAPSED) - } else { - expandCollapseAnimator.snapTo(COLLAPSED) - } - } - - companion object { - private const val COLLAPSED = 0f - private const val EXPANDED = 1f - } -} - - -@Composable -fun rememberExpandableScaffoldState() = remember { - ExpandableScaffoldState() -} - -@Composable -fun ExpandableScaffold( - modifier: Modifier = Modifier, - state: ExpandableScaffoldState = rememberExpandableScaffoldState(), - topBar: @Composable () -> Unit = {}, - expandedContent: @Composable () -> Unit = {}, - collapsedContent: @Composable () -> Unit = {}, - bottomBar: @Composable () -> Unit = {}, - content: @Composable () -> Unit -) { - - val density = LocalDensity.current - val scope = rememberCoroutineScope() - - val globalModifier = if (state.fullMaxHeight.isNaN()) { - Modifier.onGloballyPositioned { - state.fullMaxHeight = it.size.height.toFloat() - } - } else { - Modifier - } - Box( - modifier = modifier - .fillMaxSize() - .then(globalModifier) - ) { - Column( - modifier = Modifier.padding(bottom = (state.collapsedHeight + state.bottomBarMaxHeight).toDp(density)) - ) { - val topBarModifier = if (state.topBarMaxHeight.isNaN()) { - Modifier.onGloballyPositioned { - state.topBarMaxHeight = it.size.height.toFloat() - } - } else { - Modifier.offset(y = -state.topBarOffset.toDp(density)) - } - Box( - modifier = topBarModifier - ) { - topBar() - } - content() - Spacer(modifier = Modifier.height(state.collapsedHeight.toDp(density))) - } - - var offsetPosition by remember { mutableFloatStateOf(0f) } - - val heightModifier = if (state.collapsedHeight.isNaN()) { - Modifier - } else { - Modifier.height(state.playerHeight.toDp(density)) - } - Box( - modifier = Modifier - .fillMaxWidth() - .then(heightModifier) - .background(MaterialTheme.colorScheme.surfaceContainerHigh) - .align(Alignment.BottomEnd) - .draggable( - onDragStopped = { - if (offsetPosition < state.fullMaxHeight * 0.5f) { - offsetPosition = 0f - scope.launch { - state.collapse() - } - } else { - offsetPosition = state.fullMaxHeight - scope.launch { - state.expand() - } - } - }, - orientation = Orientation.Vertical, - state = rememberDraggableState { delta -> - val newValue = offsetPosition - delta - offsetPosition = newValue.coerceIn(state.playerMinHeight, state.fullMaxHeight) - scope.launch { - state.expandCollapseAnimator.animateTo(offsetPosition / state.fullMaxHeight) - } - } - ) - ) { - Box( - modifier = Modifier - .alpha(state.expandedCrossfade) - ) { - if (state.expandedCrossfade > 0) { - expandedContent() - } - } - val collapsedMeasureModifier = Modifier.onGloballyPositioned { - val measuredHeight = it.size.height.toFloat() - if (state.collapsedHeight != measuredHeight) { - state.collapsedHeight = measuredHeight - } - } - Box( - modifier = Modifier - .alpha(state.collapsedCrossfade) - .then(collapsedMeasureModifier) - ) { - if (state.collapsedCrossfade > 0) { - collapsedContent() - } - } - } - val bottomBarModifier = if (state.bottomBarMaxHeight.isNaN()) { - Modifier.onGloballyPositioned { - state.bottomBarMaxHeight = it.size.height.toFloat() - } - } else { - Modifier.offset(y = state.bottomBarOffset.toDp(density)) - } - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .then(bottomBarModifier) - ) { - bottomBar() - } - } -} - -@Composable -@Preview -fun ScreenWithDraggablePlayerPreview() { - YouampPlayerTheme { - ExpandableScaffold( - content = { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color.DarkGray) - ) - }, - topBar = { - Box( - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - .background(Color.Red) - ) - }, - expandedContent = { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color.Blue) - ) { - Text( - text = "Expanded content", - modifier = Modifier.align(Alignment.Center) - ) - } - }, - collapsedContent = { - Row( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - .background(Color.Green), - horizontalArrangement = Arrangement.Center - ) { - Text( - text = "Collapsed content", - modifier = Modifier.align(Alignment.CenterVertically) - ) - } - }, - bottomBar = { - Box( - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - .background(Color.Red) - ) - } - ) - } -} \ No newline at end of file diff --git a/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainDestination.kt b/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainDestination.kt new file mode 100644 index 00000000..71f916f7 --- /dev/null +++ b/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainDestination.kt @@ -0,0 +1,18 @@ +package ru.stersh.youamp.feature.main.ui + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.MusicNote +import androidx.compose.material.icons.rounded.Person +import androidx.compose.material.icons.rounded.Search +import androidx.compose.ui.graphics.vector.ImageVector +import ru.stersh.youamp.feature.main.R + +internal enum class MainDestination( + @StringRes val titleResId: Int, + val icon: ImageVector +) { + PERSONAL(R.string.personal_title, Icons.Rounded.Person), + EXPLORE(R.string.explore_title, Icons.Rounded.Search), + LIBRARY(R.string.library_title, Icons.Rounded.MusicNote), +} \ No newline at end of file diff --git a/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainScreen.kt b/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainScreen.kt index ab8ec0e2..b473dad1 100644 --- a/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainScreen.kt +++ b/feature/main/src/main/java/ru/stersh/youamp/feature/main/ui/MainScreen.kt @@ -2,31 +2,24 @@ package ru.stersh.youamp.feature.main.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.MusicNote -import androidx.compose.material.icons.rounded.Person -import androidx.compose.material.icons.rounded.Search import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavDestination.Companion.hierarchy -import androidx.navigation.NavGraph.Companion.findStartDestination -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController import org.koin.androidx.compose.koinViewModel import ru.stersh.youamp.core.ui.YouampPlayerTheme @@ -44,7 +37,6 @@ internal data class MainStateUi( @Composable fun MainScreen( miniPlayer: @Composable () -> Unit, - bigPlayer: @Composable () -> Unit, personal: @Composable () -> Unit, explore: @Composable () -> Unit, library: @Composable () -> Unit, @@ -57,7 +49,6 @@ fun MainScreen( MainScreen( state = state, miniPlayer = miniPlayer, - bigPlayer = bigPlayer, personal = personal, explore = explore, library = library, @@ -65,113 +56,53 @@ fun MainScreen( onSettingsClick = onSettingsClick ) } - -private val PERSONAL = "personal" -private val EXPLORE = "explore" -private val LIBRARY = "library" - @Composable internal fun MainScreen( state: MainStateUi, miniPlayer: @Composable () -> Unit, - bigPlayer: @Composable () -> Unit, personal: @Composable () -> Unit, explore: @Composable () -> Unit, library: @Composable () -> Unit, onAvatarClick: () -> Unit, onSettingsClick: () -> Unit ) { - val navController = rememberNavController() - ExpandableScaffold( - topBar = { - Toolbar( - serverName = state.serverInfo?.name, - avatarUrl = state.serverInfo?.avatarUrl, - onAvatarClick = onAvatarClick, - onSettingsClick = onSettingsClick - ) - }, - collapsedContent = { - miniPlayer() - }, - expandedContent = { - bigPlayer() - }, - bottomBar = { - NavigationBar { - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - NavigationBarItem( - selected = currentDestination?.hierarchy?.any { it.route == PERSONAL } == true, - onClick = { - navController.navigate(PERSONAL) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - label = { - Text(text = "Personal") - }, + var currentDestination by rememberSaveable { mutableStateOf(MainDestination.PERSONAL) } + NavigationSuiteScaffold( + navigationSuiteItems = { + MainDestination.entries.forEach { dest -> + item( icon = { Icon( - imageVector = Icons.Rounded.Person, + imageVector = dest.icon, contentDescription = null ) }, - ) - NavigationBarItem( - selected = currentDestination?.hierarchy?.any { it.route == EXPLORE } == true, - onClick = { - navController.navigate(EXPLORE) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, label = { - Text(text = "Explore") + Text(text = stringResource(dest.titleResId)) }, - icon = { - Icon( - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - } - ) - NavigationBarItem( - selected = currentDestination?.hierarchy?.any { it.route == LIBRARY } == true, + selected = currentDestination == dest, onClick = { - navController.navigate(LIBRARY) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - label = { - Text(text = "Library") - }, - icon = { - Icon( - imageVector = Icons.Rounded.MusicNote, - contentDescription = null - ) + currentDestination = dest } ) } - }, - modifier = Modifier.fillMaxSize() + } ) { - NavHost(navController, startDestination = PERSONAL) { - composable(PERSONAL) { personal() } - composable(EXPLORE) { explore() } - composable(LIBRARY) { library() } + Column { + Toolbar( + serverName = state.serverInfo?.name, + avatarUrl = state.serverInfo?.avatarUrl, + onAvatarClick = onAvatarClick, + onSettingsClick = onSettingsClick + ) + Box(modifier = Modifier.weight(1f)) { + when (currentDestination) { + MainDestination.PERSONAL -> personal() + MainDestination.EXPLORE -> explore() + MainDestination.LIBRARY -> library() + } + } + miniPlayer() } } } @@ -197,7 +128,6 @@ private fun MainScreenPreview() { ) ) }, - bigPlayer = {}, personal = { }, explore = {}, library = {}, diff --git a/feature/main/src/main/res/values-ca-rES/strings.xml b/feature/main/src/main/res/values-ca-rES/strings.xml index 7f818aa0..3ea04e70 100644 --- a/feature/main/src/main/res/values-ca-rES/strings.xml +++ b/feature/main/src/main/res/values-ca-rES/strings.xml @@ -1,7 +1,2 @@ - - Àlbums - Artistes - Llistes de reproducció - Preferits - + diff --git a/feature/main/src/main/res/values-de-rDE/strings.xml b/feature/main/src/main/res/values-de-rDE/strings.xml index 2aff45d1..3ea04e70 100644 --- a/feature/main/src/main/res/values-de-rDE/strings.xml +++ b/feature/main/src/main/res/values-de-rDE/strings.xml @@ -1,7 +1,2 @@ - - Alben - Interpreten - Wiedergabelisten - Favoriten - + diff --git a/feature/main/src/main/res/values-fr-rFR/strings.xml b/feature/main/src/main/res/values-fr-rFR/strings.xml index 48191241..3ea04e70 100644 --- a/feature/main/src/main/res/values-fr-rFR/strings.xml +++ b/feature/main/src/main/res/values-fr-rFR/strings.xml @@ -1,7 +1,2 @@ - - Albums - Artistes - Listes de lecture - Favoris - + diff --git a/feature/main/src/main/res/values-it-rIT/strings.xml b/feature/main/src/main/res/values-it-rIT/strings.xml index 31462dce..3ea04e70 100644 --- a/feature/main/src/main/res/values-it-rIT/strings.xml +++ b/feature/main/src/main/res/values-it-rIT/strings.xml @@ -1,7 +1,2 @@ - - Album - Artisti - Playlist - Preferiti - + diff --git a/feature/main/src/main/res/values-ru-rRU/strings.xml b/feature/main/src/main/res/values-ru-rRU/strings.xml index ac2858da..3ea04e70 100644 --- a/feature/main/src/main/res/values-ru-rRU/strings.xml +++ b/feature/main/src/main/res/values-ru-rRU/strings.xml @@ -1,7 +1,2 @@ - - Альбомы - Исполнители - Плейлисты - Избранное - + diff --git a/feature/main/src/main/res/values-zh-rCN/strings.xml b/feature/main/src/main/res/values-zh-rCN/strings.xml index c2f0239c..3ea04e70 100644 --- a/feature/main/src/main/res/values-zh-rCN/strings.xml +++ b/feature/main/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,2 @@ - - 专辑 - 艺术家 - 歌单 - 收藏夹 - + diff --git a/feature/main/src/main/res/values/strings.xml b/feature/main/src/main/res/values/strings.xml index 1a656d63..ed95cc80 100644 --- a/feature/main/src/main/res/values/strings.xml +++ b/feature/main/src/main/res/values/strings.xml @@ -1,7 +1,6 @@ - Albums - Artists - Playlists - Favorites + Personal + Explore + Library \ No newline at end of file diff --git a/feature/player/screen/src/main/java/ru/stersh/youamp/feature/player/screen/ui/PlayerScreen.kt b/feature/player/screen/src/main/java/ru/stersh/youamp/feature/player/screen/ui/PlayerScreen.kt index 37d5ba38..3eaf0ce9 100644 --- a/feature/player/screen/src/main/java/ru/stersh/youamp/feature/player/screen/ui/PlayerScreen.kt +++ b/feature/player/screen/src/main/java/ru/stersh/youamp/feature/player/screen/ui/PlayerScreen.kt @@ -2,16 +2,16 @@ package ru.stersh.youamp.feature.player.screen.ui import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio +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.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Album import androidx.compose.material.icons.rounded.SkipNext @@ -73,7 +73,6 @@ private fun PlayerScreen( onBackClick: () -> Unit, onPlayQueueClick: () -> Unit ) { - val scrollState = rememberScrollState() Scaffold( topBar = { TopAppBar( @@ -89,129 +88,194 @@ private fun PlayerScreen( ) } ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .padding(it) - .verticalScroll(scrollState) - ) { - - Artwork( - artworkUrl = state.artworkUrl, - placeholder = Icons.Rounded.Album, - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth() - .aspectRatio(1f) - ) - - Spacer(modifier = Modifier.height(24.dp)) - - Text( - text = state.title.orEmpty(), - style = MaterialTheme.typography.titleLarge, - textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - modifier = Modifier.padding(start = 24.dp, end = 24.dp) - ) - - Text( - text = state.artist.orEmpty(), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.secondary, - modifier = Modifier - .padding(bottom = 12.dp, start = 24.dp, end = 24.dp) - ) - - Spacer(modifier = Modifier.height(12.dp)) - - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - PlayerSlider( - progress = state.progress, - onSeek = onSeek, - modifier = Modifier - .padding(horizontal = 8.dp) - ) + BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + if (maxWidth > 600.dp) { + val height = maxHeight * 0.6f Row( modifier = Modifier - .padding(top = 8.dp) - .fillMaxWidth(), + .padding(it) + .align(Alignment.Center), verticalAlignment = Alignment.CenterVertically ) { - Text( - text = state.currentTime.orEmpty(), - style = MaterialTheme.typography.labelLarge + Artwork( + artworkUrl = state.artworkUrl, + placeholder = Icons.Rounded.Album, + modifier = Modifier + .padding(horizontal = 16.dp) + .size(height) ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = state.totalTime.orEmpty(), - style = MaterialTheme.typography.labelLarge + PlayerLayout( + state = state, + tabletLayout = true, + onSeek = onSeek, + onRepeatModeChanged = onRepeatModeChanged, + onShuffleModeChanged = onShuffleModeChanged, + onFavoriteChanged = onFavoriteChanged, + onPlayPause = onPlayPause, + onPrevious = onPrevious, + onNext = onNext ) } - } - - Spacer(modifier = Modifier.height(48.dp)) - - Row( - modifier = Modifier.padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - FilledTonalIconButton( - onClick = onPrevious, - modifier = Modifier.size(56.dp) + } else { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(it) ) { - Icon( - imageVector = Icons.Rounded.SkipPrevious, - contentDescription = stringResource(R.string.previous_song_description) - ) - } - PlayPauseButton( - isPlayed = state.isPlaying, - onPlayedChanged = onPlayPause - ) + Artwork( + artworkUrl = state.artworkUrl, + placeholder = Icons.Rounded.Album, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + .aspectRatio(1f) + ) - FilledTonalIconButton( - onClick = onNext, - modifier = Modifier.size(56.dp) - ) { - Icon( - imageVector = Icons.Rounded.SkipNext, - contentDescription = stringResource(R.string.next_song_description) + PlayerLayout( + state = state, + tabletLayout = false, + onSeek = onSeek, + onRepeatModeChanged = onRepeatModeChanged, + onShuffleModeChanged = onShuffleModeChanged, + onFavoriteChanged = onFavoriteChanged, + onPlayPause = onPlayPause, + onPrevious = onPrevious, + onNext = onNext ) } } + } - Spacer(modifier = Modifier.weight(1f)) + } +} + +@Composable +private fun PlayerLayout( + state: StateUi, + tabletLayout: Boolean, + onSeek: (progress: Float) -> Unit, + onRepeatModeChanged: (newRepeatMode: RepeatModeUi) -> Unit, + onShuffleModeChanged: (newShuffleMode: ShuffleModeUi) -> Unit, + onFavoriteChanged: (isFavorite: Boolean) -> Unit, + onPlayPause: () -> Unit, + onPrevious: () -> Unit, + onNext: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + ) { + Text( + text = state.title.orEmpty(), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.padding(horizontal = 24.dp) + ) + + Text( + text = state.artist.orEmpty(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.secondary, + modifier = Modifier + .padding(bottom = 12.dp) + .padding(horizontal = 24.dp) + ) + Spacer(modifier = Modifier.height(12.dp)) + + Column( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + PlayerSlider( + progress = state.progress, + onSeek = onSeek, + modifier = Modifier + .padding(horizontal = 8.dp) + ) Row( - horizontalArrangement = Arrangement.SpaceAround, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically ) { - ShuffleButton( - shuffleMode = state.shuffleMode, - onShuffleModeChanged = onShuffleModeChanged + Text( + text = state.currentTime.orEmpty(), + style = MaterialTheme.typography.labelLarge ) - RepeatButton( - repeatMode = state.repeatMode, - onRepeatModeChanged = onRepeatModeChanged + Spacer(modifier = Modifier.weight(1f)) + Text( + text = state.totalTime.orEmpty(), + style = MaterialTheme.typography.labelLarge ) - FavoriteButton( - isFavorite = state.isFavorite, - onFavoriteChanged = onFavoriteChanged + } + } + + Spacer(modifier = Modifier.height(48.dp)) + + Row( + modifier = Modifier.padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + FilledTonalIconButton( + onClick = onPrevious, + modifier = Modifier.size(56.dp) + ) { + Icon( + imageVector = Icons.Rounded.SkipPrevious, + contentDescription = stringResource(R.string.previous_song_description) + ) + } + + PlayPauseButton( + isPlayed = state.isPlaying, + onPlayedChanged = onPlayPause + ) + + FilledTonalIconButton( + onClick = onNext, + modifier = Modifier.size(56.dp) + ) { + Icon( + imageVector = Icons.Rounded.SkipNext, + contentDescription = stringResource(R.string.next_song_description) ) } } + + if (!tabletLayout) { + Spacer(modifier = Modifier.weight(1f)) + } + + Row( + horizontalArrangement = Arrangement.SpaceAround, + modifier = Modifier.fillMaxWidth() + ) { + ShuffleButton( + shuffleMode = state.shuffleMode, + onShuffleModeChanged = onShuffleModeChanged + ) + RepeatButton( + repeatMode = state.repeatMode, + onRepeatModeChanged = onRepeatModeChanged + ) + FavoriteButton( + isFavorite = state.isFavorite, + onFavoriteChanged = onFavoriteChanged + ) + } } } @Composable @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview +@Preview(name = "5-inch Device Landscape", widthDp = 640, heightDp = 360) +@Preview(name = "10-inch Tablet Landscape", widthDp = 960, heightDp = 600) private fun PlayerScreenPreview() { YouampPlayerTheme { val state = StateUi( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c46f1c84..cda11303 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ junit-version = "1.2.1" espresso-core = "3.6.1" appcompat = "1.7.0" paging = "3.3.5" +material3 = "1.3.1" [libraries] koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } @@ -59,9 +60,10 @@ compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } -compose-material3 = "androidx.compose.material3:material3:1.3.1" +compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } compose-material-icons-core = { module = "androidx.compose.material:material-icons-core" } compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +compose-material3-adaptiveNavigationSuite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3" } compose-icons-simple = { module = "br.com.devsrsouza.compose.icons:simple-icons", version.ref = "compose-icons" } compose-constraintLayout = "androidx.constraintlayout:constraintlayout-compose:1.1.0" material = { group = "com.google.android.material", name = "material", version.ref = "google-material" }