feat(ui): add PlayerBottomSheet and PlayerViewModel

This commit is contained in:
nk
2026-06-01 13:02:16 +03:00
parent 1c902b5607
commit ee91837910
2 changed files with 237 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
package com.radiola.ui.player
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
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.DeeplinkService
import com.radiola.domain.model.Station
import com.radiola.domain.model.Track
@Composable
fun PlayerBottomSheet(
station: Station?,
track: Track?,
isPlaying: Boolean,
onPlayPause: () -> Unit,
modifier: Modifier = Modifier,
viewModel: PlayerViewModel = hiltViewModel()
) {
val context = LocalContext.current
val enabledServices by viewModel.enabledServices.collectAsState()
Column(
modifier = modifier
.fillMaxWidth()
.background(
brush = Brush.verticalGradient(
colors = listOf(Color(0xFF1a1a2e), Color(0xFF121212))
)
)
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
station?.let {
Text(
text = it.name,
style = MaterialTheme.typography.labelMedium,
color = Color(0xFF888888)
)
}
Spacer(modifier = Modifier.height(24.dp))
Box(
modifier = Modifier
.size(240.dp)
.clip(RoundedCornerShape(20.dp))
.background(
Brush.linearGradient(listOf(Color(0xFF667eea), Color(0xFF764ba2)))
)
) {
AsyncImage(
model = track?.coverUrl ?: station?.coverUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = track?.song ?: "",
style = MaterialTheme.typography.headlineMedium,
maxLines = 1
)
Text(
text = track?.artist ?: "",
style = MaterialTheme.typography.bodyLarge,
color = Color(0xFF888888),
maxLines = 1
)
Spacer(modifier = Modifier.height(20.dp))
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 8.dp)
) {
items(enabledServices) { service ->
DeeplinkButton(
service = service,
onClick = {
track?.let { t ->
val url = viewModel.getDeeplinkUrl(t, service)
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}
)
}
}
Spacer(modifier = Modifier.height(30.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
ControlButton(size = 56.dp, onClick = { })
ControlButton(
size = 72.dp,
isPlay = true,
isPlaying = isPlaying,
onClick = onPlayPause
)
ControlButton(size = 56.dp, onClick = { })
}
}
}
@Composable
private fun DeeplinkButton(
service: DeeplinkService,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.size(48.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF2A2A2A))
.clickable(onClick = onClick),
contentAlignment = Alignment.Center
) {
Text(
text = service.displayName.take(2),
style = MaterialTheme.typography.labelSmall,
color = Color(0xFF888888)
)
}
}
@Composable
private fun ControlButton(
size: androidx.compose.ui.unit.Dp,
isPlay: Boolean = false,
isPlaying: Boolean = false,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.size(size)
.clip(CircleShape)
.background(if (isPlay) Color.White else Color(0xFF2A2A2A))
.clickable(onClick = onClick),
contentAlignment = Alignment.Center
) {
if (isPlay) {
Icon(
imageVector = if (isPlaying) Lucide.Pause else Lucide.Play,
contentDescription = if (isPlaying) "Pause" else "Play",
tint = Color.Black,
modifier = Modifier.size(size * 0.4f)
)
}
}
}