feat(search): wire full-screen search with genre browsing

This commit is contained in:
2026-05-02 15:15:20 +02:00
parent c6fe84654f
commit 672d8e1d64
9 changed files with 301 additions and 234 deletions

View File

@@ -3,6 +3,7 @@ package hu.bbara.purefin.data
import hu.bbara.purefin.Offline
import hu.bbara.purefin.Online
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Genre
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series
import kotlinx.coroutines.CoroutineScope
@@ -10,7 +11,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -33,15 +34,19 @@ class CompositeMediaRepository @Inject constructor(
override val movies: StateFlow<Map<UUID, Movie>> = activeRepository
.flatMapLatest { it.movies }
.stateIn(scope, SharingStarted.Companion.Eagerly, emptyMap())
.stateIn(scope, Eagerly, emptyMap())
override val series: StateFlow<Map<UUID, Series>> = activeRepository
.flatMapLatest { it.series }
.stateIn(scope, SharingStarted.Companion.Eagerly, emptyMap())
.stateIn(scope, Eagerly, emptyMap())
override val episodes: StateFlow<Map<UUID, Episode>> = activeRepository
.flatMapLatest { it.episodes }
.stateIn(scope, SharingStarted.Companion.Eagerly, emptyMap())
.stateIn(scope, Eagerly, emptyMap())
override val genres: StateFlow<Set<Genre>> = activeRepository
.flatMapLatest { it.genres }
.stateIn(scope, Eagerly, emptySet())
override suspend fun getMovie(id: UUID): Flow<Movie?> {
return activeRepository

View File

@@ -1,6 +1,7 @@
package hu.bbara.purefin.data
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Genre
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series
import kotlinx.coroutines.flow.Flow
@@ -12,6 +13,7 @@ interface MediaCatalogReader {
val movies: StateFlow<Map<UUID, Movie>>
val series: StateFlow<Map<UUID, Series>>
val episodes: StateFlow<Map<UUID, Episode>>
val genres: StateFlow<Set<Genre>>
suspend fun getMovie(id: UUID): Flow<Movie?>
suspend fun getSeries(id: UUID): Flow<Series?>
suspend fun getEpisode(id: UUID): Flow<Episode?>

View File

@@ -5,8 +5,13 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.data.MediaCatalogReader
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.image.ArtworkKind
import hu.bbara.purefin.image.ImageUrlBuilder
import java.util.UUID
import hu.bbara.purefin.model.MediaKind
import hu.bbara.purefin.navigation.MovieDto
import hu.bbara.purefin.navigation.NavigationManager
import hu.bbara.purefin.navigation.Route
import hu.bbara.purefin.navigation.SeriesDto
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -15,7 +20,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import hu.bbara.purefin.image.ArtworkKind
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
@@ -23,8 +28,11 @@ import javax.inject.Inject
class SearchViewModel @Inject constructor(
private val mediaCatalogReader: MediaCatalogReader,
private val userSessionRepository: UserSessionRepository,
private val navigationManager: NavigationManager,
) : ViewModel() {
val genres = mediaCatalogReader.genres
private val _searchResult = MutableStateFlow<List<SearchResult>>(emptyList())
val searchResult = _searchResult.asStateFlow()
@@ -54,6 +62,34 @@ class SearchViewModel @Inject constructor(
this.query.value = query
}
fun onSearchResultSelected(searchResult: SearchResult) {
when (searchResult.type) {
MediaKind.MOVIE -> onMovieSelected(searchResult.id)
MediaKind.SERIES -> onSeriesSelected(searchResult.id)
else -> Unit
}
}
private fun onMovieSelected(movieId: UUID) {
navigationManager.navigate(
Route.MovieRoute(
MovieDto(
id = movieId,
)
)
)
}
private fun onSeriesSelected(seriesId: UUID) {
navigationManager.navigate(
Route.SeriesRoute(
SeriesDto(
id = seriesId,
)
)
)
}
private suspend fun createImageUrl(id: UUID) : String {
return ImageUrlBuilder.toImageUrl(userSessionRepository.serverUrl.first(), id,
ArtworkKind.PRIMARY)