diff --git a/app/src/main/java/com/radiola/ui/stations/StationsScreen.kt b/app/src/main/java/com/radiola/ui/stations/StationsScreen.kt new file mode 100644 index 0000000..8720d45 --- /dev/null +++ b/app/src/main/java/com/radiola/ui/stations/StationsScreen.kt @@ -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) } + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/radiola/ui/stations/StationsViewModel.kt b/app/src/main/java/com/radiola/ui/stations/StationsViewModel.kt new file mode 100644 index 0000000..3f26671 --- /dev/null +++ b/app/src/main/java/com/radiola/ui/stations/StationsViewModel.kt @@ -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 = _searchQuery.asStateFlow() + + private val _selectedTag = MutableStateFlow(null) + val selectedTag: StateFlow = _selectedTag.asStateFlow() + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading.asStateFlow() + + private val _error = MutableStateFlow(null) + val error: StateFlow = _error.asStateFlow() + + val stations: StateFlow> = 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> = 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) + } + } +}