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" }