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:
@@ -1,5 +1,10 @@
|
||||
package com.radiola.ui.favorites
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
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.LazyVerticalGrid
|
||||
@@ -7,14 +12,20 @@ import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
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.composables.icons.lucide.Heart
|
||||
import com.composables.icons.lucide.Lucide
|
||||
import com.radiola.domain.model.Station
|
||||
import com.radiola.ui.components.EmptyState
|
||||
import com.radiola.ui.components.StationCard
|
||||
import com.radiola.ui.theme.RadiolaTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun FavoritesScreen(
|
||||
onStationClick: (Station) -> Unit,
|
||||
@@ -23,40 +34,69 @@ fun FavoritesScreen(
|
||||
) {
|
||||
val favorites by viewModel.favorites.collectAsState()
|
||||
val favoriteIds by viewModel.favoriteIds.collectAsState()
|
||||
val colors = RadiolaTheme.colors
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Избранное") },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
// Заголовок с двухцветным текстом и счётчиком
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 20.dp, bottom = 20.dp),
|
||||
verticalAlignment = androidx.compose.ui.Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = colors.textPrimary)) { append("Из") }
|
||||
withStyle(SpanStyle(color = colors.accent)) { append("бранное") }
|
||||
},
|
||||
style = MaterialTheme.typography.headlineLarge
|
||||
)
|
||||
if (favorites.isNotEmpty()) {
|
||||
Text(
|
||||
text = "${favorites.size}",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = colors.textSecondary,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
if (favorites.isEmpty()) {
|
||||
EmptyState(
|
||||
message = "Нет избранных станций",
|
||||
modifier = Modifier.fillMaxSize().padding(padding)
|
||||
)
|
||||
} else {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(2),
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(favorites, key = { it.id }) { station ->
|
||||
StationCard(
|
||||
station = station,
|
||||
isFavorite = favoriteIds.contains(station.id),
|
||||
onClick = { onStationClick(station) },
|
||||
onFavoriteClick = { viewModel.toggleFavorite(station) }
|
||||
|
||||
Crossfade(
|
||||
targetState = favorites.isEmpty(),
|
||||
label = "favoritesState"
|
||||
) { isEmpty ->
|
||||
if (isEmpty) {
|
||||
AnimatedVisibility(
|
||||
visible = true,
|
||||
enter = fadeIn() + slideInVertically()
|
||||
) {
|
||||
EmptyState(
|
||||
message = "Нет избранных станций",
|
||||
icon = Lucide.Heart,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(2),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
items(favorites, key = { it.id }) { station ->
|
||||
StationCard(
|
||||
station = station,
|
||||
isFavorite = favoriteIds.contains(station.id),
|
||||
onClick = { onStationClick(station) },
|
||||
onFavoriteClick = { viewModel.toggleFavorite(station) },
|
||||
modifier = Modifier.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user