From 116ab95abda6721ac537420a836392e40d8c9a95 Mon Sep 17 00:00:00 2001 From: nk Date: Mon, 1 Jun 2026 12:58:25 +0300 Subject: [PATCH] feat(ui): add shared components (StationCard, TrackListItem, SearchBar, FilterChips, MiniPlayer, EmptyState) --- .../com/radiola/ui/components/EmptyState.kt | 27 +++++++ .../com/radiola/ui/components/FilterChips.kt | 39 +++++++++++ .../com/radiola/ui/components/MiniPlayer.kt | 70 +++++++++++++++++++ .../com/radiola/ui/components/SearchBar.kt | 43 ++++++++++++ .../com/radiola/ui/components/StationCard.kt | 64 +++++++++++++++++ .../radiola/ui/components/TrackListItem.kt | 61 ++++++++++++++++ 6 files changed, 304 insertions(+) create mode 100644 app/src/main/java/com/radiola/ui/components/EmptyState.kt create mode 100644 app/src/main/java/com/radiola/ui/components/FilterChips.kt create mode 100644 app/src/main/java/com/radiola/ui/components/MiniPlayer.kt create mode 100644 app/src/main/java/com/radiola/ui/components/SearchBar.kt create mode 100644 app/src/main/java/com/radiola/ui/components/StationCard.kt create mode 100644 app/src/main/java/com/radiola/ui/components/TrackListItem.kt diff --git a/app/src/main/java/com/radiola/ui/components/EmptyState.kt b/app/src/main/java/com/radiola/ui/components/EmptyState.kt new file mode 100644 index 0000000..b7fe9fd --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/EmptyState.kt @@ -0,0 +1,27 @@ +package com.radiola.ui.components + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun EmptyState( + message: String, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = message, + style = MaterialTheme.typography.bodyLarge, + color = Color(0xFF888888) + ) + } +} diff --git a/app/src/main/java/com/radiola/ui/components/FilterChips.kt b/app/src/main/java/com/radiola/ui/components/FilterChips.kt new file mode 100644 index 0000000..f3fa805 --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/FilterChips.kt @@ -0,0 +1,39 @@ +package com.radiola.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun FilterChips( + tags: List, + selectedTag: String?, + onTagSelected: (String?) -> Unit, + modifier: Modifier = Modifier +) { + LazyRow( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + item { + FilterChip( + selected = selectedTag == null, + onClick = { onTagSelected(null) }, + label = { Text("Все") } + ) + } + items(tags) { tag -> + FilterChip( + selected = selectedTag == tag, + onClick = { onTagSelected(tag) }, + label = { Text(tag) } + ) + } + } +} diff --git a/app/src/main/java/com/radiola/ui/components/MiniPlayer.kt b/app/src/main/java/com/radiola/ui/components/MiniPlayer.kt new file mode 100644 index 0000000..9dd282d --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/MiniPlayer.kt @@ -0,0 +1,70 @@ +package com.radiola.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.composables.icons.lucide.Lucide +import com.composables.icons.lucide.Pause +import com.composables.icons.lucide.Play +import com.radiola.domain.model.Track + +@Composable +fun MiniPlayer( + stationName: String, + track: Track?, + isPlaying: Boolean, + onClick: () -> Unit, + onPlayPause: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(64.dp) + .background(Color(0xFF1E1E1E)) + .clickable(onClick = onClick) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = track?.coverUrl, + contentDescription = null, + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(6.dp)) + ) + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = stationName, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1 + ) + Text( + text = track?.let { "${it.artist} — ${it.song}" } ?: "", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + maxLines = 1 + ) + } + IconButton(onClick = onPlayPause) { + Icon( + imageVector = if (isPlaying) Lucide.Pause else Lucide.Play, + contentDescription = if (isPlaying) "Pause" else "Play", + tint = Color.White + ) + } + } +} diff --git a/app/src/main/java/com/radiola/ui/components/SearchBar.kt b/app/src/main/java/com/radiola/ui/components/SearchBar.kt new file mode 100644 index 0000000..f038698 --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/SearchBar.kt @@ -0,0 +1,43 @@ +package com.radiola.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.composables.icons.lucide.Lucide +import com.composables.icons.lucide.Search + +@Composable +fun SearchBar( + query: String, + onQueryChange: (String) -> Unit, + placeholder: String = "Поиск станции...", + modifier: Modifier = Modifier +) { + TextField( + value = query, + onValueChange = onQueryChange, + modifier = modifier + .fillMaxWidth() + .background(Color(0xFF2A2A2A), RoundedCornerShape(8.dp)), + placeholder = { Text(placeholder, color = Color(0xFF888888)) }, + leadingIcon = { Icon(Lucide.Search, contentDescription = null, tint = Color(0xFF888888)) }, + singleLine = true, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color(0xFF2A2A2A), + unfocusedContainerColor = Color(0xFF2A2A2A), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedTextColor = Color.White, + unfocusedTextColor = Color.White + ) + ) +} diff --git a/app/src/main/java/com/radiola/ui/components/StationCard.kt b/app/src/main/java/com/radiola/ui/components/StationCard.kt new file mode 100644 index 0000000..080e9b8 --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/StationCard.kt @@ -0,0 +1,64 @@ +package com.radiola.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.radiola.domain.model.Station + +@Composable +fun StationCard( + station: Station, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier + .aspectRatio(1f) + .clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFF1E1E1E)) + ) { + Column { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + .background( + Brush.linearGradient( + colors = listOf( + Color(0xFF667eea), + Color(0xFF764ba2) + ) + ) + ) + ) { + AsyncImage( + model = station.coverUrl, + contentDescription = station.name, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + Text( + text = station.name, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(12.dp), + maxLines = 1 + ) + } + } +} diff --git a/app/src/main/java/com/radiola/ui/components/TrackListItem.kt b/app/src/main/java/com/radiola/ui/components/TrackListItem.kt new file mode 100644 index 0000000..9e177b9 --- /dev/null +++ b/app/src/main/java/com/radiola/ui/components/TrackListItem.kt @@ -0,0 +1,61 @@ +package com.radiola.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.radiola.domain.model.Track +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +@Composable +fun TrackListItem( + track: Track, + timestamp: Long? = null, + onClick: () -> Unit = {}, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = track.coverUrl, + contentDescription = null, + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(8.dp)) + ) + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = "${track.artist} — ${track.song}", + style = MaterialTheme.typography.bodyMedium, + maxLines = 1 + ) + Text( + text = track.stationName, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + timestamp?.let { + Text( + text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(it)), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) + ) + } + } +}