Compare commits

..

11 Commits

21 changed files with 581 additions and 251 deletions

70
AGENTS.md Normal file
View File

@@ -0,0 +1,70 @@
# Purefin AGENTS.md
Follow these instructions before making changes:
* Understand the relevant files before editing.
* Always follow official Android recommendations and best practices.
* Always include the pages you have read in your reply, even in planning mode.
* Prefer small, targeted changes over broad refactors.
* Preserve existing architecture and naming unless the task requires a change.
* Do not introduce new dependencies without a clear reason.
* Ask for clarification or leave a note when requirements are ambiguous.
---
## Repository Expectations
* Always use the currently used syntax in the codebase. Include examples for any naming you introduce.
* Avoid touching unrelated files.
* Do not fix unrelated existing problems in the codebase.
* Prefer consistency with the existing codebase over idealized patterns.
* When modifying a file, match its local style and conventions.
* Favor readable, maintainable code over clever code.
---
## Testing
* Running a build is sufficient to verify your implementation.
* Do not write tests unless explicitly asked to.
---
## Git and Commit Rules
When asked to prepare a commit:
* Use **Conventional Commits** format.
* Format:
```
<type>(<scope>): <description>
```
* The description should not be too long, but must include any major change.
* Use imperative mood.
* Keep the subject concise.
**Common types:**
* `feat` new feature
* `fix` bug fix
* `refactor` code change without feature or fix
* `test` adding or updating tests
* `docs` documentation changes
* `chore` maintenance tasks
* `build` build system changes
* `ci` CI/CD changes
---
## Preferred Workflow
1. Read the task carefully.
2. Read relevant official Google/Android documentation.
3. Inspect the relevant files and nearby code.
4. Choose the simplest solution aligned with Googles standards.
5. Check if the code can be simplified:
* If yes, simplify it.
* Do not reduce readability.
6. Summarize the result, including anything not verified.

View File

@@ -22,7 +22,7 @@ import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.ui.model.EpisodeUiModel import hu.bbara.purefin.ui.model.EpisodeUiModel
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.model.MovieUiModel import hu.bbara.purefin.ui.model.MovieUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.screen.home.TvHomeScreen import hu.bbara.purefin.ui.screen.home.TvHomeScreen
import hu.bbara.purefin.ui.theme.AppTheme import hu.bbara.purefin.ui.theme.AppTheme
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@@ -385,9 +385,9 @@ class TvHomeContentTest {
) )
} }
private fun sampleLibraries(): List<LibraryItem> { private fun sampleLibraries(): List<LibraryUiModel> {
return listOf( return listOf(
LibraryItem( LibraryUiModel(
id = sampleLibraryId(), id = sampleLibraryId(),
name = "Movies", name = "Movies",
type = LibraryKind.MOVIES, type = LibraryKind.MOVIES,

View File

@@ -5,7 +5,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import hu.bbara.purefin.model.LibraryKind import hu.bbara.purefin.model.LibraryKind
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.theme.AppTheme import hu.bbara.purefin.ui.theme.AppTheme
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
@@ -20,14 +20,14 @@ class TvLibrariesOverviewScreenTest {
@Test @Test
fun tvLibrariesOverviewScreen_opensSelectedLibrary() { fun tvLibrariesOverviewScreen_opensSelectedLibrary() {
val libraries = listOf( val libraries = listOf(
LibraryItem( LibraryUiModel(
id = UUID.fromString("11111111-1111-1111-1111-111111111111"), id = UUID.fromString("11111111-1111-1111-1111-111111111111"),
name = "Movies", name = "Movies",
type = LibraryKind.MOVIES, type = LibraryKind.MOVIES,
posterUrl = "", posterUrl = "",
isEmpty = false isEmpty = false
), ),
LibraryItem( LibraryUiModel(
id = UUID.fromString("22222222-2222-2222-2222-222222222222"), id = UUID.fromString("22222222-2222-2222-2222-222222222222"),
name = "Shows", name = "Shows",
type = LibraryKind.SERIES, type = LibraryKind.SERIES,
@@ -35,7 +35,7 @@ class TvLibrariesOverviewScreenTest {
isEmpty = false isEmpty = false
) )
) )
var openedLibrary: LibraryItem? = null var openedLibrary: LibraryUiModel? = null
composeRule.setContent { composeRule.setContent {
AppTheme { AppTheme {

View File

@@ -13,14 +13,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.model.MovieUiModel import hu.bbara.purefin.ui.model.MovieUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.screen.home.components.TvFocusedItemHero import hu.bbara.purefin.ui.screen.home.components.TvFocusedItemHero
import hu.bbara.purefin.ui.screen.home.components.TvHomeContent import hu.bbara.purefin.ui.screen.home.components.TvHomeContent
import java.util.UUID import java.util.UUID
@Composable @Composable
fun TvHomeScreen( fun TvHomeScreen(
libraries: List<LibraryItem>, libraries: List<LibraryUiModel>,
libraryContent: Map<UUID, List<MediaUiModel>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<MediaUiModel>, continueWatching: List<MediaUiModel>,
nextUp: List<MediaUiModel>, nextUp: List<MediaUiModel>,

View File

@@ -24,7 +24,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import java.util.UUID import java.util.UUID
internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item" internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item"
@@ -32,7 +32,7 @@ internal const val TvHomeContentViewportTag = "tv-home-content-viewport"
@Composable @Composable
fun TvHomeContent( fun TvHomeContent(
libraries: List<LibraryItem>, libraries: List<LibraryUiModel>,
libraryContent: Map<UUID, List<MediaUiModel>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<MediaUiModel>, continueWatching: List<MediaUiModel>,
nextUp: List<MediaUiModel>, nextUp: List<MediaUiModel>,

View File

@@ -1,11 +1,9 @@
package hu.bbara.purefin.ui.screen.home.components package hu.bbara.purefin.ui.screen.home.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -13,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -38,9 +35,6 @@ import androidx.tv.material3.MaterialTheme as TvMaterialTheme
internal const val TvDrawerItemTagPrefix = "tv-drawer-item-" internal const val TvDrawerItemTagPrefix = "tv-drawer-item-"
internal const val TvDrawerTitleTag = "tv-drawer-title" internal const val TvDrawerTitleTag = "tv-drawer-title"
private val TvDrawerCollapsedWidth = 92.dp
private val TvDrawerExpandedWidth = 280.dp
@Composable @Composable
fun TvNavigationDrawer( fun TvNavigationDrawer(
destinations: List<TvDrawerDestinationItem>, destinations: List<TvDrawerDestinationItem>,
@@ -52,9 +46,9 @@ fun TvNavigationDrawer(
ProvideTvDrawerTheme { ProvideTvDrawerTheme {
NavigationDrawer( NavigationDrawer(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
drawerContent = { drawerContent = { drawerValue ->
TvNavigationDrawerRail( TvNavigationDrawerRail(
drawerValue = if (hasFocus) DrawerValue.Open else DrawerValue.Closed, drawerValue = drawerValue,
destinations = destinations, destinations = destinations,
selectedDestination = selectedDestination, selectedDestination = selectedDestination,
onDestinationSelected = onDestinationSelected onDestinationSelected = onDestinationSelected
@@ -137,22 +131,13 @@ private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail(
onDestinationSelected: (Route) -> Unit, onDestinationSelected: (Route) -> Unit,
) { ) {
val expanded = drawerValue == DrawerValue.Open val expanded = drawerValue == DrawerValue.Open
val drawerWidth = animateDpAsState(
targetValue = if (expanded) TvDrawerExpandedWidth else TvDrawerCollapsedWidth,
label = "tv-drawer-width"
)
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
Box(
modifier = Modifier
.width(drawerWidth.value)
.fillMaxHeight()
.background(scheme.surface.copy(alpha = 0.96f))
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxHeight()
.padding(horizontal = 16.dp, vertical = 24.dp) .background(scheme.surface.copy(alpha = 0.96f))
.padding(horizontal = 12.dp, vertical = 24.dp)
.selectableGroup(), .selectableGroup(),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
@@ -175,7 +160,6 @@ private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail(
) )
} }
) { ) {
if (expanded) {
Text( Text(
text = destination.label, text = destination.label,
maxLines = 1, maxLines = 1,
@@ -183,8 +167,6 @@ private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail(
) )
} }
} }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
} }
}
} }

View File

@@ -43,7 +43,7 @@ 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 hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.model.LibraryKind import hu.bbara.purefin.model.LibraryKind
internal const val TvLibrariesOverviewItemTagPrefix = "tv-libraries-overview-item-" internal const val TvLibrariesOverviewItemTagPrefix = "tv-libraries-overview-item-"
@@ -52,8 +52,8 @@ private val TvLibrariesOverviewCardShape = RoundedCornerShape(18.dp)
@Composable @Composable
fun TvLibrariesOverviewScreen( fun TvLibrariesOverviewScreen(
libraries: List<LibraryItem>, libraries: List<LibraryUiModel>,
onLibrarySelected: (LibraryItem) -> Unit, onLibrarySelected: (LibraryUiModel) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
@@ -117,7 +117,7 @@ fun TvLibrariesOverviewScreen(
@Composable @Composable
private fun TvLibraryOverviewCard( private fun TvLibraryOverviewCard(
item: LibraryItem, item: LibraryUiModel,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {

View File

@@ -5,7 +5,7 @@ import hu.bbara.purefin.model.LibraryKind
import hu.bbara.purefin.model.MediaKind import hu.bbara.purefin.model.MediaKind
import hu.bbara.purefin.model.Movie import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.feature.browse.home.NextUpItem import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.feature.browse.home.PosterItem
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@@ -104,8 +104,8 @@ class TvHomeHeroStateTest {
assertEquals(continueWatching, registry.firstAvailableItem) assertEquals(continueWatching, registry.firstAvailableItem)
} }
private fun sampleLibrary(id: UUID, isEmpty: Boolean = false): LibraryItem { private fun sampleLibrary(id: UUID, isEmpty: Boolean = false): LibraryUiModel {
return LibraryItem( return LibraryUiModel(
id = id, id = id,
name = "Movies", name = "Movies",
type = LibraryKind.MOVIES, type = LibraryKind.MOVIES,

View File

@@ -1,10 +1,8 @@
package hu.bbara.purefin.ui.screen package hu.bbara.purefin.ui.screen
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -12,7 +10,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.IntOffset
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavBackStack
@@ -20,13 +17,11 @@ import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.scene.Scene
import androidx.navigation3.ui.NavDisplay import androidx.navigation3.ui.NavDisplay
import hu.bbara.purefin.feature.browse.home.AppViewModel import hu.bbara.purefin.feature.browse.home.AppViewModel
import hu.bbara.purefin.navigation.LocalNavigationManager import hu.bbara.purefin.navigation.LocalNavigationManager
import hu.bbara.purefin.ui.screen.download.DownloadsScreen import hu.bbara.purefin.ui.screen.download.DownloadsScreen
import hu.bbara.purefin.ui.screen.home.HomeScreen import hu.bbara.purefin.ui.screen.home.HomeScreen
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem
import hu.bbara.purefin.ui.screen.libraries.LibrariesScreen import hu.bbara.purefin.ui.screen.libraries.LibrariesScreen
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -48,14 +43,6 @@ fun AppScreen(
val currentRoute = backStack.lastOrNull() ?: AppTabRoute.Home val currentRoute = backStack.lastOrNull() ?: AppTabRoute.Home
val selectedTab = currentRoute.toTabIndex() val selectedTab = currentRoute.toTabIndex()
val libraryNavItems = libraries.map {
HomeNavItem(
id = it.id,
label = it.name,
posterUrl = it.posterUrl
)
}
LifecycleResumeEffect(Unit) { LifecycleResumeEffect(Unit) {
viewModel.onResumed() viewModel.onResumed()
onPauseOrDispose { } onPauseOrDispose { }
@@ -97,8 +84,8 @@ fun AppScreen(
} }
entry<AppTabRoute.Libraries>(metadata = appTabMetadata(AppTabRoute.Libraries)) { entry<AppTabRoute.Libraries>(metadata = appTabMetadata(AppTabRoute.Libraries)) {
LibrariesScreen( LibrariesScreen(
items = libraryNavItems, items = libraries,
onLibrarySelected = { item -> viewModel.onLibrarySelected(item.id, item.label) }, onLibrarySelected = { item -> viewModel.onLibrarySelected(item.id, item.name) },
selectedTab = selectedTab, selectedTab = selectedTab,
onTabSelected = onTabSelected, onTabSelected = onTabSelected,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
@@ -125,9 +112,9 @@ fun AppScreen(
entryDecorators = listOf( entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator() rememberSaveableStateHolderNavEntryDecorator()
), ),
transitionSpec = { appTabTransition() }, transitionSpec = { fadeIn(tween(220)) togetherWith fadeOut(tween(220)) },
popTransitionSpec = { appTabTransition() }, popTransitionSpec = { fadeIn(tween(220)) togetherWith fadeOut(tween(220)) },
predictivePopTransitionSpec = { _ -> appTabTransition() }, predictivePopTransitionSpec = { _ -> fadeIn(tween(220)) togetherWith fadeOut(tween(220)) },
entryProvider = tabEntryProvider, entryProvider = tabEntryProvider,
modifier = modifier.fillMaxSize() modifier = modifier.fillMaxSize()
) )
@@ -158,27 +145,6 @@ private fun Int.toAppTabRoute(): AppTabRoute = when (this) {
else -> AppTabRoute.Home else -> AppTabRoute.Home
} }
private fun AnimatedContentTransitionScope<Scene<AppTabRoute>>.appTabTransition(): ContentTransform {
val initialIndex = initialState.metadata.appTabIndex()
val targetIndex = targetState.metadata.appTabIndex()
val animationSpec = tween<IntOffset>(durationMillis = 140)
return when {
targetIndex > initialIndex -> {
slideInHorizontally(animationSpec = animationSpec) { fullWidth -> fullWidth } togetherWith
slideOutHorizontally(animationSpec = animationSpec) { fullWidth -> -fullWidth }
}
targetIndex < initialIndex -> {
slideInHorizontally(animationSpec = animationSpec) { fullWidth -> -fullWidth } togetherWith
slideOutHorizontally(animationSpec = animationSpec) { fullWidth -> fullWidth }
}
else -> {
slideInHorizontally(animationSpec = animationSpec) { 0 } togetherWith
slideOutHorizontally(animationSpec = animationSpec) { 0 }
}
}
}
private fun appTabMetadata(route: AppTabRoute): Map<String, Any> = private fun appTabMetadata(route: AppTabRoute): Map<String, Any> =
mapOf(APP_TAB_INDEX_METADATA to route.toTabIndex()) mapOf(APP_TAB_INDEX_METADATA to route.toTabIndex())

View File

@@ -12,7 +12,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.screen.AppBottomBar import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.HomeContent import hu.bbara.purefin.ui.screen.home.components.HomeContent
@@ -23,7 +23,7 @@ import java.util.UUID
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeScreen( fun HomeScreen(
libraries: List<LibraryItem>, libraries: List<LibraryUiModel>,
libraryContent: Map<UUID, List<MediaUiModel>>, libraryContent: Map<UUID, List<MediaUiModel>>,
suggestions: List<MediaUiModel>, suggestions: List<MediaUiModel>,
continueWatching: List<MediaUiModel>, continueWatching: List<MediaUiModel>,
@@ -31,7 +31,7 @@ fun HomeScreen(
isRefreshing: Boolean, isRefreshing: Boolean,
onRefresh: () -> Unit, onRefresh: () -> Unit,
onMediaSelected: (MediaUiModel) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
onLibrarySelected: (LibraryItem) -> Unit, onLibrarySelected: (LibraryUiModel) -> Unit,
onProfileClick: () -> Unit, onProfileClick: () -> Unit,
onSettingsClick: () -> Unit, onSettingsClick: () -> Unit,
onLogoutClick: () -> Unit, onLogoutClick: () -> Unit,

View File

@@ -22,7 +22,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.screen.home.components.continuewatching.ContinueWatchingSection import hu.bbara.purefin.ui.screen.home.components.continuewatching.ContinueWatchingSection
import hu.bbara.purefin.ui.screen.home.components.featured.SuggestionsSection import hu.bbara.purefin.ui.screen.home.components.featured.SuggestionsSection
import hu.bbara.purefin.ui.screen.home.components.library.LibraryPosterSection import hu.bbara.purefin.ui.screen.home.components.library.LibraryPosterSection
@@ -32,7 +32,7 @@ import java.util.UUID
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeContent( fun HomeContent(
libraries: List<LibraryItem>, libraries: List<LibraryUiModel>,
libraryContent: Map<UUID, List<MediaUiModel>>, libraryContent: Map<UUID, List<MediaUiModel>>,
suggestions: List<MediaUiModel>, suggestions: List<MediaUiModel>,
continueWatching: List<MediaUiModel>, continueWatching: List<MediaUiModel>,
@@ -40,7 +40,7 @@ fun HomeContent(
isRefreshing: Boolean, isRefreshing: Boolean,
onRefresh: () -> Unit, onRefresh: () -> Unit,
onMediaSelected: (MediaUiModel) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
onLibrarySelected: (LibraryItem) -> Unit, onLibrarySelected: (LibraryUiModel) -> Unit,
onBrowseLibrariesClick: () -> Unit, onBrowseLibrariesClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {

View File

@@ -1,15 +0,0 @@
package hu.bbara.purefin.ui.screen.home.components
import java.util.UUID
data class HomeNavItem(
val id: UUID,
val label: String,
val posterUrl: String,
val selected: Boolean = false
)
data class HomeUser(
val name: String,
val plan: String
)

View File

@@ -12,14 +12,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
@Composable @Composable
fun LibraryPosterSection( fun LibraryPosterSection(
library: LibraryItem, library: LibraryUiModel,
items: List<MediaUiModel>, items: List<MediaUiModel>,
onLibrarySelected: (LibraryItem) -> Unit, onLibrarySelected: (LibraryUiModel) -> Unit,
onMediaSelected: (MediaUiModel) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {

View File

@@ -0,0 +1,407 @@
package hu.bbara.purefin.ui.screen.home.components.search
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.LocalFireDepartment
import androidx.compose.material.icons.outlined.Menu
import androidx.compose.material.icons.outlined.Movie
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.TrendingUp
import androidx.compose.material.icons.outlined.Tune
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.common.image.PurefinLogo
import hu.bbara.purefin.ui.theme.AppTheme
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun HomeSearchFullScreen(
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
Column(
modifier = modifier
.fillMaxSize()
.background(scheme.background)
.statusBarsPadding()
.verticalScroll(rememberScrollState())
.padding(horizontal = 20.dp)
.padding(bottom = 28.dp)
) {
SearchHeader(modifier = Modifier.padding(top = 12.dp))
Spacer(modifier = Modifier.height(24.dp))
SearchField()
Spacer(modifier = Modifier.height(30.dp))
SectionTitle(
text = "Trending Searches",
icon = {
Icon(
imageVector = Icons.Outlined.LocalFireDepartment,
contentDescription = null,
tint = scheme.primary,
modifier = Modifier.size(26.dp)
)
}
)
Spacer(modifier = Modifier.height(16.dp))
FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
previewTrendingSearches.forEach { title ->
TrendingSearchChip(title = title)
}
}
Spacer(modifier = Modifier.height(34.dp))
SectionTitle(text = "Browse Categories")
Spacer(modifier = Modifier.height(18.dp))
CategoryGrid(categories = previewCategories)
Spacer(modifier = Modifier.height(24.dp))
OutlinedButton(
onClick = {},
shape = RoundedCornerShape(28.dp),
border = BorderStroke(1.dp, scheme.primary.copy(alpha = 0.52f)),
colors = ButtonDefaults.outlinedButtonColors(contentColor = scheme.primary),
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(
text = "View All Genres",
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 4.dp)
)
}
}
}
@Composable
private fun SearchHeader(
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier.fillMaxWidth()
) {
IconButton(onClick = {}) {
Icon(
imageVector = Icons.Outlined.Menu,
contentDescription = "Menu",
tint = scheme.primary,
modifier = Modifier.size(30.dp)
)
}
Spacer(modifier = Modifier.weight(1f))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
PurefinLogo(
contentDescription = "PureFin",
contentScale = ContentScale.Fit,
modifier = Modifier.size(38.dp)
)
Text(
text = "PureFin",
color = scheme.primary,
fontSize = 30.sp,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic
)
}
Spacer(modifier = Modifier.weight(1f))
Surface(
shape = CircleShape,
color = scheme.surfaceContainer,
border = BorderStroke(1.dp, scheme.outlineVariant.copy(alpha = 0.28f)),
modifier = Modifier.size(48.dp)
) {
Box(contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Outlined.Person,
contentDescription = "Profile",
tint = scheme.onSurfaceVariant,
modifier = Modifier.size(26.dp)
)
}
}
}
}
@Composable
private fun SearchField(
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
Surface(
shape = RoundedCornerShape(24.dp),
color = scheme.surfaceContainer,
border = BorderStroke(1.dp, scheme.outlineVariant.copy(alpha = 0.16f)),
modifier = modifier.fillMaxWidth()
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(68.dp)
.padding(horizontal = 20.dp)
) {
Icon(
imageVector = Icons.Outlined.Search,
contentDescription = null,
tint = scheme.onSurfaceVariant,
modifier = Modifier.size(30.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = "Search movies, shows, genres...",
style = MaterialTheme.typography.titleMedium,
color = scheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier
.height(34.dp)
.width(1.dp)
.background(scheme.outlineVariant.copy(alpha = 0.18f))
)
Spacer(modifier = Modifier.width(14.dp))
Icon(
imageVector = Icons.Outlined.Tune,
contentDescription = "Filter",
tint = scheme.primary,
modifier = Modifier.size(30.dp)
)
}
}
}
@Composable
private fun SectionTitle(
text: String,
modifier: Modifier = Modifier,
icon: @Composable (() -> Unit)? = null
) {
val scheme = MaterialTheme.colorScheme
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = modifier.fillMaxWidth()
) {
icon?.invoke()
Text(
text = text,
style = MaterialTheme.typography.headlineSmall,
color = scheme.onBackground,
fontWeight = FontWeight.Bold
)
}
}
@Composable
private fun TrendingSearchChip(
title: String,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
Surface(
shape = RoundedCornerShape(24.dp),
color = scheme.surfaceContainer,
border = BorderStroke(1.dp, scheme.outlineVariant.copy(alpha = 0.14f)),
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(horizontal = 18.dp, vertical = 11.dp)
) {
Icon(
imageVector = Icons.Outlined.TrendingUp,
contentDescription = null,
tint = scheme.onSurfaceVariant,
modifier = Modifier.size(18.dp)
)
Text(
text = title,
style = MaterialTheme.typography.labelLarge,
color = scheme.onSurface,
fontWeight = FontWeight.SemiBold
)
}
}
}
@Composable
private fun CategoryGrid(
categories: List<SearchCategory>,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier.fillMaxWidth()
) {
categories.chunked(2).forEach { rowItems ->
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth()
) {
rowItems.forEach { category ->
CategoryCard(
category = category,
modifier = Modifier.weight(1f)
)
}
if (rowItems.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
}
@Composable
private fun CategoryCard(
category: SearchCategory,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
val shape = RoundedCornerShape(18.dp)
Surface(
shape = shape,
color = scheme.surfaceContainer,
modifier = modifier
.clip(shape)
.aspectRatio(1.45f)
) {
Box(modifier = Modifier.fillMaxSize()) {
PurefinAsyncImage(
model = category.imageUrl,
contentDescription = category.name,
fallbackIcon = Icons.Outlined.Movie,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Box(
modifier = Modifier
.matchParentSize()
.background(
Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 0.18f),
Color.Black.copy(alpha = 0.42f),
Color.Black.copy(alpha = 0.76f)
)
)
)
)
Text(
text = category.name,
style = MaterialTheme.typography.titleLarge,
color = Color.White,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.align(Alignment.BottomStart)
.padding(16.dp)
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun HomeSearchFullScreenPreview() {
AppTheme {
HomeSearchFullScreen(modifier = Modifier.fillMaxSize())
}
}
private data class SearchCategory(
val name: String,
val imageUrl: String
)
private val previewTrendingSearches = listOf(
"Dune",
"Severance",
"The Last of Us",
"Blade Runner",
"Foundation"
)
private val previewCategories = listOf(
SearchCategory(
name = "Action",
imageUrl = "https://images.unsplash.com/photo-1535016120720-40c646be5580"
),
SearchCategory(
name = "Adventure",
imageUrl = "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee"
),
SearchCategory(
name = "Comedy",
imageUrl = "https://images.unsplash.com/photo-1527224857830-43a7acc85260"
),
SearchCategory(
name = "Fantasy",
imageUrl = "https://images.unsplash.com/photo-1518709268805-4e9042af2176"
),
SearchCategory(
name = "Romance",
imageUrl = "https://images.unsplash.com/photo-1516589178581-6cd7833ae3b2"
),
SearchCategory(
name = "Sci-Fi",
imageUrl = "https://images.unsplash.com/photo-1446776811953-b23d57bd21aa"
)
)

View File

@@ -6,15 +6,15 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.screen.AppBottomBar import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem
import hu.bbara.purefin.ui.screen.libraries.components.LibrariesContent import hu.bbara.purefin.ui.screen.libraries.components.LibrariesContent
import hu.bbara.purefin.ui.screen.library.components.LibraryTopBar import hu.bbara.purefin.ui.screen.library.components.LibraryTopBar
@Composable @Composable
fun LibrariesScreen( fun LibrariesScreen(
items: List<HomeNavItem>, items: List<LibraryUiModel>,
onLibrarySelected: (HomeNavItem) -> Unit, onLibrarySelected: (LibraryUiModel) -> Unit,
selectedTab: Int, selectedTab: Int,
onTabSelected: (Int) -> Unit, onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier

View File

@@ -9,12 +9,12 @@ import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem import hu.bbara.purefin.ui.model.LibraryUiModel
@Composable @Composable
fun LibrariesContent( fun LibrariesContent(
items: List<HomeNavItem>, items: List<LibraryUiModel>,
onLibrarySelected: (HomeNavItem) -> Unit, onLibrarySelected: (LibraryUiModel) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
LazyVerticalGrid( LazyVerticalGrid(
@@ -26,7 +26,7 @@ fun LibrariesContent(
) { ) {
items(items, key = { it.id }) { item -> items(items, key = { it.id }) { item ->
LibraryListItem( LibraryListItem(
item = item, uiModel = item,
modifier = Modifier.clickable { modifier = Modifier.clickable {
onLibrarySelected(item) onLibrarySelected(item)
} }

View File

@@ -11,18 +11,18 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.screen.home.components.HomeNavItem
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.model.LibraryUiModel
@Composable @Composable
fun LibraryListItem( fun LibraryListItem(
item: HomeNavItem, uiModel: LibraryUiModel,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column(modifier = modifier.fillMaxWidth()) { Column(modifier = modifier.fillMaxWidth()) {
PurefinAsyncImage( PurefinAsyncImage(
model = item.posterUrl, model = uiModel.posterUrl,
contentDescription = item.label, contentDescription = uiModel.name,
contentScale = ContentScale.Inside, contentScale = ContentScale.Inside,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -30,7 +30,7 @@ fun LibraryListItem(
.clip(RoundedCornerShape(24.dp)) .clip(RoundedCornerShape(24.dp))
) )
Text( Text(
text = item.label, text = uiModel.name,
style = MaterialTheme.typography.displaySmall style = MaterialTheme.typography.displaySmall
) )
} }

View File

@@ -16,6 +16,7 @@ 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.ui.model.EpisodeUiModel import hu.bbara.purefin.ui.model.EpisodeUiModel
import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.model.MovieUiModel import hu.bbara.purefin.ui.model.MovieUiModel
import hu.bbara.purefin.ui.model.SeriesUiModel import hu.bbara.purefin.ui.model.SeriesUiModel
@@ -51,12 +52,12 @@ class AppViewModel @Inject constructor(
val libraries = homeRepository.libraries.map { libraries -> val libraries = homeRepository.libraries.map { libraries ->
libraries.map { libraries.map {
LibraryItem( LibraryUiModel(
id = it.id, id = it.id,
name = it.name, name = it.name,
type = it.type, type = it.type,
posterUrl = it.posterUrl, posterUrl = it.posterUrl,
isEmpty = when(it.type) { isEmpty = when (it.type) {
LibraryKind.MOVIES -> mediaCatalogReader.movies.value.isEmpty() LibraryKind.MOVIES -> mediaCatalogReader.movies.value.isEmpty()
LibraryKind.SERIES -> mediaCatalogReader.series.value.isEmpty() LibraryKind.SERIES -> mediaCatalogReader.series.value.isEmpty()
} }

View File

@@ -1,106 +0,0 @@
package hu.bbara.purefin.feature.browse.home
import hu.bbara.purefin.image.ArtworkKind
import hu.bbara.purefin.image.ImageUrlBuilder
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.LibraryKind
import hu.bbara.purefin.model.MediaKind
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series
import java.util.UUID
sealed interface FocusableItem {
val imageUrl: String
val primaryText: String
val secondaryText: String
val description: String
val id: UUID
val type: MediaKind
}
data class NextUpItem(
val episode: Episode
) : FocusableItem {
override val id: UUID = episode.id
override val type: MediaKind
get() = MediaKind.EPISODE
override val imageUrl: String
get() = ImageUrlBuilder.finishImageUrl(
prefixImageUrl = episode.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
override val primaryText: String = episode.title
override val secondaryText: String = episode.releaseDate
override val description: String
get() = episode.synopsis
}
data class LibraryItem(
val id: UUID,
val name: String,
val type: LibraryKind,
val posterUrl: String,
val isEmpty: Boolean
)
data class PosterItem(
override val type: MediaKind,
val movie: Movie? = null,
val series: Series? = null,
val episode: Episode? = null
) : FocusableItem {
override val id: UUID = when (type) {
MediaKind.MOVIE -> movie!!.id
MediaKind.EPISODE -> episode!!.id
MediaKind.SERIES -> series!!.id
else -> throw IllegalArgumentException("Invalid type: $type")
}
val title: String = when (type) {
MediaKind.MOVIE -> movie!!.title
MediaKind.EPISODE -> episode!!.title
MediaKind.SERIES -> series!!.name
else -> throw IllegalArgumentException("Invalid type: $type")
}
override val imageUrl: String = when (type) {
MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = movie!!.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = episode!!.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
MediaKind.SERIES -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = series!!.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
else -> throw IllegalArgumentException("Invalid type: $type")
}
override val primaryText: String
get() = when (type) {
MediaKind.MOVIE -> movie!!.title
MediaKind.EPISODE -> episode!!.title
MediaKind.SERIES -> series!!.name
else -> throw IllegalArgumentException("Invalid type: $type")
}
override val secondaryText: String
get() = when (type) {
MediaKind.MOVIE -> movie!!.year
MediaKind.EPISODE -> episode!!.releaseDate
MediaKind.SERIES -> series!!.year
else -> throw IllegalArgumentException("Invalid type: $type")
}
override val description: String
get() = when (type) {
MediaKind.MOVIE -> movie!!.synopsis
MediaKind.EPISODE -> episode!!.synopsis
MediaKind.SERIES -> series!!.synopsis
else -> throw IllegalArgumentException("Invalid type: $type")
}
fun watched() = when (type) {
MediaKind.MOVIE -> movie!!.watched
MediaKind.EPISODE -> episode!!.watched
else -> throw IllegalArgumentException("Invalid type: $type")
}
}

View File

@@ -0,0 +1,12 @@
package hu.bbara.purefin.ui.model
import hu.bbara.purefin.model.LibraryKind
import java.util.UUID
data class LibraryUiModel(
val id: UUID,
val name: String,
val type: LibraryKind,
val posterUrl: String,
val isEmpty: Boolean
)

View File

@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.Response import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.api.client.extensions.authenticateUserByName import org.jellyfin.sdk.api.client.extensions.authenticateUserByName
import org.jellyfin.sdk.api.client.extensions.genresApi
import org.jellyfin.sdk.api.client.extensions.itemsApi import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
import org.jellyfin.sdk.api.client.extensions.mediaSegmentsApi import org.jellyfin.sdk.api.client.extensions.mediaSegmentsApi
@@ -250,6 +251,18 @@ class JellyfinApiClient @Inject constructor(
nextUpEpisodes nextUpEpisodes
} }
suspend fun getGenres(id: UUID? = null) : List<BaseItemDto> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) {
return@withContext emptyList()
}
val result = api.genresApi.getGenres(
userId = getUserId(),
parentId = id,
)
Log.d("getGenres", result.toString())
result.content.items
}
suspend fun getMediaSources(mediaId: UUID): List<MediaSourceInfo> = withContext(Dispatchers.IO) { suspend fun getMediaSources(mediaId: UUID): List<MediaSourceInfo> = withContext(Dispatchers.IO) {
if (!ensureConfigured()) { if (!ensureConfigured()) {
return@withContext emptyList() return@withContext emptyList()