Add shared discover screen

This commit is contained in:
KhooLy 2026-07-13 17:45:48 +03:00
parent fcb9df3de3
commit 4f4b049f2d
3 changed files with 169 additions and 0 deletions

View file

@ -27,6 +27,9 @@ import com.fluxa.app.shared.feature.catalog.CatalogItemUiModel
import com.fluxa.app.shared.feature.detail.DetailAction
import com.fluxa.app.shared.feature.detail.DetailScreen
import com.fluxa.app.shared.feature.detail.DetailUiState
import com.fluxa.app.shared.feature.discover.DiscoverAction
import com.fluxa.app.shared.feature.discover.DiscoverScreen
import com.fluxa.app.shared.feature.discover.DiscoverUiState
import com.fluxa.app.shared.feature.search.SearchAction
import com.fluxa.app.shared.feature.search.SearchScreen
import com.fluxa.app.shared.feature.search.SearchUiState
@ -58,6 +61,8 @@ fun FluxaApp(
onDetailAction: (DetailAction) -> Unit = {},
searchState: SearchUiState? = null,
onSearchAction: (SearchAction) -> Unit = {},
discoverState: DiscoverUiState? = null,
onDiscoverAction: (DiscoverAction) -> Unit = {},
modifier: Modifier = Modifier
) {
MaterialTheme {
@ -85,6 +90,13 @@ fun FluxaApp(
onItemSelected = { item -> onSearchAction(SearchAction.ItemSelected(item)) },
modifier = Modifier.weight(1f)
)
state.destination == FluxaDestination.Discover && discoverState != null -> DiscoverScreen(
state = discoverState,
language = state.language,
onFiltersChanged = { filters -> onDiscoverAction(DiscoverAction.FiltersChanged(filters)) },
onItemSelected = { item -> onDiscoverAction(DiscoverAction.ItemSelected(item)) },
modifier = Modifier.weight(1f)
)
state.destination == FluxaDestination.Home -> FluxaHomeContent(
state = state,
onCatalogAction = onCatalogAction,

View file

@ -13,10 +13,14 @@ import com.fluxa.app.shared.feature.catalog.CatalogHomeStore
import com.fluxa.app.shared.feature.detail.DetailAction
import com.fluxa.app.shared.feature.detail.DetailDataSource
import com.fluxa.app.shared.feature.detail.DetailStore
import com.fluxa.app.shared.feature.discover.DiscoverAction
import com.fluxa.app.shared.feature.discover.DiscoverDataSource
import com.fluxa.app.shared.feature.discover.DiscoverStore
import com.fluxa.app.shared.feature.search.SearchAction
import com.fluxa.app.shared.feature.search.SearchDataSource
import com.fluxa.app.shared.feature.search.SearchStore
import com.fluxa.app.shared.platform.FluxaDetailServices
import com.fluxa.app.shared.platform.FluxaDiscoverServices
import com.fluxa.app.shared.platform.FluxaPlatformServices
import com.fluxa.app.shared.platform.FluxaSearchServices
import kotlinx.coroutines.launch
@ -31,6 +35,7 @@ fun FluxaAppHost(
FluxaAppHost(
catalogHomeDataSource = platformServices.catalogHomeDataSource,
detailDataSource = (platformServices as? FluxaDetailServices)?.detailDataSource,
discoverDataSource = (platformServices as? FluxaDiscoverServices)?.discoverDataSource,
searchDataSource = (platformServices as? FluxaSearchServices)?.searchDataSource,
language = language,
onCatalogAction = onCatalogAction,
@ -42,6 +47,7 @@ fun FluxaAppHost(
fun FluxaAppHost(
catalogHomeDataSource: CatalogHomeDataSource,
detailDataSource: DetailDataSource? = null,
discoverDataSource: DiscoverDataSource? = null,
searchDataSource: SearchDataSource? = null,
language: String? = null,
onCatalogAction: (CatalogAction) -> Unit = {},
@ -56,6 +62,10 @@ fun FluxaAppHost(
remember(source) { SearchStore(source, scope) }
}
val searchState = searchStore?.state?.collectAsState()?.value
val discoverStore = discoverDataSource?.let { source ->
remember(source) { DiscoverStore(source, scope) }
}
val discoverState = discoverStore?.state?.collectAsState()?.value
val appState = rememberFluxaAppState()
val selectedDetail = appState.uiState.selectedDetail
val detailStore = selectedDetail?.let { item ->
@ -112,6 +122,16 @@ fun FluxaAppHost(
searchStore?.dispatch(action)
}
},
discoverState = discoverState,
onDiscoverAction = { action ->
if (action is DiscoverAction.ItemSelected) {
appState.selectDetail(action.item)
onCatalogAction(CatalogAction.ItemSelected(action.item))
}
scope.launch {
discoverStore?.dispatch(action)
}
},
modifier = modifier
)
}

View file

@ -0,0 +1,137 @@
package com.fluxa.app.shared.feature.discover
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.CircularProgressIndicator
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.fluxa.app.common.AppStrings
import com.fluxa.app.shared.feature.catalog.CatalogItemUiModel
import com.fluxa.app.ui.catalog.CatalogCard
import com.fluxa.app.ui.catalog.FluxaColors
@Composable
fun DiscoverScreen(
state: DiscoverUiState,
language: String?,
onFiltersChanged: (DiscoverFiltersUiModel) -> Unit,
onItemSelected: (CatalogItemUiModel) -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxSize()
.background(FluxaColors.background)
.padding(horizontal = 20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = AppStrings.t(language, "nav.discover"),
color = Color.White,
fontWeight = FontWeight.Bold
)
DiscoverFilters(
filters = state.filters,
catalogOptions = state.catalogOptions,
genreOptions = state.genreOptions,
language = language,
onFiltersChanged = onFiltersChanged
)
when {
state.isLoading && state.results.isEmpty() -> Box(
modifier = Modifier.weight(1f).fillMaxWidth(),
contentAlignment = Alignment.Center
) { CircularProgressIndicator(color = Color.White) }
state.results.isEmpty() -> Box(
modifier = Modifier.weight(1f).fillMaxWidth(),
contentAlignment = Alignment.Center
) { Text(AppStrings.t(language, "auto.no_results_yet"), color = Color.White) }
else -> LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 128.dp),
modifier = Modifier.weight(1f).fillMaxWidth(),
contentPadding = PaddingValues(bottom = 20.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(state.results, key = { "${it.type}:${it.id}" }) { item ->
CatalogCard(model = item.card, onClick = { onItemSelected(item) })
}
}
}
}
}
@Composable
private fun DiscoverFilters(
filters: DiscoverFiltersUiModel,
catalogOptions: List<DiscoverFilterOptionUiModel>,
genreOptions: List<DiscoverFilterOptionUiModel>,
language: String?,
onFiltersChanged: (DiscoverFiltersUiModel) -> Unit
) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
DiscoverFilterRow(
options = listOf(
DiscoverFilterOptionUiModel("movie", AppStrings.t(language, "auto.movie")),
DiscoverFilterOptionUiModel("series", AppStrings.t(language, "auto.series"))
),
selectedId = filters.contentType,
onSelected = { value ->
onFiltersChanged(DiscoverFiltersUiModel(contentType = value.orEmpty()))
}
)
if (catalogOptions.isNotEmpty()) {
DiscoverFilterRow(
options = catalogOptions,
selectedId = filters.catalogKey,
onSelected = { value ->
onFiltersChanged(filters.copy(catalogKey = value, genre = null))
}
)
}
if (genreOptions.isNotEmpty()) {
DiscoverFilterRow(
options = genreOptions,
selectedId = filters.genre,
onSelected = { value ->
onFiltersChanged(filters.copy(genre = value))
}
)
}
}
}
@Composable
private fun DiscoverFilterRow(
options: List<DiscoverFilterOptionUiModel>,
selectedId: String?,
onSelected: (String?) -> Unit
) {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
options.forEach { option ->
val selected = option.id == selectedId
Text(
text = option.label,
color = if (selected) Color.White else Color.White.copy(alpha = 0.6f),
fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium,
modifier = Modifier.clickable { onSelected(option.id) }
)
}
}
}