mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
implement latest library content loading and refactor Home screen state
- Implement `loadLatestLibraryContent` in `InMemoryMediaRepository` to fetch and categorize latest media from Jellyfin libraries. - Update `HomePageViewModel` to use `mapLatest` and `stateIn` for asynchronous, thread-safe loading of "Continue Watching" and "Latest" content. - Refactor `HomePage` and its UI components (`HomeContent`, `HomeDrawer`, `HomeSections`) to pass data and callbacks as parameters instead of using `hiltViewModel()` internally. - Enhance `PosterCard` and `ContinueWatchingCard` with explicit click listeners and image request optimization using `Coil`. - Add `SeasonMedia` type to the `Media` sealed class to support more granular library item tracking. - Standardize `UUID` usage for media selection callbacks across `LibraryViewModel` and common UI components. - Improve UI styling by replacing shadows with subtle borders and consistent corner radii on media cards.
This commit is contained in:
@@ -35,7 +35,8 @@ fun HomePage(
|
|||||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val libraries = viewModel.libraries.collectAsState().value.map {
|
val libraries = viewModel.libraries.collectAsState().value
|
||||||
|
val libraryNavItems = libraries.map {
|
||||||
HomeNavItem(
|
HomeNavItem(
|
||||||
id = it.id,
|
id = it.id,
|
||||||
label = it.name,
|
label = it.name,
|
||||||
@@ -47,6 +48,7 @@ fun HomePage(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val continueWatching = viewModel.continueWatching.collectAsState()
|
val continueWatching = viewModel.continueWatching.collectAsState()
|
||||||
|
val latestLibraryContent = viewModel.latestLibraryContent.collectAsState()
|
||||||
|
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
@@ -61,9 +63,11 @@ fun HomePage(
|
|||||||
HomeDrawerContent(
|
HomeDrawerContent(
|
||||||
title = "Jellyfin",
|
title = "Jellyfin",
|
||||||
subtitle = "Library Dashboard",
|
subtitle = "Library Dashboard",
|
||||||
primaryNavItems = libraries,
|
primaryNavItems = libraryNavItems,
|
||||||
secondaryNavItems = HomeMockData.secondaryNavItems,
|
secondaryNavItems = HomeMockData.secondaryNavItems,
|
||||||
user = HomeMockData.user,
|
user = HomeMockData.user,
|
||||||
|
onLibrarySelected = viewModel::onLibrarySelected,
|
||||||
|
onLogout = viewModel::logout
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +83,12 @@ fun HomePage(
|
|||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
HomeContent(
|
HomeContent(
|
||||||
|
libraries = libraries,
|
||||||
|
libraryContent = latestLibraryContent.value,
|
||||||
continueWatching = continueWatching.value,
|
continueWatching = continueWatching.value,
|
||||||
|
onMovieSelected = viewModel::onMovieSelected,
|
||||||
|
onSeriesSelected = viewModel::onSeriesSelected,
|
||||||
|
onEpisodeSelected = viewModel::onEpisodeSelected,
|
||||||
modifier = Modifier.padding(innerPadding)
|
modifier = Modifier.padding(innerPadding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ import hu.bbara.purefin.navigation.NavigationManager
|
|||||||
import hu.bbara.purefin.navigation.Route
|
import hu.bbara.purefin.navigation.Route
|
||||||
import hu.bbara.purefin.navigation.SeriesDto
|
import hu.bbara.purefin.navigation.SeriesDto
|
||||||
import hu.bbara.purefin.session.UserSessionRepository
|
import hu.bbara.purefin.session.UserSessionRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
@@ -49,42 +51,106 @@ class HomePageViewModel @Inject constructor(
|
|||||||
private val _libraries = MutableStateFlow<List<LibraryItem>>(emptyList())
|
private val _libraries = MutableStateFlow<List<LibraryItem>>(emptyList())
|
||||||
val libraries = _libraries.asStateFlow()
|
val libraries = _libraries.asStateFlow()
|
||||||
|
|
||||||
val continueWatching = mediaRepository.continueWatching.map { list ->
|
init {
|
||||||
list.map {
|
viewModelScope.launch {
|
||||||
when ( it ) {
|
loadLibraries()
|
||||||
is Media.MovieMedia -> {
|
}
|
||||||
val movie = mediaRepository.getMovie(it.movieId)
|
}
|
||||||
ContinueWatchingItem(
|
|
||||||
type = BaseItemKind.MOVIE,
|
val continueWatching = mediaRepository.continueWatching
|
||||||
movie = movie
|
.mapLatest { list ->
|
||||||
)
|
withContext(Dispatchers.IO) {
|
||||||
}
|
list.map { media ->
|
||||||
is Media.EpisodeMedia -> {
|
when (media) {
|
||||||
val episode = mediaRepository.getEpisode(
|
is Media.MovieMedia -> {
|
||||||
seriesId = it.seriesId,
|
val movie = mediaRepository.getMovie(media.movieId)
|
||||||
episodeId = it.episodeId
|
ContinueWatchingItem(
|
||||||
)
|
type = BaseItemKind.MOVIE,
|
||||||
ContinueWatchingItem(
|
movie = movie
|
||||||
type = BaseItemKind.EPISODE,
|
)
|
||||||
episode = episode
|
}
|
||||||
)
|
|
||||||
}
|
is Media.EpisodeMedia -> {
|
||||||
else -> throw UnsupportedOperationException("Unsupported item type: $it")
|
val episode = mediaRepository.getEpisode(
|
||||||
|
seriesId = media.seriesId,
|
||||||
|
episodeId = media.episodeId
|
||||||
|
)
|
||||||
|
ContinueWatchingItem(
|
||||||
|
type = BaseItemKind.EPISODE,
|
||||||
|
episode = episode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw UnsupportedOperationException("Unsupported item type: $media")
|
||||||
|
}
|
||||||
|
}.distinctBy { it.id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
started = SharingStarted.WhileSubscribed(5_000),
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
initialValue = emptyList()
|
initialValue = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _latestLibraryContent = MutableStateFlow<Map<UUID, List<PosterItem>>>(emptyMap())
|
val latestLibraryContent = mediaRepository.latestLibraryContent
|
||||||
val latestLibraryContent = _latestLibraryContent.asStateFlow()
|
.mapLatest { libraryMap ->
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
libraryMap.mapValues { (_, items) ->
|
||||||
|
items.map { media ->
|
||||||
|
when (media) {
|
||||||
|
is Media.MovieMedia -> {
|
||||||
|
val movie = mediaRepository.getMovie(media.movieId)
|
||||||
|
PosterItem(
|
||||||
|
type = BaseItemKind.MOVIE,
|
||||||
|
movie = movie
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Media.EpisodeMedia -> {
|
||||||
|
val episode = mediaRepository.getEpisode(
|
||||||
|
seriesId = media.seriesId,
|
||||||
|
episodeId = media.episodeId
|
||||||
|
)
|
||||||
|
PosterItem(
|
||||||
|
type = BaseItemKind.EPISODE,
|
||||||
|
episode = episode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Media.SeriesMedia -> {
|
||||||
|
val series = mediaRepository.getSeries(media.id)
|
||||||
|
PosterItem(
|
||||||
|
type = BaseItemKind.SERIES,
|
||||||
|
series = series
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Media.SeasonMedia -> {
|
||||||
|
val series = mediaRepository.getSeries(media.seriesId)
|
||||||
|
PosterItem(
|
||||||
|
type = BaseItemKind.SERIES,
|
||||||
|
series = series
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw UnsupportedOperationException("Unsupported item type: $media")
|
||||||
|
}
|
||||||
|
}.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { mediaRepository.ensureReady() }
|
viewModelScope.launch { mediaRepository.ensureReady() }
|
||||||
loadHomePageData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLibrarySelected(library : HomeNavItem) {
|
fun onLibrarySelected(library : HomeNavItem) {
|
||||||
@@ -132,19 +198,7 @@ class HomePageViewModel @Inject constructor(
|
|||||||
navigationManager.replaceAll(Route.Home)
|
navigationManager.replaceAll(Route.Home)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadContinueWatching() {
|
private suspend fun loadLibraries() {
|
||||||
viewModelScope.launch {
|
|
||||||
// mediaRepository.loadContinueWatching()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadLibraries() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
// mediaRepository.loadLibraries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun loadLibrariesInternal() {
|
|
||||||
val libraries: List<BaseItemDto> = jellyfinApiClient.getLibraries()
|
val libraries: List<BaseItemDto> = jellyfinApiClient.getLibraries()
|
||||||
val mappedLibraries = libraries.map {
|
val mappedLibraries = libraries.map {
|
||||||
LibraryItem(
|
LibraryItem(
|
||||||
@@ -157,72 +211,6 @@ class HomePageViewModel @Inject constructor(
|
|||||||
_libraries.value = mappedLibraries
|
_libraries.value = mappedLibraries
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAllShownLibraryItems() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
if (_libraries.value.isEmpty()) {
|
|
||||||
loadLibrariesInternal()
|
|
||||||
}
|
|
||||||
_libraries.value.forEach { library ->
|
|
||||||
loadLatestLibraryItems(library.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadLatestLibraryItems(libraryId: UUID) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
val latestLibraryItems = jellyfinApiClient.getLatestFromLibrary(libraryId)
|
|
||||||
val latestLibraryPosterItem = latestLibraryItems.map {
|
|
||||||
when (it.type) {
|
|
||||||
BaseItemKind.MOVIE -> {
|
|
||||||
val movie = mediaRepository.getMovie(it.id)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.MOVIE,
|
|
||||||
movie = movie
|
|
||||||
)
|
|
||||||
}
|
|
||||||
BaseItemKind.EPISODE -> {
|
|
||||||
val episode = mediaRepository.getEpisode(
|
|
||||||
it.seriesId!!,
|
|
||||||
it.parentId!!,
|
|
||||||
it.id
|
|
||||||
)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.EPISODE,
|
|
||||||
episode = episode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
BaseItemKind.SEASON -> {
|
|
||||||
val series = mediaRepository.getSeries(
|
|
||||||
seriesId = it.seriesId!!
|
|
||||||
)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.SERIES,
|
|
||||||
series = series
|
|
||||||
)
|
|
||||||
}
|
|
||||||
BaseItemKind.SERIES -> {
|
|
||||||
val series = mediaRepository.getSeries(
|
|
||||||
seriesId = it.id
|
|
||||||
)
|
|
||||||
PosterItem(
|
|
||||||
type = BaseItemKind.SERIES,
|
|
||||||
series = series
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> throw UnsupportedOperationException("Unsupported item type: ${it.type}")
|
|
||||||
}
|
|
||||||
}.distinctBy { it.id }
|
|
||||||
_latestLibraryContent.update { currentMap ->
|
|
||||||
currentMap + (libraryId to latestLibraryPosterItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadHomePageData() {
|
|
||||||
loadContinueWatching()
|
|
||||||
loadAllShownLibraryItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getImageUrl(itemId: UUID, type: ImageType): String {
|
fun getImageUrl(itemId: UUID, type: ImageType): String {
|
||||||
return JellyfinImageHelper.toImageUrl(
|
return JellyfinImageHelper.toImageUrl(
|
||||||
url = _url.value,
|
url = _url.value,
|
||||||
|
|||||||
@@ -8,23 +8,20 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import org.jellyfin.sdk.model.UUID
|
||||||
import hu.bbara.purefin.app.home.HomePageViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeContent(
|
fun HomeContent(
|
||||||
viewModel: HomePageViewModel = hiltViewModel(),
|
libraries: List<LibraryItem>,
|
||||||
|
libraryContent: Map<UUID, List<PosterItem>>,
|
||||||
continueWatching: List<ContinueWatchingItem>,
|
continueWatching: List<ContinueWatchingItem>,
|
||||||
|
onMovieSelected: (UUID) -> Unit,
|
||||||
|
onSeriesSelected: (UUID) -> Unit,
|
||||||
|
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val libraries by viewModel.libraries.collectAsState()
|
|
||||||
val libraryContent by viewModel.latestLibraryContent.collectAsState()
|
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -35,7 +32,9 @@ fun HomeContent(
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ContinueWatchingSection(
|
ContinueWatchingSection(
|
||||||
items = continueWatching
|
items = continueWatching,
|
||||||
|
onMovieSelected = onMovieSelected,
|
||||||
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(
|
items(
|
||||||
@@ -46,6 +45,9 @@ fun HomeContent(
|
|||||||
title = item.name,
|
title = item.name,
|
||||||
items = libraryContent[item.id] ?: emptyList(),
|
items = libraryContent[item.id] ?: emptyList(),
|
||||||
action = "See All",
|
action = "See All",
|
||||||
|
onMovieSelected = onMovieSelected,
|
||||||
|
onSeriesSelected = onSeriesSelected,
|
||||||
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import hu.bbara.purefin.app.home.HomePageViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeDrawerContent(
|
fun HomeDrawerContent(
|
||||||
@@ -36,6 +34,8 @@ fun HomeDrawerContent(
|
|||||||
primaryNavItems: List<HomeNavItem>,
|
primaryNavItems: List<HomeNavItem>,
|
||||||
secondaryNavItems: List<HomeNavItem>,
|
secondaryNavItems: List<HomeNavItem>,
|
||||||
user: HomeUser,
|
user: HomeUser,
|
||||||
|
onLibrarySelected: (HomeNavItem) -> Unit,
|
||||||
|
onLogout: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier.fillMaxSize()) {
|
Column(modifier = modifier.fillMaxSize()) {
|
||||||
@@ -45,10 +45,11 @@ fun HomeDrawerContent(
|
|||||||
)
|
)
|
||||||
HomeDrawerNav(
|
HomeDrawerNav(
|
||||||
primaryItems = primaryNavItems,
|
primaryItems = primaryNavItems,
|
||||||
secondaryItems = secondaryNavItems
|
secondaryItems = secondaryNavItems,
|
||||||
|
onLibrarySelected = onLibrarySelected
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
HomeDrawerFooter(user = user)
|
HomeDrawerFooter(user = user, onLogout = onLogout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ fun HomeDrawerHeader(
|
|||||||
fun HomeDrawerNav(
|
fun HomeDrawerNav(
|
||||||
primaryItems: List<HomeNavItem>,
|
primaryItems: List<HomeNavItem>,
|
||||||
secondaryItems: List<HomeNavItem>,
|
secondaryItems: List<HomeNavItem>,
|
||||||
|
onLibrarySelected: (HomeNavItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -108,7 +110,7 @@ fun HomeDrawerNav(
|
|||||||
.padding(vertical = 16.dp)
|
.padding(vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
primaryItems.forEach { item ->
|
primaryItems.forEach { item ->
|
||||||
HomeDrawerNavItem(item = item)
|
HomeDrawerNavItem(item = item, onLibrarySelected = onLibrarySelected)
|
||||||
}
|
}
|
||||||
if (secondaryItems.isNotEmpty()) {
|
if (secondaryItems.isNotEmpty()) {
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
@@ -117,7 +119,7 @@ fun HomeDrawerNav(
|
|||||||
color = MaterialTheme.colorScheme.outlineVariant
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
)
|
)
|
||||||
secondaryItems.forEach { item ->
|
secondaryItems.forEach { item ->
|
||||||
HomeDrawerNavItem(item = item)
|
HomeDrawerNavItem(item = item, onLibrarySelected = onLibrarySelected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +129,7 @@ fun HomeDrawerNav(
|
|||||||
fun HomeDrawerNavItem(
|
fun HomeDrawerNavItem(
|
||||||
item: HomeNavItem,
|
item: HomeNavItem,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: HomePageViewModel = hiltViewModel(),
|
onLibrarySelected: (HomeNavItem) -> Unit
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
val background = if (item.selected) scheme.primary.copy(alpha = 0.12f) else Color.Transparent
|
val background = if (item.selected) scheme.primary.copy(alpha = 0.12f) else Color.Transparent
|
||||||
@@ -137,7 +139,7 @@ fun HomeDrawerNavItem(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp)
|
.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||||
.background(background, RoundedCornerShape(12.dp))
|
.background(background, RoundedCornerShape(12.dp))
|
||||||
.clickable { viewModel.onLibrarySelected(item) }
|
.clickable { onLibrarySelected(item) }
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -158,8 +160,8 @@ fun HomeDrawerNavItem(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeDrawerFooter (
|
fun HomeDrawerFooter (
|
||||||
viewModel: HomePageViewModel = hiltViewModel(),
|
|
||||||
user: HomeUser,
|
user: HomeUser,
|
||||||
|
onLogout: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
@@ -181,7 +183,7 @@ fun HomeDrawerFooter (
|
|||||||
iconTint = scheme.onBackground
|
iconTint = scheme.onBackground
|
||||||
)
|
)
|
||||||
Column(modifier = Modifier.padding(start = 12.dp)
|
Column(modifier = Modifier.padding(start = 12.dp)
|
||||||
.clickable {viewModel.logout()}) {
|
.clickable { onLogout() }) {
|
||||||
Text(
|
Text(
|
||||||
text = user.name,
|
text = user.name,
|
||||||
color = scheme.onBackground,
|
color = scheme.onBackground,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package hu.bbara.purefin.app.home.ui
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -32,25 +33,26 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import coil3.request.ImageRequest
|
||||||
import hu.bbara.purefin.app.home.HomePageViewModel
|
|
||||||
import hu.bbara.purefin.common.ui.PosterCard
|
import hu.bbara.purefin.common.ui.PosterCard
|
||||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||||
import hu.bbara.purefin.player.PlayerActivity
|
import hu.bbara.purefin.player.PlayerActivity
|
||||||
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
|
||||||
import kotlin.math.nextUp
|
import kotlin.math.nextUp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ContinueWatchingSection(
|
fun ContinueWatchingSection(
|
||||||
items: List<ContinueWatchingItem>,
|
items: List<ContinueWatchingItem>,
|
||||||
|
onMovieSelected: (UUID) -> Unit,
|
||||||
|
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
@@ -65,7 +67,9 @@ fun ContinueWatchingSection(
|
|||||||
items(
|
items(
|
||||||
items = items, key = { it.id }) { item ->
|
items = items, key = { it.id }) { item ->
|
||||||
ContinueWatchingCard(
|
ContinueWatchingCard(
|
||||||
item = item
|
item = item,
|
||||||
|
onMovieSelected = onMovieSelected,
|
||||||
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,42 +79,55 @@ fun ContinueWatchingSection(
|
|||||||
fun ContinueWatchingCard(
|
fun ContinueWatchingCard(
|
||||||
item: ContinueWatchingItem,
|
item: ContinueWatchingItem,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: HomePageViewModel = hiltViewModel()
|
onMovieSelected: (UUID) -> Unit,
|
||||||
|
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
val imageUrl = when (item.type) {
|
||||||
|
BaseItemKind.MOVIE -> item.movie?.heroImageUrl
|
||||||
|
BaseItemKind.EPISODE -> item.episode?.heroImageUrl
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
val cardWidth = 280.dp
|
||||||
|
val cardHeight = cardWidth * 9 / 16
|
||||||
|
|
||||||
fun openItem(item: ContinueWatchingItem) {
|
fun openItem(item: ContinueWatchingItem) {
|
||||||
when (item.type) {
|
when (item.type) {
|
||||||
BaseItemKind.MOVIE -> viewModel.onMovieSelected(item.movie!!.id)
|
BaseItemKind.MOVIE -> onMovieSelected(item.movie!!.id)
|
||||||
BaseItemKind.EPISODE -> {
|
BaseItemKind.EPISODE -> {
|
||||||
val episode = item.episode!!
|
val episode = item.episode!!
|
||||||
viewModel.onEpisodeSelected(
|
onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id)
|
||||||
seriesId = episode.seriesId,
|
|
||||||
seasonId = episode.seasonId,
|
|
||||||
episodeId = episode.id
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
|
.data(imageUrl)
|
||||||
|
.size(with(density) { cardWidth.roundToPx() }, with(density) { cardHeight.roundToPx() })
|
||||||
|
.build()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(280.dp)
|
.width(cardWidth)
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.width(cardWidth)
|
||||||
.aspectRatio(16f / 9f)
|
.aspectRatio(16f / 9f)
|
||||||
.shadow(12.dp, RoundedCornerShape(16.dp))
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.border(1.dp, scheme.outlineVariant.copy(alpha = 0.3f), RoundedCornerShape(16.dp))
|
||||||
.background(scheme.surfaceVariant)
|
.background(scheme.surfaceVariant)
|
||||||
) {
|
) {
|
||||||
PurefinAsyncImage(
|
PurefinAsyncImage(
|
||||||
model = viewModel.getImageUrl(itemId = item.id, type = ImageType.PRIMARY),
|
model = imageRequest,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -187,7 +204,9 @@ fun LibraryPosterSection(
|
|||||||
items: List<PosterItem>,
|
items: List<PosterItem>,
|
||||||
action: String?,
|
action: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: HomePageViewModel = hiltViewModel()
|
onMovieSelected: (UUID) -> Unit,
|
||||||
|
onSeriesSelected: (UUID) -> Unit,
|
||||||
|
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
||||||
) {
|
) {
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
title = title,
|
title = title,
|
||||||
@@ -202,15 +221,9 @@ fun LibraryPosterSection(
|
|||||||
items = items, key = { it.id }) { item ->
|
items = items, key = { it.id }) { item ->
|
||||||
PosterCard(
|
PosterCard(
|
||||||
item = item,
|
item = item,
|
||||||
onMovieSelected = { viewModel.onMovieSelected(item.movie!!.id) },
|
onMovieSelected = onMovieSelected,
|
||||||
onSeriesSelected = { viewModel.onSeriesSelected(item.series!!.id) },
|
onSeriesSelected = onSeriesSelected,
|
||||||
onEpisodeSelected = {
|
onEpisodeSelected = onEpisodeSelected
|
||||||
viewModel.onEpisodeSelected(
|
|
||||||
seriesId = item.episode!!.seriesId,
|
|
||||||
seasonId = item.episode.seasonId,
|
|
||||||
episodeId = item.episode.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,19 +42,19 @@ class LibraryViewModel @Inject constructor(
|
|||||||
viewModelScope.launch { mediaRepository.ensureReady() }
|
viewModelScope.launch { mediaRepository.ensureReady() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMovieSelected(movieId: String) {
|
fun onMovieSelected(movieId: UUID) {
|
||||||
navigationManager.navigate(Route.MovieRoute(
|
navigationManager.navigate(Route.MovieRoute(
|
||||||
MovieDto(
|
MovieDto(
|
||||||
id = UUID.fromString(movieId),
|
id = movieId,
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSeriesSelected(seriesId: String) {
|
fun onSeriesSelected(seriesId: UUID) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
navigationManager.navigate(Route.SeriesRoute(
|
navigationManager.navigate(Route.SeriesRoute(
|
||||||
SeriesDto(
|
SeriesDto(
|
||||||
id = UUID.fromString(seriesId),
|
id = seriesId,
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,9 @@ internal fun LibraryPosterGrid(
|
|||||||
items(libraryItems) { item ->
|
items(libraryItems) { item ->
|
||||||
PosterCard(
|
PosterCard(
|
||||||
item = item,
|
item = item,
|
||||||
onMovieSelected = { viewModel.onMovieSelected(item.id.toString()) },
|
onMovieSelected = viewModel::onMovieSelected,
|
||||||
onSeriesSelected = { viewModel.onSeriesSelected(item.id.toString()) },
|
onSeriesSelected = viewModel::onSeriesSelected,
|
||||||
onEpisodeSelected = {
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package hu.bbara.purefin.common.ui
|
package hu.bbara.purefin.common.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
@@ -12,45 +13,62 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import coil3.request.ImageRequest
|
||||||
import hu.bbara.purefin.app.home.ui.PosterItem
|
import hu.bbara.purefin.app.home.ui.PosterItem
|
||||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||||
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PosterCard(
|
fun PosterCard(
|
||||||
item: PosterItem,
|
item: PosterItem,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onMovieSelected: (String) -> Unit,
|
onMovieSelected: (UUID) -> Unit,
|
||||||
onSeriesSelected: (String) -> Unit,
|
onSeriesSelected: (UUID) -> Unit,
|
||||||
onEpisodeSelected: (String) -> Unit,
|
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
val context = LocalContext.current
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
val posterWidth = 144.dp
|
||||||
|
val posterHeight = posterWidth * 3 / 2
|
||||||
|
|
||||||
fun openItem(posterItem: PosterItem) {
|
fun openItem(posterItem: PosterItem) {
|
||||||
when (posterItem.type) {
|
when (posterItem.type) {
|
||||||
BaseItemKind.MOVIE -> onMovieSelected(posterItem.id.toString())
|
BaseItemKind.MOVIE -> onMovieSelected(posterItem.id)
|
||||||
BaseItemKind.SERIES -> onSeriesSelected(posterItem.id.toString())
|
BaseItemKind.SERIES -> onSeriesSelected(posterItem.id)
|
||||||
BaseItemKind.EPISODE -> onEpisodeSelected(posterItem.id.toString())
|
BaseItemKind.EPISODE -> onEpisodeSelected(
|
||||||
|
posterItem.episode!!.seriesId,
|
||||||
|
posterItem.episode.seasonId,
|
||||||
|
posterItem.episode.id
|
||||||
|
)
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
|
.data(item.imageUrl)
|
||||||
|
.size(with(density) { posterWidth.roundToPx() }, with(density) { posterHeight.roundToPx() })
|
||||||
|
.build()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(144.dp)
|
.width(posterWidth)
|
||||||
) {
|
) {
|
||||||
PurefinAsyncImage(
|
PurefinAsyncImage(
|
||||||
model = item.imageUrl,
|
model = imageRequest,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.aspectRatio(2f / 3f)
|
.aspectRatio(2f / 3f)
|
||||||
.shadow(10.dp, RoundedCornerShape(14.dp))
|
|
||||||
.clip(RoundedCornerShape(14.dp))
|
.clip(RoundedCornerShape(14.dp))
|
||||||
|
.border(1.dp, scheme.outlineVariant.copy(alpha = 0.3f), RoundedCornerShape(14.dp))
|
||||||
.background(scheme.surfaceVariant)
|
.background(scheme.surfaceVariant)
|
||||||
.clickable(onClick = { openItem(item) }),
|
.clickable(onClick = { openItem(item) }),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
@@ -40,16 +41,19 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
private val ready = CompletableDeferred<Unit>()
|
private val ready = CompletableDeferred<Unit>()
|
||||||
|
|
||||||
private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Loading)
|
private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Loading)
|
||||||
override val state: StateFlow<MediaRepositoryState> = _state
|
override val state: StateFlow<MediaRepositoryState> = _state.asStateFlow()
|
||||||
|
|
||||||
private val _movies : MutableStateFlow<Map<UUID, Movie>> = MutableStateFlow(emptyMap())
|
private val _movies : MutableStateFlow<Map<UUID, Movie>> = MutableStateFlow(emptyMap())
|
||||||
override val movies: StateFlow<Map<UUID, Movie>> = _movies
|
override val movies: StateFlow<Map<UUID, Movie>> = _movies.asStateFlow()
|
||||||
|
|
||||||
private val _series : MutableStateFlow<Map<UUID, Series>> = MutableStateFlow(emptyMap())
|
private val _series : MutableStateFlow<Map<UUID, Series>> = MutableStateFlow(emptyMap())
|
||||||
override val series: StateFlow<Map<UUID, Series>> = _series
|
override val series: StateFlow<Map<UUID, Series>> = _series.asStateFlow()
|
||||||
|
|
||||||
private val _continueWatching: MutableStateFlow<List<Media>> = MutableStateFlow(emptyList())
|
private val _continueWatching: MutableStateFlow<List<Media>> = MutableStateFlow(emptyList())
|
||||||
val continueWatching: StateFlow<List<Media>> = _continueWatching
|
val continueWatching: StateFlow<List<Media>> = _continueWatching.asStateFlow()
|
||||||
|
|
||||||
|
private val _latestLibraryContent: MutableStateFlow<Map<UUID, List<Media>>> = MutableStateFlow(emptyMap())
|
||||||
|
val latestLibraryContent: StateFlow<Map<UUID, List<Media>>> = _latestLibraryContent.asStateFlow()
|
||||||
|
|
||||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
@@ -65,6 +69,7 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
try {
|
try {
|
||||||
loadLibraries()
|
loadLibraries()
|
||||||
loadContinueWatching()
|
loadContinueWatching()
|
||||||
|
loadLatestLibraryContent()
|
||||||
_state.value = MediaRepositoryState.Ready
|
_state.value = MediaRepositoryState.Ready
|
||||||
ready.complete(Unit)
|
ready.complete(Unit)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
@@ -157,6 +162,46 @@ class InMemoryMediaRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun loadLatestLibraryContent() {
|
||||||
|
// TODO Make libraries accessible in a field or something that is not this ugly.
|
||||||
|
val librariesItem = jellyfinApiClient.getLibraries()
|
||||||
|
val filterLibraries =
|
||||||
|
librariesItem.filter { it.collectionType == CollectionType.MOVIES || it.collectionType == CollectionType.TVSHOWS }
|
||||||
|
val latestLibraryContents = filterLibraries.associate { library ->
|
||||||
|
val latestFromLibrary = jellyfinApiClient.getLatestFromLibrary(library.id)
|
||||||
|
library.id to when (library.collectionType) {
|
||||||
|
CollectionType.MOVIES -> {
|
||||||
|
latestFromLibrary.map {
|
||||||
|
val movie = it.toMovie(serverUrl(), library.id)
|
||||||
|
Media.MovieMedia(movieId = movie.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CollectionType.TVSHOWS -> {
|
||||||
|
latestFromLibrary.map {
|
||||||
|
when (it.type) {
|
||||||
|
BaseItemKind.SERIES -> {
|
||||||
|
val series = it.toSeries(serverUrl(), library.id)
|
||||||
|
Media.SeriesMedia(seriesId = series.id)
|
||||||
|
}
|
||||||
|
BaseItemKind.SEASON -> {
|
||||||
|
val season = it.toSeason(serverUrl())
|
||||||
|
Media.SeasonMedia(seasonId = season.id, seriesId = season.seriesId)
|
||||||
|
}
|
||||||
|
BaseItemKind.EPISODE -> {
|
||||||
|
val episode = it.toEpisode(serverUrl())
|
||||||
|
Media.EpisodeMedia(episodeId = episode.id, seriesId = episode.seriesId)
|
||||||
|
} else -> throw UnsupportedOperationException("Unsupported item type: ${it.type}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw UnsupportedOperationException("Unsupported library type: ${library.collectionType}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_latestLibraryContent.value = latestLibraryContents
|
||||||
|
|
||||||
|
//TODO Load seasons and episodes, other types are already loaded at this point.
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getMovie(movieId: UUID): Movie {
|
override suspend fun getMovie(movieId: UUID): Movie {
|
||||||
awaitReady()
|
awaitReady()
|
||||||
localDataSource.getMovie(movieId)?.let {
|
localDataSource.getMovie(movieId)?.let {
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ sealed class Media(
|
|||||||
) {
|
) {
|
||||||
class MovieMedia(val movieId: UUID) : Media(movieId, BaseItemKind.MOVIE)
|
class MovieMedia(val movieId: UUID) : Media(movieId, BaseItemKind.MOVIE)
|
||||||
class SeriesMedia(val seriesId: UUID) : Media(seriesId, BaseItemKind.SERIES)
|
class SeriesMedia(val seriesId: UUID) : Media(seriesId, BaseItemKind.SERIES)
|
||||||
|
class SeasonMedia(val seasonId: UUID, val seriesId: UUID) : Media(seasonId, BaseItemKind.SEASON)
|
||||||
class EpisodeMedia(val episodeId: UUID, val seriesId: UUID) : Media(episodeId, BaseItemKind.EPISODE)
|
class EpisodeMedia(val episodeId: UUID, val seriesId: UUID) : Media(episodeId, BaseItemKind.EPISODE)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user