feat(ui): рестайл всех экранов + плеер + официальные mono-логотипы сервисов

- экраны (Станции/Избранное/История/Записи/Настройки/Вход): двухцветные
  заголовки, токены темы, EmptyState, анимации появления и перестановки
- AuthScreen: брендовый локап (AppMark + RadiolaWordmark)
- PlayerBottomSheet: живой эфир — LiveEqualizer вместо перемотки,
  Crossfade трека и play/pause, pressScale, анимация избранного/записи
- кнопки музыкальных сервисов: монохромные официальные логотипы
  (vector drawable из Simple Icons CC0 + Yandex), маппинг serviceLogoRes
- DeeplinkBottomSheet: сетка сервисов с логотипами
This commit is contained in:
nk
2026-06-02 21:31:16 +03:00
parent d652dc399a
commit f604ad42e8
16 changed files with 1195 additions and 499 deletions

View File

@@ -1,5 +1,9 @@
package com.radiola.ui.stations
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
@@ -9,12 +13,16 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.radiola.domain.model.Station
import com.radiola.ui.components.*
import com.radiola.ui.theme.RadiolaTheme
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun StationsScreen(
onStationClick: (Station) -> Unit,
@@ -28,43 +36,63 @@ fun StationsScreen(
val isLoading by viewModel.isLoading.collectAsState()
val error by viewModel.error.collectAsState()
val favoriteIds by viewModel.favoriteIds.collectAsState()
val colors = RadiolaTheme.colors
Column(modifier = modifier.fillMaxSize()) {
TopAppBar(
title = { Text("Радио") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
)
Column(
modifier = modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
// Двухцветный заголовок экрана
Text(
text = buildAnnotatedString {
withStyle(SpanStyle(color = colors.textPrimary)) { append("Откройте ") }
withStyle(SpanStyle(color = colors.accent)) { append("радио") }
},
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(top = 20.dp, bottom = 16.dp)
)
when {
isLoading && stations.isEmpty() -> Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
CircularProgressIndicator(color = colors.accent)
}
stations.isEmpty() -> Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp)
stations.isEmpty() -> {
AnimatedVisibility(
visible = true,
enter = fadeIn() + slideInVertically()
) {
EmptyState(message = error ?: "Станции не найдены")
if (selectedTag != null) {
Button(onClick = { viewModel.onTagSelected(null) }) {
Text("Показать все")
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
EmptyState(message = error ?: "Станции не найдены")
if (selectedTag != null) {
OutlinedButton(
onClick = { viewModel.onTagSelected(null) },
colors = ButtonDefaults.outlinedButtonColors(
contentColor = colors.accent
),
border = androidx.compose.foundation.BorderStroke(1.dp, colors.accent)
) {
Text("Показать все")
}
}
}
}
}
else -> LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
contentPadding = PaddingValues(bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
item(span = { GridItemSpan(maxLineSpan) }) {
SearchBar(
@@ -85,7 +113,8 @@ fun StationsScreen(
station = station,
isFavorite = favoriteIds.contains(station.id),
onClick = { onStationClick(station) },
onFavoriteClick = { viewModel.toggleFavorite(station) }
onFavoriteClick = { viewModel.toggleFavorite(station) },
modifier = Modifier.animateItemPlacement()
)
}
}