feat(ui): add StationsScreen and StationsViewModel

This commit is contained in:
nk
2026-06-01 13:00:12 +03:00
parent 116ab95abd
commit 1c902b5607
2 changed files with 149 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
package com.radiola.ui.stations
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.radiola.ui.components.*
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StationsScreen(
onStationClick: (Int) -> Unit,
modifier: Modifier = Modifier,
viewModel: StationsViewModel = hiltViewModel()
) {
val stations by viewModel.stations.collectAsState()
val tags by viewModel.tags.collectAsState()
val searchQuery by viewModel.searchQuery.collectAsState()
val selectedTag by viewModel.selectedTag.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
val error by viewModel.error.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = { Text("Радио") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
)
)
}
) { padding ->
Column(
modifier = modifier
.fillMaxSize()
.padding(padding)
) {
SearchBar(
query = searchQuery,
onQueryChange = viewModel::onSearchQueryChange,
modifier = Modifier.padding(16.dp)
)
FilterChips(
tags = tags,
selectedTag = selectedTag,
onTagSelected = viewModel::onTagSelected,
modifier = Modifier.padding(vertical = 8.dp)
)
when {
isLoading -> Box(modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
error != null -> EmptyState(message = error!!)
stations.isEmpty() -> EmptyState(message = "Станции не найдены")
else -> LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(stations, key = { it.id }) { station ->
StationCard(
station = station,
onClick = { onStationClick(station.id) }
)
}
}
}
}
}
}

View File

@@ -0,0 +1,72 @@
package com.radiola.ui.stations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.radiola.domain.model.Station
import com.radiola.domain.usecase.GetStationsUseCase
import com.radiola.domain.usecase.PlayStationUseCase
import com.radiola.domain.usecase.ToggleFavoriteUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class StationsViewModel @Inject constructor(
private val getStationsUseCase: GetStationsUseCase,
private val playStationUseCase: PlayStationUseCase,
private val toggleFavoriteUseCase: ToggleFavoriteUseCase
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
private val _selectedTag = MutableStateFlow<String?>(null)
val selectedTag: StateFlow<String?> = _selectedTag.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
val stations: StateFlow<List<Station>> = combine(
getStationsUseCase(),
_searchQuery,
_selectedTag
) { allStations, query, tag ->
allStations
.filter { station ->
tag == null || station.tags.contains(tag) || station.genre.equals(tag, ignoreCase = true)
}
.filter { station ->
query.isBlank() ||
station.name.contains(query, ignoreCase = true) ||
station.genre.contains(query, ignoreCase = true)
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val tags: StateFlow<List<String>> = getStationsUseCase()
.map { stations -> stations.flatMap { it.tags }.distinct().sorted() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun onSearchQueryChange(query: String) {
_searchQuery.value = query
}
fun onTagSelected(tag: String?) {
_selectedTag.value = tag
}
fun playStation(station: Station) {
viewModelScope.launch {
playStationUseCase(station)
}
}
fun toggleFavorite(station: Station) {
viewModelScope.launch {
toggleFavoriteUseCase(station)
}
}
}