feat(ui): add shared components (StationCard, TrackListItem, SearchBar, FilterChips, MiniPlayer, EmptyState)

This commit is contained in:
nk
2026-06-01 12:58:25 +03:00
parent 28309c201e
commit 116ab95abd
6 changed files with 304 additions and 0 deletions

View File

@@ -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)
)
}
}

View File

@@ -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<String>,
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) }
)
}
}
}

View File

@@ -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
)
}
}
}

View File

@@ -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
)
)
}

View File

@@ -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
)
}
}
}

View File

@@ -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)
)
}
}
}