feat(ui): add StationsScreen and StationsViewModel
This commit is contained in:
77
app/src/main/java/com/radiola/ui/stations/StationsScreen.kt
Normal file
77
app/src/main/java/com/radiola/ui/stations/StationsScreen.kt
Normal 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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user