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,19 +1,29 @@
package com.radiola.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Music
import com.radiola.deeplink.DeeplinkNavigator
import com.radiola.domain.model.DeeplinkService
import com.radiola.domain.model.Track
import com.radiola.ui.player.PlayerViewModel
import com.radiola.ui.theme.RadiolaTheme
import com.radiola.ui.theme.pressScale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -25,21 +35,44 @@ fun DeeplinkBottomSheet(
) {
val context = androidx.compose.ui.platform.LocalContext.current
val enabledServices by viewModel.enabledServices.collectAsState()
val colors = RadiolaTheme.colors
ModalBottomSheet(
onDismissRequest = onDismiss,
modifier = modifier
modifier = modifier,
containerColor = colors.elevated
) {
Text(
text = "Найти трек",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
)
LazyColumn {
Column(modifier = Modifier.padding(horizontal = 20.dp, vertical = 8.dp)) {
Text(
text = "Найти трек",
style = MaterialTheme.typography.titleLarge,
color = colors.textPrimary
)
Spacer(Modifier.height(4.dp))
Text(
text = "${track.artist}${track.song}",
style = MaterialTheme.typography.bodyMedium,
color = colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(Modifier.height(20.dp))
}
// Сетка кнопок сервисов — монохромные, без официальных логотипов
LazyVerticalGrid(
columns = GridCells.Fixed(4),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
contentPadding = PaddingValues(bottom = 32.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(enabledServices) { service ->
ListItem(
headlineContent = { Text(service.displayName) },
modifier = Modifier.clickable {
ServiceGridBtn(
service = service,
onClick = {
DeeplinkNavigator.openSearch(context, track, service)
onDismiss()
}
@@ -48,3 +81,52 @@ fun DeeplinkBottomSheet(
}
}
}
/** Монохромная кнопка сервиса в сетке боттомшита. */
@Composable
private fun ServiceGridBtn(
service: DeeplinkService,
onClick: () -> Unit
) {
val colors = RadiolaTheme.colors
val interaction = remember { MutableInteractionSource() }
Column(
modifier = Modifier
.pressScale(interactionSource = interaction)
.clickable(interactionSource = interaction, indication = null, onClick = onClick),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
Box(
modifier = Modifier
.size(52.dp)
.clip(CircleShape)
.background(colors.surface2),
contentAlignment = Alignment.Center
) {
val logoRes = serviceLogoRes(service)
if (logoRes != null) {
Icon(
painter = androidx.compose.ui.res.painterResource(logoRes),
contentDescription = service.displayName,
tint = colors.textSecondary,
modifier = Modifier.size(24.dp)
)
} else {
Icon(
imageVector = Lucide.Music,
contentDescription = service.displayName,
tint = colors.textSecondary,
modifier = Modifier.size(22.dp)
)
}
}
Text(
text = service.displayName,
style = MaterialTheme.typography.labelSmall,
color = colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}