feat(orientation): полноценная поддержка альбомной ориентации
- боковой nav-rail слева вместо нижнего бара в альбоме (SideNavRail) - мини-плеер уезжает под контент в альбомной раскладке - плеер эфира: двухпанельный (обложка слева, инфо/эквалайзер/контролы справа) - плеер записи: слева управление, справа прокручиваемый список треков - сетки станций и избранного: 4 колонки в альбоме вместо 2 - хелпер isLandscape() через LocalConfiguration Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -86,17 +86,12 @@ fun PlayerBottomSheet(
|
||||
val spectrum by viewModel.spectrum.collectAsState()
|
||||
val vizStyle by viewModel.visualizerStyle.collectAsState()
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(colors.bgBase)
|
||||
.navigationBarsPadding()
|
||||
// Скролл — чтобы на телефонах с меньшей высотой в dp (высокий dpi)
|
||||
// низ плеера (кнопка «Текст песни») не обрезался шторкой.
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp, vertical = 20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
val landscape = com.radiola.ui.util.isLandscape()
|
||||
|
||||
// ── Секции плеера как лямбды: переиспользуются в портретной (колонка)
|
||||
// и альбомной (две панели) раскладках. ──
|
||||
|
||||
val labelSection: @Composable () -> Unit = {
|
||||
// Метка «В ЭФИРЕ» + чип качества справа (если у станции есть варианты)
|
||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
@@ -118,8 +113,9 @@ fun PlayerBottomSheet(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(6.dp))
|
||||
}
|
||||
|
||||
val nameSection: @Composable () -> Unit = {
|
||||
// Название радиостанции — под меткой, над обложкой
|
||||
Text(
|
||||
text = station?.name ?: "",
|
||||
@@ -131,12 +127,13 @@ fun PlayerBottomSheet(
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
||||
modifier = Modifier.basicMarquee()
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
val coverSection: @Composable (Dp) -> Unit = { coverSize ->
|
||||
// Обложка станции/трека
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(190.dp)
|
||||
.size(coverSize)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(colors.surface2),
|
||||
contentAlignment = Alignment.Center
|
||||
@@ -155,8 +152,9 @@ fun PlayerBottomSheet(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(14.dp))
|
||||
}
|
||||
|
||||
val trackInfoSection: @Composable () -> Unit = {
|
||||
// Название трека и исполнитель с Crossfade при смене
|
||||
Crossfade(
|
||||
targetState = track?.song to track?.artist,
|
||||
@@ -183,8 +181,9 @@ fun PlayerBottomSheet(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
}
|
||||
|
||||
val visualizerSection: @Composable () -> Unit = {
|
||||
// Живой эквалайзер — вместо прогресс-бара (эфир нельзя перематывать)
|
||||
com.radiola.ui.components.Visualizer(
|
||||
style = com.radiola.ui.components.VisualizerStyle.fromKey(vizStyle),
|
||||
@@ -195,8 +194,9 @@ fun PlayerBottomSheet(
|
||||
.fillMaxWidth()
|
||||
.height(if (com.radiola.ui.components.VisualizerStyle.fromKey(vizStyle) == com.radiola.ui.components.VisualizerStyle.RADIAL) 120.dp else 40.dp)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
val controlsSection: @Composable () -> Unit = {
|
||||
// Управление воспроизведением
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
|
||||
@@ -286,8 +286,9 @@ fun PlayerBottomSheet(
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
}
|
||||
|
||||
val servicesSection: @Composable () -> Unit = {
|
||||
// Ряд кнопок музыкальных сервисов
|
||||
if (enabledServices.isNotEmpty()) {
|
||||
LazyRow(
|
||||
@@ -307,16 +308,16 @@ fun PlayerBottomSheet(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
|
||||
val lyricsSection: @Composable () -> Unit = {
|
||||
// Кнопка «Текст песни» — активна только когда играет трек.
|
||||
// Явная пилюля с фоном: на реальном телефоне мелкий TextButton почти не виден.
|
||||
if (track != null) {
|
||||
val lyricsInteraction = remember { MutableInteractionSource() }
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.clip(RoundedCornerShape(50))
|
||||
.background(colors.surface2)
|
||||
.pressScale(interactionSource = lyricsInteraction)
|
||||
@@ -343,6 +344,77 @@ fun PlayerBottomSheet(
|
||||
}
|
||||
}
|
||||
|
||||
if (landscape) {
|
||||
// Альбом: слева обложка с названием станции, справа — трек, эквалайзер,
|
||||
// управление и сервисы (правая панель скроллится на низких экранах).
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(colors.bgBase)
|
||||
.navigationBarsPadding()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(0.42f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
labelSection()
|
||||
Spacer(Modifier.height(6.dp))
|
||||
nameSection()
|
||||
Spacer(Modifier.height(14.dp))
|
||||
coverSection(170.dp)
|
||||
}
|
||||
Spacer(Modifier.width(24.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(0.58f)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
trackInfoSection()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
visualizerSection()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
controlsSection()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
servicesSection()
|
||||
if (track != null) {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
lyricsSection()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(colors.bgBase)
|
||||
.navigationBarsPadding()
|
||||
// Скролл — чтобы на телефонах с меньшей высотой в dp (высокий dpi)
|
||||
// низ плеера (кнопка «Текст песни») не обрезался шторкой.
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp, vertical = 20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
labelSection()
|
||||
Spacer(Modifier.height(6.dp))
|
||||
nameSection()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
coverSection(190.dp)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
trackInfoSection()
|
||||
Spacer(Modifier.height(20.dp))
|
||||
visualizerSection()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
controlsSection()
|
||||
Spacer(Modifier.height(20.dp))
|
||||
servicesSection()
|
||||
if (enabledServices.isNotEmpty()) Spacer(Modifier.height(12.dp))
|
||||
lyricsSection()
|
||||
}
|
||||
}
|
||||
|
||||
// Шторка выбора качества
|
||||
if (showQuality && station != null) {
|
||||
val qualities = station.qualities
|
||||
|
||||
Reference in New Issue
Block a user