diff --git a/app-tv/build.gradle.kts b/app-tv/build.gradle.kts index 222dd7d9..30b99790 100644 --- a/app-tv/build.gradle.kts +++ b/app-tv/build.gradle.kts @@ -52,9 +52,19 @@ kotlin { dependencies { implementation(project(":core:model")) + implementation(project(":core:image")) implementation(project(":core:data")) + implementation(project(":core:navigation")) + implementation(project(":core:download")) implementation(project(":core:player")) - implementation(project(":feature:shared")) + implementation(project(":data:jellyfin")) + implementation(project(":data:offline")) + implementation(project(":data:catalog")) + implementation(project(":feature:browse")) + implementation(project(":feature:search")) + implementation(project(":feature:content")) + implementation(project(":feature:downloads")) + implementation(project(":feature:login")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.viewmodel.compose) @@ -83,8 +93,6 @@ dependencies { implementation(libs.media3.datasource.okhttp) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.navigation3.ui) - implementation(libs.androidx.room.ktx) - ksp(libs.androidx.room.compiler) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app-tv/src/androidTest/java/hu/bbara/purefin/tv/home/ui/TvHomeContentTest.kt b/app-tv/src/androidTest/java/hu/bbara/purefin/tv/home/ui/TvHomeContentTest.kt index 08d0f762..c5de1225 100644 --- a/app-tv/src/androidTest/java/hu/bbara/purefin/tv/home/ui/TvHomeContentTest.kt +++ b/app-tv/src/androidTest/java/hu/bbara/purefin/tv/home/ui/TvHomeContentTest.kt @@ -18,10 +18,10 @@ import androidx.compose.ui.input.key.Key import androidx.compose.ui.unit.dp import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.tv.home.TvHomeScreen import hu.bbara.purefin.ui.theme.AppTheme import org.jellyfin.sdk.model.api.BaseItemKind diff --git a/app-tv/src/androidTest/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreenTest.kt b/app-tv/src/androidTest/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreenTest.kt index 327bdf23..d65625a4 100644 --- a/app-tv/src/androidTest/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreenTest.kt +++ b/app-tv/src/androidTest/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreenTest.kt @@ -4,7 +4,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick -import hu.bbara.purefin.feature.shared.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.ui.theme.AppTheme import org.jellyfin.sdk.model.api.CollectionType import org.junit.Assert.assertEquals diff --git a/app-tv/src/main/java/hu/bbara/purefin/app/content/episode/TvEpisodeScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/app/content/episode/TvEpisodeScreen.kt index 907d5dba..bc63a6b2 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/app/content/episode/TvEpisodeScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/app/content/episode/TvEpisodeScreen.kt @@ -20,13 +20,13 @@ import hu.bbara.purefin.common.ui.components.MediaDetailPlaybackSection import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle import hu.bbara.purefin.common.ui.components.TvMediaDetailBodyBox import hu.bbara.purefin.common.ui.components.TvMediaDetailScaffold -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.navigation.EpisodeDto +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.EpisodeDto import hu.bbara.purefin.tv.navigation.LocalNavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.model.Episode -import hu.bbara.purefin.feature.shared.content.episode.EpisodeScreenViewModel -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.feature.content.episode.EpisodeScreenViewModel +import hu.bbara.purefin.core.image.ArtworkKind @Composable fun TvEpisodeScreen( @@ -87,7 +87,7 @@ internal fun TvEpisodeScreenContent( ) { item { TvMediaDetailBodyBox( - backgroundImageUrl = JellyfinImageHelper.finishImageUrl(episode.imageUrlPrefix, ImageType.PRIMARY), + backgroundImageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), modifier = it ) { TvEpisodeHeroSection( diff --git a/app-tv/src/main/java/hu/bbara/purefin/app/content/movie/TvMovieScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/app/content/movie/TvMovieScreen.kt index 6422974f..360a4387 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/app/content/movie/TvMovieScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/app/content/movie/TvMovieScreen.kt @@ -21,9 +21,9 @@ import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle import hu.bbara.purefin.common.ui.components.TvMediaDetailBodyBox import hu.bbara.purefin.common.ui.components.TvMediaDetailScaffold import hu.bbara.purefin.common.ui.components.tvMediaDetailBackgroundImageUrl -import hu.bbara.purefin.feature.shared.navigation.MovieDto +import hu.bbara.purefin.core.navigation.MovieDto import hu.bbara.purefin.core.model.Movie -import hu.bbara.purefin.feature.shared.content.movie.MovieScreenViewModel +import hu.bbara.purefin.feature.content.movie.MovieScreenViewModel @Composable fun TvMovieScreen( diff --git a/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesComponents.kt b/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesComponents.kt index fbabc819..f888266c 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesComponents.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesComponents.kt @@ -59,13 +59,13 @@ import hu.bbara.purefin.common.ui.components.MediaProgressBar import hu.bbara.purefin.common.ui.components.MediaResumeButton import hu.bbara.purefin.common.ui.components.PurefinAsyncImage import hu.bbara.purefin.common.ui.components.WatchStateIndicator -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.model.CastMember import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Season import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.feature.content.series.SeriesViewModel +import hu.bbara.purefin.core.image.ArtworkKind internal const val SeriesPlayButtonTag = "series-play-button" internal const val SeriesFirstSeasonTabTag = "series-first-season-tab" @@ -296,7 +296,7 @@ private fun TvEpisodeCard( ) ) { PurefinAsyncImage( - model = JellyfinImageHelper.finishImageUrl(episode.imageUrlPrefix, ImageType.PRIMARY), + model = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop diff --git a/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesScreen.kt index 5cedeb04..65d4ba17 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/app/content/series/TvSeriesScreen.kt @@ -22,10 +22,10 @@ import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle import hu.bbara.purefin.common.ui.components.TvMediaDetailBodyBox import hu.bbara.purefin.common.ui.components.TvMediaDetailScaffold import hu.bbara.purefin.common.ui.components.tvMediaDetailBackgroundImageUrl -import hu.bbara.purefin.feature.shared.navigation.SeriesDto +import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.core.model.Season import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel +import hu.bbara.purefin.feature.content.series.SeriesViewModel import java.util.UUID @Composable diff --git a/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt b/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt index f79a9d9e..41a3f618 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt @@ -32,8 +32,8 @@ import androidx.compose.ui.unit.sp import hu.bbara.purefin.common.ui.components.PurefinAsyncImage import hu.bbara.purefin.common.ui.components.UnwatchedEpisodeIndicator import hu.bbara.purefin.common.ui.components.WatchStateIndicator -import hu.bbara.purefin.feature.shared.home.FocusableItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.FocusableItem +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID import hu.bbara.purefin.core.model.MediaKind diff --git a/app-tv/src/main/java/hu/bbara/purefin/common/ui/components/MediaDetailShell.kt b/app-tv/src/main/java/hu/bbara/purefin/common/ui/components/MediaDetailShell.kt index d4310688..52b4e13f 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/common/ui/components/MediaDetailShell.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/common/ui/components/MediaDetailShell.kt @@ -32,8 +32,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import hu.bbara.purefin.common.ui.MediaSynopsis -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.image.ArtworkKind internal val MediaDetailHorizontalPadding = 48.dp private val MediaDetailHeaderTopPadding = 104.dp @@ -151,8 +151,8 @@ internal fun TvMediaDetailBodyBox( } internal fun tvMediaDetailBackgroundImageUrl(imageUrlPrefix: String?): String { - val primaryImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY) - val backdropImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.BACKDROP) + val primaryImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY) + val backdropImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.BACKDROP) return backdropImageUrl.ifBlank { primaryImageUrl } } diff --git a/app-tv/src/main/java/hu/bbara/purefin/login/ui/LoginScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/login/ui/LoginScreen.kt index 45c784aa..f8ace80a 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/login/ui/LoginScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/login/ui/LoginScreen.kt @@ -41,7 +41,7 @@ import hu.bbara.purefin.common.ui.PurefinComplexTextField import hu.bbara.purefin.common.ui.PurefinPasswordField import hu.bbara.purefin.common.ui.PurefinTextButton import hu.bbara.purefin.common.ui.PurefinWaitingScreen -import hu.bbara.purefin.feature.shared.login.LoginViewModel +import hu.bbara.purefin.feature.login.LoginViewModel import kotlinx.coroutines.launch @Composable diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt index eb970418..84154fea 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt @@ -37,13 +37,13 @@ import coil3.request.crossfade import coil3.util.DebugLogger import dagger.hilt.android.AndroidEntryPoint import hu.bbara.purefin.common.ui.PurefinWaitingScreen -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.client.JellyfinAuthInterceptor +import hu.bbara.purefin.core.data.SessionBootstrapper +import hu.bbara.purefin.data.jellyfin.client.JellyfinAuthInterceptor import hu.bbara.purefin.core.data.session.UserSessionRepository -import hu.bbara.purefin.feature.shared.navigation.NavigationCommand -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route import hu.bbara.purefin.login.ui.LoginScreen +import hu.bbara.purefin.core.navigation.NavigationCommand +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.tv.navigation.LocalNavigationBackStack import hu.bbara.purefin.tv.navigation.LocalNavigationManager import hu.bbara.purefin.ui.theme.AppTheme @@ -66,7 +66,7 @@ class TvActivity : ComponentActivity() { lateinit var navigationManager: NavigationManager @Inject - lateinit var jellyfinApiClient: JellyfinApiClient + lateinit var sessionBootstrapper: SessionBootstrapper @Inject lateinit var authInterceptor: JellyfinAuthInterceptor @@ -94,7 +94,7 @@ class TvActivity : ComponentActivity() { } private suspend fun init() { - jellyfinApiClient.updateApiClient() + sessionBootstrapper.initialize() } private fun configureImageLoader() { diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/TvAppScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/TvAppScreen.kt index 576080bf..5574d4c1 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/TvAppScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/TvAppScreen.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect -import hu.bbara.purefin.feature.shared.home.AppViewModel +import hu.bbara.purefin.feature.browse.home.AppViewModel import hu.bbara.purefin.tv.home.TvHomeScreen import hu.bbara.purefin.tv.home.ui.TvDrawerDestination import hu.bbara.purefin.tv.home.ui.TvDrawerDestinationItem diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackModule.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackModule.kt deleted file mode 100644 index 61bb5bc6..00000000 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackModule.kt +++ /dev/null @@ -1,56 +0,0 @@ -package hu.bbara.purefin.tv.di - -import androidx.media3.datasource.DataSource -import androidx.media3.datasource.okhttp.OkHttpDataSource -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.download.MediaDownloadController -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf -import java.util.UUID -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object TvPlaybackModule { - - @Provides - @Singleton - fun providePlaybackDataSourceFactory( - okHttpDataSourceFactory: OkHttpDataSource.Factory - ): DataSource.Factory { - return okHttpDataSourceFactory - } - - @Provides - @Singleton - fun provideMediaDownloadController(): MediaDownloadController { - return TvNoOpMediaDownloadController - } -} - -internal object TvNoOpMediaDownloadController : MediaDownloadController { - private val notDownloaded = MutableStateFlow(DownloadState.NotDownloaded) - - override fun observeActiveDownloads(): Flow> = flowOf(emptyMap()) - - override fun observeDownloadState(contentId: String) = notDownloaded - - override suspend fun downloadMovie(movieId: UUID) = Unit - - override suspend fun cancelDownload(movieId: UUID) = Unit - - override suspend fun downloadEpisode(episodeId: UUID) = Unit - - override suspend fun downloadEpisodes(episodeIds: List) = Unit - - override suspend fun cancelEpisodeDownload(episodeId: UUID) = Unit - - override suspend fun enableSmartDownload(seriesId: UUID) = Unit - - override suspend fun syncSmartDownloads() = Unit -} diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackProfileFamilyModule.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackProfileFamilyModule.kt index 4c1ea36a..ce2609f5 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackProfileFamilyModule.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/di/TvPlaybackProfileFamilyModule.kt @@ -4,7 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import hu.bbara.purefin.core.data.client.PlaybackProfileFamily +import hu.bbara.purefin.core.data.PlaybackProfileFamily @Module @InstallIn(SingletonComponent::class) diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/TvHomeScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/TvHomeScreen.kt index b55561c0..e064438d 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/TvHomeScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/TvHomeScreen.kt @@ -9,10 +9,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.tv.home.ui.TvFocusedItemHero import hu.bbara.purefin.tv.home.ui.TvHomeContent import hu.bbara.purefin.tv.home.ui.rememberTvHomeHeroState diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvFocusedHeroModel.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvFocusedHeroModel.kt index 96b04492..10b9093b 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvFocusedHeroModel.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvFocusedHeroModel.kt @@ -1,16 +1,16 @@ package hu.bbara.purefin.tv.home.ui -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.FocusableItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.FocusableItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID import hu.bbara.purefin.core.model.MediaKind -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import kotlin.math.roundToInt internal data class TvFocusedHeroModel( @@ -42,7 +42,7 @@ internal fun FocusableItem.toTvFocusedHeroModel(): TvFocusedHeroModel { } internal fun backdropImageUrl(imageUrlPrefix: String?, fallbackImageUrl: String): String { - val backdropImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.BACKDROP) + val backdropImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.BACKDROP) return backdropImageUrl.ifBlank { fallbackImageUrl } } @@ -51,7 +51,7 @@ private fun Movie.toTvFocusedHeroModel(): TvFocusedHeroModel { id = id, backdropImageUrl = backdropImageUrl( imageUrlPrefix = imageUrlPrefix, - fallbackImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY) + fallbackImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY) ), title = title, metadata = listOf(year, rating, runtime, format).compactMetadata(), @@ -63,7 +63,7 @@ private fun Episode.toTvFocusedHeroModel(): TvFocusedHeroModel { id = id, backdropImageUrl = backdropImageUrl( imageUrlPrefix = imageUrlPrefix, - fallbackImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY) + fallbackImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY) ), title = title, metadata = listOf( @@ -87,7 +87,7 @@ private fun Series.toTvFocusedHeroModel(): TvFocusedHeroModel { id = id, backdropImageUrl = backdropImageUrl( imageUrlPrefix = imageUrlPrefix, - fallbackImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY) + fallbackImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY) ), title = name, metadata = listOf(year, seasonLabel(seasonCount), unwatchedText).compactMetadata(), diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt index 9f6c66bf..ea32bb6c 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt @@ -24,11 +24,11 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.FocusableItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.FocusableItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item" diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroState.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroState.kt index d0927863..eeca4cce 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroState.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroState.kt @@ -5,11 +5,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.FocusableItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.FocusableItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID internal data class TvHomeItemRegistry( diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt index 3ef0b7a8..e25e5246 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt @@ -47,14 +47,14 @@ import androidx.compose.ui.unit.sp import hu.bbara.purefin.common.ui.PosterCard import hu.bbara.purefin.common.ui.components.MediaProgressBar import hu.bbara.purefin.common.ui.components.PurefinAsyncImage -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.FocusableItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.FocusableItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID import hu.bbara.purefin.core.model.MediaKind -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind private val TvHomeSectionsThumbShape = RoundedCornerShape(20.dp) private val TvHomeSectionsHorizontalPadding = 32.dp @@ -93,14 +93,14 @@ fun TvContinueWatchingSection( title = item.primaryText, supporting = item.secondaryText, imageUrl = when (item.type) { - MediaKind.MOVIE -> JellyfinImageHelper.finishImageUrl( + MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = item.movie?.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) - MediaKind.EPISODE -> JellyfinImageHelper.finishImageUrl( + MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = item.episode?.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) else -> null diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreen.kt index e9a692a0..d7c6b45a 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibrariesOverviewScreen.kt @@ -44,7 +44,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import hu.bbara.purefin.common.ui.components.PurefinAsyncImage -import hu.bbara.purefin.feature.shared.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.core.model.LibraryKind internal const val TvLibrariesOverviewItemTagPrefix = "tv-libraries-overview-item-" diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibraryScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibraryScreen.kt index fd4a41a3..84bc6fe1 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibraryScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/library/ui/TvLibraryScreen.kt @@ -24,9 +24,9 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.common.ui.PosterCard import hu.bbara.purefin.common.ui.components.PurefinIconButton -import hu.bbara.purefin.feature.shared.navigation.LibraryDto -import hu.bbara.purefin.feature.shared.home.PosterItem -import hu.bbara.purefin.feature.shared.library.LibraryViewModel +import hu.bbara.purefin.core.navigation.LibraryDto +import hu.bbara.purefin.feature.browse.home.PosterItem +import hu.bbara.purefin.feature.browse.library.LibraryViewModel import java.util.UUID @Composable diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/NavigationLocals.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/NavigationLocals.kt index f29bcd93..c3c0c9cb 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/NavigationLocals.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/NavigationLocals.kt @@ -3,8 +3,8 @@ package hu.bbara.purefin.tv.navigation import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route val LocalNavigationManager: ProvidableCompositionLocal = staticCompositionLocalOf { error("NavigationManager not provided") } diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvNavigationModule.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvNavigationModule.kt index e7336472..8534dc5b 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvNavigationModule.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvNavigationModule.kt @@ -6,7 +6,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route @Module @InstallIn(ActivityRetainedComponent::class) diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvRouteEntryBuilder.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvRouteEntryBuilder.kt index c32ec930..743cedca 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvRouteEntryBuilder.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/navigation/TvRouteEntryBuilder.kt @@ -4,7 +4,7 @@ import androidx.navigation3.runtime.EntryProviderScope import hu.bbara.purefin.app.content.episode.TvEpisodeScreen import hu.bbara.purefin.app.content.movie.TvMovieScreen import hu.bbara.purefin.app.content.series.TvSeriesScreen -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.login.ui.LoginScreen import hu.bbara.purefin.tv.TvAppScreen import hu.bbara.purefin.tv.library.ui.TvLibraryScreen diff --git a/app-tv/src/test/java/hu/bbara/purefin/tv/di/TvNoOpMediaDownloadControllerTest.kt b/app-tv/src/test/java/hu/bbara/purefin/tv/di/TvNoOpMediaDownloadControllerTest.kt deleted file mode 100644 index 5415e5c0..00000000 --- a/app-tv/src/test/java/hu/bbara/purefin/tv/di/TvNoOpMediaDownloadControllerTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hu.bbara.purefin.tv.di - -import hu.bbara.purefin.core.data.download.DownloadState -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Test - -class TvNoOpMediaDownloadControllerTest { - - @Test - fun `tv no-op download controller always reports no downloads`() = runBlocking { - assertEquals(emptyMap(), TvNoOpMediaDownloadController.observeActiveDownloads().first()) - assertEquals( - DownloadState.NotDownloaded, - TvNoOpMediaDownloadController.observeDownloadState("movie-1").value - ) - } -} diff --git a/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvFocusedItemHeroTest.kt b/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvFocusedItemHeroTest.kt index 29e16c29..c95acebd 100644 --- a/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvFocusedItemHeroTest.kt +++ b/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvFocusedItemHeroTest.kt @@ -1,14 +1,14 @@ package hu.bbara.purefin.tv.home.ui -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import org.jellyfin.sdk.model.api.BaseItemKind -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Test @@ -33,7 +33,7 @@ class TvFocusedItemHeroTest { assertEquals("42%", hero.progressLabel) assertNull(hero.watchedText) assertEquals( - JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.BACKDROP), + ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.BACKDROP), hero.backdropImageUrl ) } diff --git a/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroStateTest.kt b/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroStateTest.kt index 086e89f2..92803f2a 100644 --- a/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroStateTest.kt +++ b/app-tv/src/test/java/hu/bbara/purefin/tv/home/ui/TvHomeHeroStateTest.kt @@ -2,10 +2,10 @@ package hu.bbara.purefin.tv.home.ui import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.CollectionType import org.junit.Assert.assertEquals diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 684d3600..e5aba915 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,10 +51,19 @@ kotlin { dependencies { implementation(project(":core:model")) + implementation(project(":core:image")) implementation(project(":core:data")) - implementation(project(":feature:download")) + implementation(project(":core:navigation")) + implementation(project(":core:download")) implementation(project(":core:player")) - implementation(project(":feature:shared")) + implementation(project(":data:jellyfin")) + implementation(project(":data:offline")) + implementation(project(":data:catalog")) + implementation(project(":feature:browse")) + implementation(project(":feature:search")) + implementation(project(":feature:content")) + implementation(project(":feature:downloads")) + implementation(project(":feature:login")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.viewmodel.compose) @@ -83,8 +92,6 @@ dependencies { implementation(libs.media3.datasource.okhttp) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.navigation3.ui) - implementation(libs.androidx.room.ktx) - ksp(libs.androidx.room.compiler) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e4a42bca..f644fbc8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ android:exported="false" android:theme="@style/Theme.Purefin" /> diff --git a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt index fe8ce43d..833301e7 100644 --- a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt +++ b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt @@ -36,15 +36,15 @@ import coil3.network.okhttp.OkHttpNetworkFetcherFactory import coil3.request.crossfade import coil3.util.DebugLogger import dagger.hilt.android.AndroidEntryPoint -import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.client.JellyfinAuthInterceptor +import hu.bbara.purefin.core.data.SessionBootstrapper +import hu.bbara.purefin.data.jellyfin.client.JellyfinAuthInterceptor import hu.bbara.purefin.core.data.session.UserSessionRepository -import hu.bbara.purefin.feature.shared.navigation.NavigationCommand -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route import hu.bbara.purefin.navigation.LocalNavigationBackStack import hu.bbara.purefin.navigation.LocalNavigationManager +import hu.bbara.purefin.core.navigation.NavigationCommand +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route +import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.screen.login.LoginScreen import hu.bbara.purefin.ui.theme.AppTheme import hu.bbara.purefin.ui.theme.backgroundDark @@ -65,7 +65,7 @@ class PurefinActivity : ComponentActivity() { lateinit var navigationManager: NavigationManager @Inject - lateinit var jellyfinApiClient: JellyfinApiClient + lateinit var sessionBootstrapper: SessionBootstrapper @Inject lateinit var authInterceptor: JellyfinAuthInterceptor @@ -93,7 +93,7 @@ class PurefinActivity : ComponentActivity() { } private suspend fun init() { - jellyfinApiClient.updateApiClient() + sessionBootstrapper.initialize() } private fun configureImageLoader() { diff --git a/app/src/main/java/hu/bbara/purefin/modules/NavigationModule.kt b/app/src/main/java/hu/bbara/purefin/modules/NavigationModule.kt index 6176a23d..06fd732a 100644 --- a/app/src/main/java/hu/bbara/purefin/modules/NavigationModule.kt +++ b/app/src/main/java/hu/bbara/purefin/modules/NavigationModule.kt @@ -6,7 +6,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.navigation.appRouteEntryBuilder @Module diff --git a/app/src/main/java/hu/bbara/purefin/modules/PlaybackProfileFamilyModule.kt b/app/src/main/java/hu/bbara/purefin/modules/PlaybackProfileFamilyModule.kt index 33372c00..a5303d6f 100644 --- a/app/src/main/java/hu/bbara/purefin/modules/PlaybackProfileFamilyModule.kt +++ b/app/src/main/java/hu/bbara/purefin/modules/PlaybackProfileFamilyModule.kt @@ -4,7 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import hu.bbara.purefin.core.data.client.PlaybackProfileFamily +import hu.bbara.purefin.core.data.PlaybackProfileFamily @Module @InstallIn(SingletonComponent::class) diff --git a/app/src/main/java/hu/bbara/purefin/navigation/NavigationLocals.kt b/app/src/main/java/hu/bbara/purefin/navigation/NavigationLocals.kt index 04a6d775..de0cc1c8 100644 --- a/app/src/main/java/hu/bbara/purefin/navigation/NavigationLocals.kt +++ b/app/src/main/java/hu/bbara/purefin/navigation/NavigationLocals.kt @@ -3,8 +3,8 @@ package hu.bbara.purefin.navigation import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route val LocalNavigationManager: ProvidableCompositionLocal = staticCompositionLocalOf { error("NavigationManager not provided") } diff --git a/app/src/main/java/hu/bbara/purefin/navigation/RouteEntryBuilder.kt b/app/src/main/java/hu/bbara/purefin/navigation/RouteEntryBuilder.kt index bd29e4c6..4a776d8b 100644 --- a/app/src/main/java/hu/bbara/purefin/navigation/RouteEntryBuilder.kt +++ b/app/src/main/java/hu/bbara/purefin/navigation/RouteEntryBuilder.kt @@ -1,7 +1,7 @@ package hu.bbara.purefin.navigation import androidx.navigation3.runtime.EntryProviderScope -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.ui.screen.library.LibraryScreen import hu.bbara.purefin.ui.screen.login.LoginScreen import hu.bbara.purefin.ui.screen.AppScreen diff --git a/app/src/main/java/hu/bbara/purefin/ui/common/DefaultTopBar.kt b/app/src/main/java/hu/bbara/purefin/ui/common/DefaultTopBar.kt index b5a5eb46..8b27855d 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/common/DefaultTopBar.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/common/DefaultTopBar.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel -import hu.bbara.purefin.feature.shared.search.SearchViewModel +import hu.bbara.purefin.feature.search.SearchViewModel @Composable fun DefaultTopBar( diff --git a/app/src/main/java/hu/bbara/purefin/ui/common/card/PosterCard.kt b/app/src/main/java/hu/bbara/purefin/ui/common/card/PosterCard.kt index 6620b6c4..d9f86bf2 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/common/card/PosterCard.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/common/card/PosterCard.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.request.ImageRequest -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.ui.common.badge.UnwatchedEpisodeIndicator import hu.bbara.purefin.ui.common.badge.WatchStateIndicator import hu.bbara.purefin.ui.common.image.PurefinAsyncImage diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt index da4a2b9b..b2fd2a45 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect -import hu.bbara.purefin.feature.shared.home.AppViewModel +import hu.bbara.purefin.feature.browse.home.AppViewModel import hu.bbara.purefin.ui.screen.download.DownloadsScreen import hu.bbara.purefin.ui.screen.home.HomeScreen import hu.bbara.purefin.ui.screen.home.components.HomeNavItem diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt index e6a62591..f4221311 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.image.PurefinAsyncImage -import hu.bbara.purefin.feature.shared.download.ActiveDownloadItem +import hu.bbara.purefin.feature.downloads.ActiveDownloadItem @Composable internal fun DownloadingItemRow( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt index 64950402..eddd0f79 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.ui.common.card.PosterCard -import hu.bbara.purefin.feature.shared.download.DownloadsViewModel +import hu.bbara.purefin.feature.downloads.DownloadsViewModel @Composable fun DownloadsContent( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt index ff9ed682..7cb61457 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt @@ -37,19 +37,19 @@ import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.ui.common.media.MediaMetaChip import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.common.media.MediaHero -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.navigation.EpisodeDto +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.EpisodeDto import hu.bbara.purefin.navigation.LocalNavigationBackStack -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.model.CastMember import hu.bbara.purefin.core.model.Episode -import hu.bbara.purefin.feature.shared.content.episode.EpisodeScreenViewModel +import hu.bbara.purefin.feature.content.episode.EpisodeScreenViewModel import hu.bbara.purefin.ui.screen.episode.components.EpisodeDetails import hu.bbara.purefin.ui.screen.episode.components.EpisodeTopBar import hu.bbara.purefin.ui.screen.episode.components.EpisodeTopBarShortcut import hu.bbara.purefin.ui.theme.AppTheme -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import java.util.UUID @Composable @@ -167,7 +167,7 @@ private fun EpisodeHeroSection( .height(sectionHeight) ) { MediaHero( - imageUrl = JellyfinImageHelper.finishImageUrl(episode.imageUrlPrefix, ImageType.PRIMARY), + imageUrl = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), backgroundColor = scheme.background, modifier = Modifier.fillMaxSize() ) diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt index 295d652d..1754efd7 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt @@ -40,7 +40,7 @@ import hu.bbara.purefin.ui.common.button.GhostIconButton import hu.bbara.purefin.ui.common.button.MediaActionButton import hu.bbara.purefin.ui.common.media.MediaPlaybackSettings import hu.bbara.purefin.ui.common.button.MediaResumeButton -import hu.bbara.purefin.core.data.download.DownloadState +import hu.bbara.purefin.core.download.DownloadState import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.player.PlayerActivity diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/HomeScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/HomeScreen.kt index ad70e3e6..75fa4733 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/HomeScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/HomeScreen.kt @@ -16,11 +16,11 @@ import androidx.compose.ui.Modifier import hu.bbara.purefin.ui.screen.home.components.HomeContent import hu.bbara.purefin.ui.screen.home.components.HomeTopBar import hu.bbara.purefin.ui.screen.home.components.search.HomeSearchOverlay -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem -import hu.bbara.purefin.feature.shared.home.SuggestedItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem +import hu.bbara.purefin.feature.browse.home.SuggestedItem import hu.bbara.purefin.ui.screen.AppBottomBar import java.util.UUID diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt index 6990ff46..844a27af 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt @@ -22,11 +22,11 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.NextUpItem -import hu.bbara.purefin.feature.shared.home.PosterItem -import hu.bbara.purefin.feature.shared.home.SuggestedItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.PosterItem +import hu.bbara.purefin.feature.browse.home.SuggestedItem 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.library.LibraryPosterSection diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingCard.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingCard.kt index 18d34143..1a869dcc 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingCard.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingCard.kt @@ -25,11 +25,11 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.image.PurefinAsyncImage -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import java.util.UUID import hu.bbara.purefin.core.model.MediaKind -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind @Composable internal fun ContinueWatchingCard( @@ -53,13 +53,13 @@ internal fun ContinueWatchingCard( else -> "" } val imageUrl = when (item.type) { - MediaKind.MOVIE -> JellyfinImageHelper.finishImageUrl( + MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = item.movie?.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) - MediaKind.EPISODE -> JellyfinImageHelper.finishImageUrl( + MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = item.episode?.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) else -> null } diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt index dcc0a216..ca8c8f93 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.header.SectionHeader -import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem +import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import java.util.UUID @Composable diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionCard.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionCard.kt index 1a504604..c5b5fe6c 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionCard.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionCard.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.image.PurefinAsyncImage -import hu.bbara.purefin.feature.shared.home.SuggestedItem +import hu.bbara.purefin.feature.browse.home.SuggestedItem @Composable internal fun SuggestionCard( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt index 23e1db07..27c514f0 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt @@ -22,7 +22,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import hu.bbara.purefin.feature.shared.home.SuggestedItem +import hu.bbara.purefin.feature.browse.home.SuggestedItem @Composable fun SuggestionsSection( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/HomeBrowseCard.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/HomeBrowseCard.kt index de4cb1fe..d41a9966 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/HomeBrowseCard.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/HomeBrowseCard.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.badge.UnwatchedEpisodeIndicator import hu.bbara.purefin.ui.common.badge.WatchStateIndicator -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID import hu.bbara.purefin.core.model.MediaKind diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt index c0951d40..6c28fa03 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt @@ -9,8 +9,8 @@ import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import hu.bbara.purefin.feature.shared.home.LibraryItem -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.feature.browse.home.LibraryItem +import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.ui.common.header.SectionHeader import java.util.UUID diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpCard.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpCard.kt index eb8a6e92..589637b0 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpCard.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpCard.kt @@ -23,10 +23,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.image.PurefinAsyncImage -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.home.NextUpItem +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.feature.browse.home.NextUpItem import java.util.UUID -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind @Composable internal fun NextUpCard( @@ -57,7 +57,7 @@ internal fun NextUpCard( .background(scheme.surface) ) { PurefinAsyncImage( - model = JellyfinImageHelper.finishImageUrl(item.episode.imageUrlPrefix, ImageType.PRIMARY), + model = ImageUrlBuilder.finishImageUrl(item.episode.imageUrlPrefix, ArtworkKind.PRIMARY), contentDescription = item.primaryText, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt index 400d00aa..42a3665f 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.header.SectionHeader -import hu.bbara.purefin.feature.shared.home.NextUpItem +import hu.bbara.purefin.feature.browse.home.NextUpItem import java.util.UUID @Composable diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchOverlay.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchOverlay.kt index b8253fb7..85f91e36 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchOverlay.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchOverlay.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import hu.bbara.purefin.feature.shared.search.SearchViewModel +import hu.bbara.purefin.feature.search.SearchViewModel import java.util.UUID import hu.bbara.purefin.core.model.MediaKind diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchResultCard.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchResultCard.kt index 46a9e51e..d1128069 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchResultCard.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/HomeSearchResultCard.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import hu.bbara.purefin.core.model.MediaKind -import hu.bbara.purefin.feature.shared.search.SearchResult +import hu.bbara.purefin.feature.search.SearchResult import hu.bbara.purefin.ui.common.image.PurefinAsyncImage @Composable diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt index 7c8940bd..b686d826 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt @@ -23,9 +23,9 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.ui.common.card.PosterCard import hu.bbara.purefin.ui.common.button.PurefinIconButton -import hu.bbara.purefin.feature.shared.navigation.LibraryDto -import hu.bbara.purefin.feature.shared.home.PosterItem -import hu.bbara.purefin.feature.shared.library.LibraryViewModel +import hu.bbara.purefin.core.navigation.LibraryDto +import hu.bbara.purefin.feature.browse.home.PosterItem +import hu.bbara.purefin.feature.browse.library.LibraryViewModel @Composable fun LibraryScreen( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt index c72a81be..f9cc06fe 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt @@ -41,7 +41,7 @@ import hu.bbara.purefin.ui.common.textfield.PurefinComplexTextField import hu.bbara.purefin.ui.common.textfield.PurefinPasswordField import hu.bbara.purefin.ui.common.button.PurefinTextButton import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen -import hu.bbara.purefin.feature.shared.login.LoginViewModel +import hu.bbara.purefin.feature.login.LoginViewModel import kotlinx.coroutines.launch @Composable diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt index bceb6e10..17aa5e3b 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt @@ -35,16 +35,16 @@ import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.ui.common.media.MediaMetaChip import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.common.media.MediaHero -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.navigation.MovieDto +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.MovieDto import hu.bbara.purefin.core.model.CastMember import hu.bbara.purefin.core.model.Movie -import hu.bbara.purefin.feature.shared.content.movie.MovieScreenViewModel +import hu.bbara.purefin.feature.content.movie.MovieScreenViewModel import hu.bbara.purefin.ui.screen.movie.components.MovieDetails import hu.bbara.purefin.ui.screen.movie.components.MovieTopBar import hu.bbara.purefin.ui.theme.AppTheme -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import java.util.UUID @Composable @@ -139,7 +139,7 @@ fun MediaHeroSection( .height(sectionHeight) ) { MediaHero( - imageUrl = JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.PRIMARY), + imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY), backgroundColor = MaterialTheme.colorScheme.background, modifier = Modifier.fillMaxSize() ) diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt index c455aa59..14081212 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt @@ -35,7 +35,7 @@ import hu.bbara.purefin.ui.common.button.GhostIconButton import hu.bbara.purefin.ui.common.button.MediaActionButton import hu.bbara.purefin.ui.common.media.MediaPlaybackSettings import hu.bbara.purefin.ui.common.button.MediaResumeButton -import hu.bbara.purefin.core.data.download.DownloadState +import hu.bbara.purefin.core.download.DownloadState import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.player.PlayerActivity diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt index 936ad07f..ea2c971b 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt @@ -33,14 +33,14 @@ import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.ui.common.media.MediaSynopsis import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.common.media.MediaHero -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.navigation.SeriesDto +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.core.model.CastMember import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Season import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel +import hu.bbara.purefin.feature.content.series.SeriesViewModel import hu.bbara.purefin.ui.screen.series.components.CastRow import hu.bbara.purefin.ui.screen.series.components.EpisodeCarousel import hu.bbara.purefin.ui.screen.series.components.SeasonTabs @@ -49,7 +49,7 @@ import hu.bbara.purefin.ui.screen.series.components.SeriesDownloadOption import hu.bbara.purefin.ui.screen.series.components.SeriesMetaChips import hu.bbara.purefin.ui.screen.series.components.SeriesTopBar import hu.bbara.purefin.ui.theme.AppTheme -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import java.util.UUID @Composable @@ -207,7 +207,7 @@ private fun SeriesHeroSection( .height(sectionHeight) ) { MediaHero( - imageUrl = JellyfinImageHelper.finishImageUrl(series.imageUrlPrefix, ImageType.PRIMARY), + imageUrl = ImageUrlBuilder.finishImageUrl(series.imageUrlPrefix, ArtworkKind.PRIMARY), backgroundColor = scheme.background, modifier = Modifier.fillMaxSize() ) diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt index c1418033..a1780ba4 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt @@ -65,18 +65,18 @@ import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.button.MediaResumeButton import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.badge.WatchStateIndicator -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.feature.shared.navigation.EpisodeDto +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.navigation.EpisodeDto import hu.bbara.purefin.navigation.LocalNavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.model.CastMember import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Season import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel +import hu.bbara.purefin.feature.content.series.SeriesViewModel import hu.bbara.purefin.player.PlayerActivity -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind @Composable internal fun SeriesTopBar( @@ -349,7 +349,7 @@ private fun EpisodeCard( .border(1.dp, scheme.outlineVariant, RoundedCornerShape(12.dp)) ) { PurefinAsyncImage( - model = JellyfinImageHelper.finishImageUrl(episode.imageUrlPrefix, ImageType.PRIMARY), + model = ImageUrlBuilder.finishImageUrl(episode.imageUrlPrefix, ArtworkKind.PRIMARY), contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 92aec6a9..8b8e1687 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -3,9 +3,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.hilt) - alias(libs.plugins.ksp) } android { @@ -30,15 +27,7 @@ kotlin { dependencies { implementation(project(":core:model")) - implementation(libs.jellyfin.core) implementation(libs.media3.common) - implementation(libs.kotlinx.serialization.json) - implementation(libs.hilt) - ksp(libs.hilt.compiler) - implementation(libs.datastore) - implementation(libs.okhttp) - implementation(libs.logging.interceptor) - implementation(libs.androidx.room.ktx) - ksp(libs.androidx.room.compiler) + implementation(libs.kotlinx.coroutines.core) testImplementation(libs.junit) } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/AuthenticationRepository.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/AuthenticationRepository.kt new file mode 100644 index 00000000..9dde8ce7 --- /dev/null +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/AuthenticationRepository.kt @@ -0,0 +1,5 @@ +package hu.bbara.purefin.core.data + +interface AuthenticationRepository { + suspend fun login(url: String, username: String, password: String): Boolean +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/DownloadMediaSourceResolver.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/DownloadMediaSourceResolver.kt new file mode 100644 index 00000000..6ca7b60a --- /dev/null +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/DownloadMediaSourceResolver.kt @@ -0,0 +1,32 @@ +package hu.bbara.purefin.core.data + +import hu.bbara.purefin.core.model.Episode +import hu.bbara.purefin.core.model.Movie +import hu.bbara.purefin.core.model.Season +import hu.bbara.purefin.core.model.Series +import java.util.UUID + +interface DownloadMediaSourceResolver { + suspend fun resolveMovieDownload(movieId: UUID): MovieDownloadSource? + suspend fun resolveEpisodeDownload(episodeId: UUID): EpisodeDownloadSource? + suspend fun isEpisodeWatched(episodeId: UUID): Boolean + suspend fun getUnwatchedEpisodeIds( + seriesId: UUID, + excludedEpisodeIds: Set, + limit: Int, + ): List +} + +data class MovieDownloadSource( + val movie: Movie, + val playbackUrl: String, + val customCacheKey: String?, +) + +data class EpisodeDownloadSource( + val episode: Episode, + val series: Series, + val season: Season, + val playbackUrl: String, + val customCacheKey: String?, +) diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/NetworkModule.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/NetworkModule.kt deleted file mode 100644 index 9667e1fb..00000000 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/NetworkModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -package hu.bbara.purefin.core.data - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -abstract class NetworkModule { - - @Binds - @Singleton - abstract fun bindNetworkMonitor(impl: ConnectivityNetworkMonitor): NetworkMonitor -} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackMethod.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackMethod.kt new file mode 100644 index 00000000..b1e63175 --- /dev/null +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackMethod.kt @@ -0,0 +1,7 @@ +package hu.bbara.purefin.core.data + +enum class PlaybackMethod { + DIRECT_PLAY, + DIRECT_STREAM, + TRANSCODE, +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProfileFamily.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProfileFamily.kt new file mode 100644 index 00000000..6771b159 --- /dev/null +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProfileFamily.kt @@ -0,0 +1,6 @@ +package hu.bbara.purefin.core.data + +enum class PlaybackProfileFamily { + MOBILE, + TV, +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProgressReporter.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProgressReporter.kt index 886442de..b4d84c5b 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProgressReporter.kt +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackProgressReporter.kt @@ -1,6 +1,5 @@ package hu.bbara.purefin.core.data -import hu.bbara.purefin.core.data.client.PlaybackReportContext import java.util.UUID interface PlaybackProgressReporter { diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackReportContext.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackReportContext.kt new file mode 100644 index 00000000..8f5915cb --- /dev/null +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/PlaybackReportContext.kt @@ -0,0 +1,10 @@ +package hu.bbara.purefin.core.data + +data class PlaybackReportContext( + val playMethod: PlaybackMethod, + val mediaSourceId: String?, + val audioStreamIndex: Int?, + val subtitleStreamIndex: Int?, + val liveStreamId: String?, + val playSessionId: String?, +) diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/SessionBootstrapper.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/SessionBootstrapper.kt new file mode 100644 index 00000000..912af10c --- /dev/null +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/SessionBootstrapper.kt @@ -0,0 +1,5 @@ +package hu.bbara.purefin.core.data + +interface SessionBootstrapper { + suspend fun initialize() +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackDecision.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackDecision.kt deleted file mode 100644 index 1a7ddff4..00000000 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackDecision.kt +++ /dev/null @@ -1,19 +0,0 @@ -package hu.bbara.purefin.core.data.client - -import org.jellyfin.sdk.model.api.MediaSourceInfo -import org.jellyfin.sdk.model.api.PlayMethod - -data class PlaybackDecision( - val url: String, - val mediaSource: MediaSourceInfo, - val reportContext: PlaybackReportContext -) - -data class PlaybackReportContext( - val playMethod: PlayMethod, - val mediaSourceId: String?, - val audioStreamIndex: Int?, - val subtitleStreamIndex: Int?, - val liveStreamId: String?, - val playSessionId: String?, -) diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/domain/usecase/RefreshHomeDataUseCase.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/domain/usecase/RefreshHomeDataUseCase.kt deleted file mode 100644 index 6ff85a5a..00000000 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/domain/usecase/RefreshHomeDataUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package hu.bbara.purefin.core.data.domain.usecase - -import hu.bbara.purefin.core.data.HomeRepository -import javax.inject.Inject - -class RefreshHomeDataUseCase @Inject constructor( - private val repository: HomeRepository -) { - suspend operator fun invoke() { - repository.refreshHomeData() - } -} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/image/JellyfinImageHelper.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/image/JellyfinImageHelper.kt deleted file mode 100644 index 06ea34c8..00000000 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/image/JellyfinImageHelper.kt +++ /dev/null @@ -1,45 +0,0 @@ -package hu.bbara.purefin.core.data.image - -import java.util.UUID -import org.jellyfin.sdk.model.api.ImageType - -class JellyfinImageHelper { - companion object { - - fun toImageUrl(url: String, itemId: UUID, type: ImageType): String { - if (url.isEmpty()) { - return "" - } - return StringBuilder() - .append(url) - .append("/Items/") - .append(itemId) - .append("/Images/") - .append(type.serialName) - .toString() - } - - fun toPrefixImageUrl(url: String, itemId: UUID): String { - if (url.isEmpty()) { - return "" - } - return StringBuilder() - .append(url) - .append("/Items/") - .append(itemId) - .append("/Images/") - .toString() - } - - fun finishImageUrl(prefixImageUrl: String?, imageType: ImageType): String { - if (prefixImageUrl.isNullOrEmpty()) { - return "" - } - return StringBuilder() - .append(prefixImageUrl) - .append(imageType.serialName) - .toString() - - } - } -} \ No newline at end of file diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt deleted file mode 100644 index 13d324d7..00000000 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineMediaDatabase.kt +++ /dev/null @@ -1,36 +0,0 @@ -package hu.bbara.purefin.core.data.room.offline - -import androidx.room.Database -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import hu.bbara.purefin.core.data.room.UuidConverters -import hu.bbara.purefin.core.data.room.dao.EpisodeDao -import hu.bbara.purefin.core.data.room.dao.MovieDao -import hu.bbara.purefin.core.data.room.dao.SeasonDao -import hu.bbara.purefin.core.data.room.dao.SeriesDao -import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao -import hu.bbara.purefin.core.data.room.entity.EpisodeEntity -import hu.bbara.purefin.core.data.room.entity.MovieEntity -import hu.bbara.purefin.core.data.room.entity.SeasonEntity -import hu.bbara.purefin.core.data.room.entity.SeriesEntity -import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity - -@Database( - entities = [ - MovieEntity::class, - SeriesEntity::class, - SeasonEntity::class, - EpisodeEntity::class, - SmartDownloadEntity::class, - ], - version = 9, - exportSchema = false -) -@TypeConverters(UuidConverters::class) -abstract class OfflineMediaDatabase : RoomDatabase() { - abstract fun movieDao(): MovieDao - abstract fun seriesDao(): SeriesDao - abstract fun seasonDao(): SeasonDao - abstract fun episodeDao(): EpisodeDao - abstract fun smartDownloadDao(): SmartDownloadDao -} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionRepository.kt b/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionRepository.kt index 0f82d47f..cde057b8 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionRepository.kt +++ b/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionRepository.kt @@ -1,54 +1,21 @@ package hu.bbara.purefin.core.data.session -import androidx.datastore.core.DataStore -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import java.util.UUID -import javax.inject.Inject +import kotlinx.coroutines.flow.Flow -class UserSessionRepository @Inject constructor( - private val userSessionDataStore: DataStore -) { - val session: Flow = userSessionDataStore.data +interface UserSessionRepository { + val serverUrl: Flow + suspend fun setServerUrl(serverUrl: String) - val serverUrl: Flow = session - .map { it.url } + val accessToken: Flow + suspend fun setAccessToken(accessToken: String) - suspend fun setServerUrl(serverUrl: String) { - userSessionDataStore.updateData { - it.copy(url = serverUrl) - } - } + val userId: Flow + suspend fun setUserId(userId: UUID?) + suspend fun getUserId(): UUID? - val accessToken: Flow = session - .map { it.accessToken } + val isLoggedIn: Flow + suspend fun setLoggedIn(isLoggedIn: Boolean) - suspend fun setAccessToken(accessToken: String) { - userSessionDataStore.updateData { - it.copy(accessToken = accessToken) - } - } - - val userId: Flow = session - .map { it.userId } - - suspend fun setUserId(userId: UUID?) { - userSessionDataStore.updateData { - it.copy(userId = userId) - } - } - - suspend fun getUserId(): UUID? = userId.first() - - val isLoggedIn: Flow = session.map { it.loggedIn }.distinctUntilChanged() - - suspend fun setLoggedIn(isLoggedIn: Boolean) { - userSessionDataStore.updateData { - it.copy(loggedIn = isLoggedIn) - } - } - - val isOfflineMode: Flow = session.map { it.isOfflineMode }.distinctUntilChanged() + val isOfflineMode: Flow } diff --git a/feature/download/build.gradle.kts b/core/download/build.gradle.kts similarity index 89% rename from feature/download/build.gradle.kts rename to core/download/build.gradle.kts index 336e149d..39affc24 100644 --- a/feature/download/build.gradle.kts +++ b/core/download/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } android { - namespace = "hu.bbara.purefin.feature.download" + namespace = "hu.bbara.purefin.core.download" compileSdk = 36 defaultConfig { @@ -30,11 +30,11 @@ kotlin { dependencies { implementation(project(":core:model")) implementation(project(":core:data")) + implementation(libs.kotlinx.coroutines.core) implementation(libs.hilt) ksp(libs.hilt.compiler) implementation(libs.medi3.exoplayer) implementation(libs.media3.datasource.okhttp) implementation(libs.okhttp) - implementation(libs.jellyfin.core) testImplementation(libs.junit) } diff --git a/feature/download/src/main/java/hu/bbara/purefin/feature/download/DownloadControllerModule.kt b/core/download/src/main/java/hu/bbara/purefin/core/download/DownloadControllerModule.kt similarity index 77% rename from feature/download/src/main/java/hu/bbara/purefin/feature/download/DownloadControllerModule.kt rename to core/download/src/main/java/hu/bbara/purefin/core/download/DownloadControllerModule.kt index 40cf40a2..38f7550e 100644 --- a/feature/download/src/main/java/hu/bbara/purefin/feature/download/DownloadControllerModule.kt +++ b/core/download/src/main/java/hu/bbara/purefin/core/download/DownloadControllerModule.kt @@ -1,10 +1,10 @@ -package hu.bbara.purefin.feature.download +package hu.bbara.purefin.core.download import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import hu.bbara.purefin.core.data.download.MediaDownloadController +import hu.bbara.purefin.core.download.MediaDownloadController import javax.inject.Singleton @Module diff --git a/feature/download/src/main/java/hu/bbara/purefin/feature/download/DownloadModule.kt b/core/download/src/main/java/hu/bbara/purefin/core/download/DownloadModule.kt similarity index 98% rename from feature/download/src/main/java/hu/bbara/purefin/feature/download/DownloadModule.kt rename to core/download/src/main/java/hu/bbara/purefin/core/download/DownloadModule.kt index 4446b7ca..a28d7b16 100644 --- a/feature/download/src/main/java/hu/bbara/purefin/feature/download/DownloadModule.kt +++ b/core/download/src/main/java/hu/bbara/purefin/core/download/DownloadModule.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.download +package hu.bbara.purefin.core.download import android.content.Context import androidx.annotation.OptIn diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/download/DownloadState.kt b/core/download/src/main/java/hu/bbara/purefin/core/download/DownloadState.kt similarity index 84% rename from core/data/src/main/java/hu/bbara/purefin/core/data/download/DownloadState.kt rename to core/download/src/main/java/hu/bbara/purefin/core/download/DownloadState.kt index b63be137..7ee38b80 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/download/DownloadState.kt +++ b/core/download/src/main/java/hu/bbara/purefin/core/download/DownloadState.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.download +package hu.bbara.purefin.core.download sealed class DownloadState { data object NotDownloaded : DownloadState() diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/download/MediaDownloadController.kt b/core/download/src/main/java/hu/bbara/purefin/core/download/MediaDownloadController.kt similarity index 93% rename from core/data/src/main/java/hu/bbara/purefin/core/data/download/MediaDownloadController.kt rename to core/download/src/main/java/hu/bbara/purefin/core/download/MediaDownloadController.kt index 0e025c55..13aa3d9b 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/download/MediaDownloadController.kt +++ b/core/download/src/main/java/hu/bbara/purefin/core/download/MediaDownloadController.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.download +package hu.bbara.purefin.core.download import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow diff --git a/feature/download/src/main/java/hu/bbara/purefin/feature/download/MediaDownloadManager.kt b/core/download/src/main/java/hu/bbara/purefin/core/download/MediaDownloadManager.kt similarity index 54% rename from feature/download/src/main/java/hu/bbara/purefin/feature/download/MediaDownloadManager.kt rename to core/download/src/main/java/hu/bbara/purefin/core/download/MediaDownloadManager.kt index bfe82233..f4df08cd 100644 --- a/feature/download/src/main/java/hu/bbara/purefin/feature/download/MediaDownloadManager.kt +++ b/core/download/src/main/java/hu/bbara/purefin/core/download/MediaDownloadManager.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.download +package hu.bbara.purefin.core.download import android.content.Context import android.util.Log @@ -9,18 +9,9 @@ import androidx.media3.exoplayer.offline.Download import androidx.media3.exoplayer.offline.DownloadManager import androidx.media3.exoplayer.offline.DownloadRequest import dagger.hilt.android.qualifiers.ApplicationContext +import hu.bbara.purefin.core.data.DownloadMediaSourceResolver import hu.bbara.purefin.core.data.OfflineCatalogStore import hu.bbara.purefin.core.data.SmartDownloadStore -import hu.bbara.purefin.core.data.client.playbackCustomCacheKey -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.image.JellyfinImageHelper -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.download.MediaDownloadController -import hu.bbara.purefin.core.data.session.UserSessionRepository -import hu.bbara.purefin.core.model.Episode -import hu.bbara.purefin.core.model.Movie -import hu.bbara.purefin.core.model.Season -import hu.bbara.purefin.core.model.Series import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay @@ -34,10 +25,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.UUID -import org.jellyfin.sdk.model.api.BaseItemDto -import org.jellyfin.sdk.model.api.ImageType -import org.jellyfin.sdk.model.api.MediaSourceInfo -import org.jellyfin.sdk.model.api.PlayMethod import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton @@ -47,10 +34,9 @@ import javax.inject.Singleton class MediaDownloadManager @Inject constructor( @ApplicationContext private val context: Context, private val downloadManager: DownloadManager, - private val jellyfinApiClient: JellyfinApiClient, + private val downloadMediaSourceResolver: DownloadMediaSourceResolver, private val offlineCatalogStore: OfflineCatalogStore, private val smartDownloadStore: SmartDownloadStore, - private val userSessionRepository: UserSessionRepository, ) : MediaDownloadController { private val stateFlows = ConcurrentHashMap>() @@ -127,53 +113,19 @@ class MediaDownloadManager @Inject constructor( override suspend fun downloadMovie(movieId: UUID) { withContext(Dispatchers.IO) { try { - val sources = jellyfinApiClient.getMediaSources(movieId) - val source = sources.firstOrNull() ?: run { - Log.e(TAG, "No media sources for $movieId") - return@withContext - } - val shouldTranscode = source.shouldUseTranscodingUrl() - - val url = jellyfinApiClient.getMediaPlaybackUrl(movieId, source) ?: run { - Log.e(TAG, "No playback URL for $movieId") + val source = downloadMediaSourceResolver.resolveMovieDownload(movieId) ?: run { + Log.e(TAG, "No downloadable movie source for $movieId") return@withContext } - val itemInfo = jellyfinApiClient.getItemInfo(movieId) ?: run { - Log.e(TAG, "No item info for $movieId") - return@withContext - } + offlineCatalogStore.saveMovies(listOf(source.movie)) - val serverUrl = userSessionRepository.serverUrl.first().trim() - val movie = Movie( - id = itemInfo.id, - libraryId = itemInfo.parentId ?: UUID.randomUUID(), - title = itemInfo.name ?: "Unknown title", - progress = itemInfo.userData?.playedPercentage, - watched = itemInfo.userData?.played ?: false, - year = itemInfo.productionYear?.toString() - ?: itemInfo.premiereDate?.year?.toString().orEmpty(), - rating = itemInfo.officialRating ?: "NR", - runtime = formatRuntime(itemInfo.runTimeTicks), - synopsis = itemInfo.overview ?: "No synopsis available", - format = itemInfo.container?.uppercase() ?: "VIDEO", - imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl( - url = serverUrl, - itemId = itemInfo.id - ), - subtitles = "ENG", - audioTrack = "ENG", - cast = emptyList() - ) - - offlineCatalogStore.saveMovies(listOf(movie)) - - Log.d(TAG, "Starting download for '${movie.title}' from: $url") + Log.d(TAG, "Starting download for '${source.movie.title}' from: ${source.playbackUrl}") val request = buildDownloadRequest( mediaId = movieId, - playbackUrl = url, - title = movie.title, - isDirectPlay = !shouldTranscode + playbackUrl = source.playbackUrl, + title = source.movie.title, + customCacheKey = source.customCacheKey, ) PurefinDownloadService.sendAddDownload(context, request) Log.d(TAG, "Download request sent for $movieId") @@ -198,50 +150,27 @@ class MediaDownloadManager @Inject constructor( override suspend fun downloadEpisode(episodeId: UUID) { withContext(Dispatchers.IO) { try { - val serverUrl = userSessionRepository.serverUrl.first().trim() - val sources = jellyfinApiClient.getMediaSources(episodeId) - val source = sources.firstOrNull() ?: run { - Log.e(TAG, "No media sources for episode $episodeId") - return@withContext - } - val shouldTranscode = source.shouldUseTranscodingUrl() - - val url = jellyfinApiClient.getMediaPlaybackUrl(episodeId, source) ?: run { - Log.e(TAG, "No playback URL for episode $episodeId") + val source = downloadMediaSourceResolver.resolveEpisodeDownload(episodeId) ?: run { + Log.e(TAG, "No downloadable episode source for $episodeId") return@withContext } - val episode = jellyfinApiClient.getItemInfo(episodeId)?.toEpisode(serverUrl) ?: run { - Log.e(TAG, "Episode not found $episodeId") - return@withContext + if (offlineCatalogStore.getSeriesBasic(source.series.id) == null) { + offlineCatalogStore.saveSeries(listOf(source.series)) } - val series = jellyfinApiClient.getItemInfo(episode.seriesId)?.toSeries(serverUrl) ?: run { - Log.e(TAG, "Series not found ${episode.seriesId}") - return@withContext + if (offlineCatalogStore.getSeason(source.season.id) == null) { + offlineCatalogStore.saveSeason(source.season) } - val season = jellyfinApiClient.getItemInfo(episode.seasonId)?.toSeason(series.id) ?: run { - Log.e(TAG, "Season not found ${episode.seasonId}") - return@withContext - } + offlineCatalogStore.saveEpisode(source.episode) - if (offlineCatalogStore.getSeriesBasic(series.id) == null) { - offlineCatalogStore.saveSeries(listOf(series)) - } - - if (offlineCatalogStore.getSeason(season.id) == null) { - offlineCatalogStore.saveSeason(season) - } - - offlineCatalogStore.saveEpisode(episode) - - Log.d(TAG, "Starting download for episode '${episode.title}' from: $url") + Log.d(TAG, "Starting download for episode '${source.episode.title}' from: ${source.playbackUrl}") val request = buildDownloadRequest( mediaId = episodeId, - playbackUrl = url, - title = episode.title, - isDirectPlay = !shouldTranscode + playbackUrl = source.playbackUrl, + title = source.episode.title, + customCacheKey = source.customCacheKey, ) PurefinDownloadService.sendAddDownload(context, request) Log.d(TAG, "Download request sent for episode $episodeId") @@ -299,17 +228,13 @@ class MediaDownloadManager @Inject constructor( private suspend fun syncSmartDownloadsForSeries(seriesId: UUID) { withContext(Dispatchers.IO) { - val serverUrl = userSessionRepository.serverUrl.first().trim() - // 1. Get currently downloaded episodes for this series val downloadedEpisodes = offlineCatalogStore.getEpisodesBySeries(seriesId) // 2. Check watched status from server and delete watched downloads val unwatchedDownloaded = mutableListOf() for (episode in downloadedEpisodes) { - val itemInfo = jellyfinApiClient.getItemInfo(episode.id) - val isWatched = itemInfo?.userData?.played ?: false - if (isWatched) { + if (downloadMediaSourceResolver.isEpisodeWatched(episode.id)) { Log.d(TAG, "Smart download: removing watched episode ${episode.title}") cancelEpisodeDownload(episode.id) } else { @@ -317,22 +242,15 @@ class MediaDownloadManager @Inject constructor( } } - // 3. Get all episodes of the series from the server in order - val seasons = jellyfinApiClient.getSeasons(seriesId) - val allEpisodes = mutableListOf() - for (season in seasons) { - val episodes = jellyfinApiClient.getEpisodesInSeason(seriesId, season.id) - allEpisodes.addAll(episodes) - } - - // 4. Find unwatched episodes not already downloaded + // 3. Find unwatched episodes not already downloaded val needed = SMART_DOWNLOAD_COUNT - unwatchedDownloaded.size if (needed <= 0) return@withContext - val toDownload = allEpisodes - .filter { ep -> ep.userData?.played != true && ep.id !in unwatchedDownloaded } - .take(needed) - .map { it.id } + val toDownload = downloadMediaSourceResolver.getUnwatchedEpisodeIds( + seriesId = seriesId, + excludedEpisodeIds = unwatchedDownloaded.toSet(), + limit = needed, + ) if (toDownload.isNotEmpty()) { Log.d(TAG, "Smart download: queuing ${toDownload.size} episodes for series $seriesId") @@ -349,18 +267,12 @@ class MediaDownloadManager @Inject constructor( mediaId: UUID, playbackUrl: String, title: String, - isDirectPlay: Boolean + customCacheKey: String?, ): DownloadRequest { val builder = DownloadRequest.Builder(mediaId.toString(), playbackUrl.toUri()) .setData(title.toByteArray(Charsets.UTF_8)) - if (isDirectPlay) { - playbackCustomCacheKey( - mediaId = mediaId.toString(), - playbackUrl = playbackUrl, - playMethod = PlayMethod.DIRECT_PLAY - )?.let(builder::setCustomCacheKey) - } + customCacheKey?.let(builder::setCustomCacheKey) return builder.build() } @@ -375,73 +287,8 @@ class MediaDownloadManager @Inject constructor( else -> DownloadState.NotDownloaded } - private fun formatRuntime(ticks: Long?): String { - if (ticks == null || ticks <= 0) return "—" - val totalSeconds = ticks / 10_000_000 - val hours = java.util.concurrent.TimeUnit.SECONDS.toHours(totalSeconds) - val minutes = java.util.concurrent.TimeUnit.SECONDS.toMinutes(totalSeconds) % 60 - return if (hours > 0) "${hours}h ${minutes}m" else "${minutes}m" - } - - private fun BaseItemDto.toEpisode(serverUrl: String): Episode { - return Episode( - id = id, - seriesId = seriesId!!, - seasonId = parentId!!, - title = name ?: "Unknown title", - index = indexNumber ?: 0, - releaseDate = productionYear?.toString() ?: "—", - rating = officialRating ?: "NR", - runtime = formatRuntime(runTimeTicks), - progress = userData?.playedPercentage, - watched = userData?.played ?: false, - format = container?.uppercase() ?: "VIDEO", - synopsis = overview ?: "No synopsis available.", - imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl( - url = serverUrl, - itemId = id - ), - cast = emptyList() - ) - } - - private fun BaseItemDto.toSeries(serverUrl: String): Series { - return Series( - id = id, - libraryId = parentId ?: java.util.UUID.randomUUID(), - name = name ?: "Unknown", - synopsis = overview ?: "No synopsis available", - year = productionYear?.toString() ?: premiereDate?.year?.toString().orEmpty(), - imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl( - url = serverUrl, - itemId = id - ), - unwatchedEpisodeCount = userData?.unplayedItemCount ?: 0, - seasonCount = childCount ?: 0, - seasons = emptyList(), - cast = emptyList() - ) - } - - private fun BaseItemDto.toSeason(seriesId: UUID): Season { - return Season( - id = id, - seriesId = this.seriesId ?: seriesId, - name = name ?: "Unknown", - index = indexNumber ?: 0, - unwatchedEpisodeCount = userData?.unplayedItemCount ?: 0, - episodeCount = childCount ?: 0, - episodes = emptyList() - ) - } - companion object { private const val TAG = "MediaDownloadManager" private const val SMART_DOWNLOAD_COUNT = 5 } } - -private fun MediaSourceInfo.shouldUseTranscodingUrl(): Boolean { - return supportsTranscoding == true && - (supportsDirectPlay == false || transcodingUrl != null) -} diff --git a/feature/download/src/main/java/hu/bbara/purefin/feature/download/PurefinDownloadService.kt b/core/download/src/main/java/hu/bbara/purefin/core/download/PurefinDownloadService.kt similarity index 96% rename from feature/download/src/main/java/hu/bbara/purefin/feature/download/PurefinDownloadService.kt rename to core/download/src/main/java/hu/bbara/purefin/core/download/PurefinDownloadService.kt index 7018cd2f..5ab3549b 100644 --- a/feature/download/src/main/java/hu/bbara/purefin/feature/download/PurefinDownloadService.kt +++ b/core/download/src/main/java/hu/bbara/purefin/core/download/PurefinDownloadService.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.download +package hu.bbara.purefin.core.download import android.app.Notification import android.content.Context @@ -50,7 +50,7 @@ class PurefinDownloadService : DownloadService( if (activeDownloads.isEmpty()) { return entryPoint.downloadNotificationHelper().buildProgressNotification( this, - R.drawable.ic_launcher_foreground, + android.R.drawable.stat_sys_download, null, null, downloads, @@ -95,7 +95,7 @@ class PurefinDownloadService : DownloadService( } return NotificationCompat.Builder(this, DOWNLOAD_CHANNEL_ID) - .setSmallIcon(R.drawable.ic_launcher_foreground) + .setSmallIcon(android.R.drawable.stat_sys_download) .setContentTitle(title) .setContentText(contentText) .setProgress(100, percent.toInt(), false) diff --git a/feature/download/src/main/res/values/strings.xml b/core/download/src/main/res/values/strings.xml similarity index 59% rename from feature/download/src/main/res/values/strings.xml rename to core/download/src/main/res/values/strings.xml index 627b05b3..0ab16b73 100644 --- a/feature/download/src/main/res/values/strings.xml +++ b/core/download/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ Downloads - Media download progress + Purefin offline downloads diff --git a/feature/download/src/test/java/hu/bbara/purefin/feature/download/DownloadModuleTest.kt b/core/download/src/test/java/hu/bbara/purefin/core/download/DownloadModuleTest.kt similarity index 97% rename from feature/download/src/test/java/hu/bbara/purefin/feature/download/DownloadModuleTest.kt rename to core/download/src/test/java/hu/bbara/purefin/core/download/DownloadModuleTest.kt index c602f6be..d5602423 100644 --- a/feature/download/src/test/java/hu/bbara/purefin/feature/download/DownloadModuleTest.kt +++ b/core/download/src/test/java/hu/bbara/purefin/core/download/DownloadModuleTest.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.download +package hu.bbara.purefin.core.download import androidx.media3.datasource.DataSource import androidx.media3.datasource.cache.CacheDataSource diff --git a/core/image/build.gradle.kts b/core/image/build.gradle.kts new file mode 100644 index 00000000..ee574787 --- /dev/null +++ b/core/image/build.gradle.kts @@ -0,0 +1,16 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlin.jvm) +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + jvmToolchain(11) +} + +dependencies { + implementation(project(":core:model")) +} diff --git a/core/image/src/main/java/hu/bbara/purefin/core/image/ArtworkKind.kt b/core/image/src/main/java/hu/bbara/purefin/core/image/ArtworkKind.kt new file mode 100644 index 00000000..f35f6cd3 --- /dev/null +++ b/core/image/src/main/java/hu/bbara/purefin/core/image/ArtworkKind.kt @@ -0,0 +1,8 @@ +package hu.bbara.purefin.core.image + +enum class ArtworkKind( + internal val pathSegment: String, +) { + PRIMARY("Primary"), + BACKDROP("Backdrop"), +} diff --git a/core/image/src/main/java/hu/bbara/purefin/core/image/ImageUrlBuilder.kt b/core/image/src/main/java/hu/bbara/purefin/core/image/ImageUrlBuilder.kt new file mode 100644 index 00000000..be7a8446 --- /dev/null +++ b/core/image/src/main/java/hu/bbara/purefin/core/image/ImageUrlBuilder.kt @@ -0,0 +1,40 @@ +package hu.bbara.purefin.core.image + +import java.util.UUID + +object ImageUrlBuilder { + fun toImageUrl(url: String, itemId: UUID, artworkKind: ArtworkKind): String { + if (url.isEmpty()) { + return "" + } + return StringBuilder() + .append(url) + .append("/Items/") + .append(itemId) + .append("/Images/") + .append(artworkKind.pathSegment) + .toString() + } + + fun toPrefixImageUrl(url: String, itemId: UUID): String { + if (url.isEmpty()) { + return "" + } + return StringBuilder() + .append(url) + .append("/Items/") + .append(itemId) + .append("/Images/") + .toString() + } + + fun finishImageUrl(prefixImageUrl: String?, artworkKind: ArtworkKind): String { + if (prefixImageUrl.isNullOrEmpty()) { + return "" + } + return StringBuilder() + .append(prefixImageUrl) + .append(artworkKind.pathSegment) + .toString() + } +} diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts new file mode 100644 index 00000000..b42362cd --- /dev/null +++ b/core/navigation/build.gradle.kts @@ -0,0 +1,38 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.core.navigation" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(libs.androidx.navigation3.runtime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + testImplementation(libs.junit) +} diff --git a/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/EpisodeDto.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/EpisodeDto.kt new file mode 100644 index 00000000..5fc6a985 --- /dev/null +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/EpisodeDto.kt @@ -0,0 +1,15 @@ +package hu.bbara.purefin.core.navigation + +import java.util.UUID +import kotlinx.serialization.Serializable +import hu.bbara.purefin.core.navigation.UuidSerializer + +@Serializable +data class EpisodeDto( + @Serializable(with = UuidSerializer::class) + val id: UUID, + @Serializable(with = UuidSerializer::class) + val seasonId: UUID, + @Serializable(with = UuidSerializer::class) + val seriesId: UUID, +) diff --git a/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/LibraryDto.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/LibraryDto.kt new file mode 100644 index 00000000..9ea517c2 --- /dev/null +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/LibraryDto.kt @@ -0,0 +1,12 @@ +package hu.bbara.purefin.core.navigation + +import java.util.UUID +import kotlinx.serialization.Serializable +import hu.bbara.purefin.core.navigation.UuidSerializer + +@Serializable +data class LibraryDto( + @Serializable(with = UuidSerializer::class) + val id: UUID, + val name: String, +) diff --git a/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/MovieDto.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/MovieDto.kt new file mode 100644 index 00000000..03b7a5f5 --- /dev/null +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/MovieDto.kt @@ -0,0 +1,11 @@ +package hu.bbara.purefin.core.navigation + +import java.util.UUID +import kotlinx.serialization.Serializable +import hu.bbara.purefin.core.navigation.UuidSerializer + +@Serializable +data class MovieDto( + @Serializable(with = UuidSerializer::class) + val id: UUID, +) diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/NavigationManager.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/NavigationManager.kt similarity index 95% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/NavigationManager.kt rename to core/navigation/src/main/java/hu/bbara/purefin/core/navigation/NavigationManager.kt index 17ac3c0d..2d2a3ed8 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/NavigationManager.kt +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/NavigationManager.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.navigation +package hu.bbara.purefin.core.navigation import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/NavigationManagerModule.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/NavigationManagerModule.kt similarity index 87% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/NavigationManagerModule.kt rename to core/navigation/src/main/java/hu/bbara/purefin/core/navigation/NavigationManagerModule.kt index 2ac0bd52..e7503ad9 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/NavigationManagerModule.kt +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/NavigationManagerModule.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.navigation +package hu.bbara.purefin.core.navigation import dagger.Module import dagger.Provides diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/Route.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/Route.kt similarity index 92% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/Route.kt rename to core/navigation/src/main/java/hu/bbara/purefin/core/navigation/Route.kt index 16e13123..9ede65df 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/Route.kt +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/Route.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.navigation +package hu.bbara.purefin.core.navigation import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable diff --git a/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/SeriesDto.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/SeriesDto.kt new file mode 100644 index 00000000..a2cffdb5 --- /dev/null +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/SeriesDto.kt @@ -0,0 +1,11 @@ +package hu.bbara.purefin.core.navigation + +import java.util.UUID +import kotlinx.serialization.Serializable +import hu.bbara.purefin.core.navigation.UuidSerializer + +@Serializable +data class SeriesDto( + @Serializable(with = UuidSerializer::class) + val id: UUID, +) diff --git a/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/UuidSerializer.kt b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/UuidSerializer.kt new file mode 100644 index 00000000..18a2073d --- /dev/null +++ b/core/navigation/src/main/java/hu/bbara/purefin/core/navigation/UuidSerializer.kt @@ -0,0 +1,20 @@ +package hu.bbara.purefin.core.navigation + +import java.util.UUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object UuidSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: UUID) { + encoder.encodeString(value.toString()) + } +} diff --git a/feature/shared/src/test/java/hu/bbara/purefin/feature/shared/navigation/RouteSerializationTest.kt b/core/navigation/src/test/java/hu/bbara/purefin/core/navigation/RouteSerializationTest.kt similarity index 96% rename from feature/shared/src/test/java/hu/bbara/purefin/feature/shared/navigation/RouteSerializationTest.kt rename to core/navigation/src/test/java/hu/bbara/purefin/core/navigation/RouteSerializationTest.kt index 0f8028c7..0d4759aa 100644 --- a/feature/shared/src/test/java/hu/bbara/purefin/feature/shared/navigation/RouteSerializationTest.kt +++ b/core/navigation/src/test/java/hu/bbara/purefin/core/navigation/RouteSerializationTest.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.navigation +package hu.bbara.purefin.core.navigation import java.util.UUID import kotlinx.serialization.decodeFromString diff --git a/core/player/build.gradle.kts b/core/player/build.gradle.kts index 29dd5d93..28a8e216 100644 --- a/core/player/build.gradle.kts +++ b/core/player/build.gradle.kts @@ -31,6 +31,7 @@ kotlin { dependencies { implementation(project(":core:model")) implementation(project(":core:data")) + implementation(project(":core:image")) implementation(libs.hilt) ksp(libs.hilt.compiler) implementation(libs.androidx.lifecycle.viewmodel.ktx) diff --git a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt index caa6c58e..740aaf21 100644 --- a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt +++ b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/PlayerManager.kt @@ -9,7 +9,7 @@ import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.Tracks import androidx.media3.common.util.UnstableApi import dagger.hilt.android.scopes.ViewModelScoped -import hu.bbara.purefin.core.data.client.PlaybackReportContext +import hu.bbara.purefin.core.data.PlaybackReportContext import hu.bbara.purefin.core.player.model.QueueItemUi import hu.bbara.purefin.core.player.model.TrackOption import hu.bbara.purefin.core.player.model.TrackType diff --git a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt index a157123e..bc4ad425 100644 --- a/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt +++ b/core/player/src/main/java/hu/bbara/purefin/core/player/manager/ProgressManager.kt @@ -4,7 +4,7 @@ import android.util.Log import dagger.hilt.android.scopes.ViewModelScoped import hu.bbara.purefin.core.data.MediaProgressWriter import hu.bbara.purefin.core.data.PlaybackProgressReporter -import hu.bbara.purefin.core.data.client.PlaybackReportContext +import hu.bbara.purefin.core.data.PlaybackReportContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/data/catalog/build.gradle.kts b/data/catalog/build.gradle.kts new file mode 100644 index 00000000..d253d17f --- /dev/null +++ b/data/catalog/build.gradle.kts @@ -0,0 +1,41 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.data.catalog" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:model")) + implementation(project(":core:data")) + implementation(project(":core:image")) + implementation(project(":data:jellyfin")) + implementation(project(":data:offline")) + implementation(libs.jellyfin.core) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.datastore) + testImplementation(libs.junit) +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/CompositeMediaRepository.kt b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/CompositeMediaRepository.kt similarity index 91% rename from core/data/src/main/java/hu/bbara/purefin/core/data/CompositeMediaRepository.kt rename to data/catalog/src/main/java/hu/bbara/purefin/data/catalog/CompositeMediaRepository.kt index fba74caa..b47e272b 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/CompositeMediaRepository.kt +++ b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/CompositeMediaRepository.kt @@ -1,5 +1,8 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog +import hu.bbara.purefin.core.data.MediaCatalogReader +import hu.bbara.purefin.core.data.MediaProgressWriter +import hu.bbara.purefin.core.data.NetworkMonitor import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Series diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/DefaultEpisodeSeriesLookup.kt b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/DefaultEpisodeSeriesLookup.kt similarity index 73% rename from core/data/src/main/java/hu/bbara/purefin/core/data/DefaultEpisodeSeriesLookup.kt rename to data/catalog/src/main/java/hu/bbara/purefin/data/catalog/DefaultEpisodeSeriesLookup.kt index cc30b919..93b61fc0 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/DefaultEpisodeSeriesLookup.kt +++ b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/DefaultEpisodeSeriesLookup.kt @@ -1,5 +1,7 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog +import hu.bbara.purefin.core.data.EpisodeSeriesLookup +import hu.bbara.purefin.core.data.MediaCatalogReader import java.util.UUID import javax.inject.Inject import javax.inject.Singleton diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/InMemoryAppContentRepository.kt b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/InMemoryAppContentRepository.kt similarity index 94% rename from core/data/src/main/java/hu/bbara/purefin/core/data/InMemoryAppContentRepository.kt rename to data/catalog/src/main/java/hu/bbara/purefin/data/catalog/InMemoryAppContentRepository.kt index 00872bf3..177923e7 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/InMemoryAppContentRepository.kt +++ b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/InMemoryAppContentRepository.kt @@ -1,11 +1,14 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog import android.util.Log import androidx.datastore.core.DataStore -import hu.bbara.purefin.core.data.cache.CachedMediaItem -import hu.bbara.purefin.core.data.cache.HomeCache -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.data.HomeRepository +import hu.bbara.purefin.core.data.NetworkMonitor +import hu.bbara.purefin.core.image.ArtworkKind +import hu.bbara.purefin.data.offline.cache.CachedMediaItem +import hu.bbara.purefin.data.offline.cache.HomeCache +import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.data.session.UserSessionRepository import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Library @@ -35,7 +38,6 @@ import kotlinx.coroutines.sync.withLock import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.CollectionType -import org.jellyfin.sdk.model.api.ImageType @Singleton class InMemoryAppContentRepository @Inject constructor( @@ -313,14 +315,14 @@ class InMemoryAppContentRepository @Inject constructor( CollectionType.MOVIES -> Library( id = id, name = name!!, - posterUrl = JellyfinImageHelper.toImageUrl(url = serverUrl, itemId = id, type = ImageType.PRIMARY), + posterUrl = ImageUrlBuilder.toImageUrl(url = serverUrl, itemId = id, artworkKind = ArtworkKind.PRIMARY), type = LibraryKind.MOVIES, movies = emptyList(), ) CollectionType.TVSHOWS -> Library( id = id, name = name!!, - posterUrl = JellyfinImageHelper.toImageUrl(url = serverUrl, itemId = id, type = ImageType.PRIMARY), + posterUrl = ImageUrlBuilder.toImageUrl(url = serverUrl, itemId = id, artworkKind = ArtworkKind.PRIMARY), type = LibraryKind.SERIES, series = emptyList(), ) @@ -340,7 +342,7 @@ class InMemoryAppContentRepository @Inject constructor( runtime = formatRuntime(runTimeTicks), synopsis = overview ?: "No synopsis available", format = container?.uppercase() ?: "VIDEO", - imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl(url = serverUrl, itemId = id), + imageUrlPrefix = ImageUrlBuilder.toPrefixImageUrl(url = serverUrl, itemId = id), subtitles = "ENG", audioTrack = "ENG", cast = emptyList(), @@ -354,7 +356,7 @@ class InMemoryAppContentRepository @Inject constructor( name = name ?: "Unknown", synopsis = overview ?: "No synopsis available", year = productionYear?.toString() ?: premiereDate?.year?.toString().orEmpty(), - imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl(url = serverUrl, itemId = id), + imageUrlPrefix = ImageUrlBuilder.toPrefixImageUrl(url = serverUrl, itemId = id), unwatchedEpisodeCount = userData!!.unplayedItemCount!!, seasonCount = childCount!!, seasons = emptyList(), @@ -377,7 +379,7 @@ class InMemoryAppContentRepository @Inject constructor( private fun BaseItemDto.toEpisode(serverUrl: String): Episode { val releaseDate = formatReleaseDate(premiereDate, productionYear) val imageUrlPrefix = id?.let { itemId -> - JellyfinImageHelper.toPrefixImageUrl(url = serverUrl, itemId = itemId) + ImageUrlBuilder.toPrefixImageUrl(url = serverUrl, itemId = itemId) } ?: "" return Episode( id = id, diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/InMemoryMediaRepository.kt b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt similarity index 94% rename from core/data/src/main/java/hu/bbara/purefin/core/data/InMemoryMediaRepository.kt rename to data/catalog/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt index b23b8733..af5b18c2 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/InMemoryMediaRepository.kt +++ b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/InMemoryMediaRepository.kt @@ -1,7 +1,9 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.data.MediaCatalogReader +import hu.bbara.purefin.core.data.MediaProgressWriter +import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.data.session.UserSessionRepository import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie @@ -117,7 +119,7 @@ class InMemoryMediaRepository @Inject constructor( private fun BaseItemDto.toEpisode(serverUrl: String): Episode { val releaseDate = formatReleaseDate(premiereDate, productionYear) val imageUrlPrefix = id?.let { itemId -> - JellyfinImageHelper.toPrefixImageUrl(url = serverUrl, itemId = itemId) + ImageUrlBuilder.toPrefixImageUrl(url = serverUrl, itemId = itemId) } ?: "" return Episode( id = id, diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/MediaRepositoryModule.kt b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/MediaRepositoryModule.kt similarity index 60% rename from core/data/src/main/java/hu/bbara/purefin/core/data/MediaRepositoryModule.kt rename to data/catalog/src/main/java/hu/bbara/purefin/data/catalog/MediaRepositoryModule.kt index 495494e4..99d64ea5 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/MediaRepositoryModule.kt +++ b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/MediaRepositoryModule.kt @@ -1,9 +1,14 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import hu.bbara.purefin.core.data.EpisodeSeriesLookup +import hu.bbara.purefin.core.data.HomeRepository +import hu.bbara.purefin.core.data.MediaCatalogReader +import hu.bbara.purefin.core.data.MediaProgressWriter +import hu.bbara.purefin.core.data.OfflineCatalogReader @Module @InstallIn(SingletonComponent::class) @@ -21,19 +26,6 @@ abstract class MediaRepositoryModule { @Binds abstract fun bindOfflineCatalogReader(impl: OfflineMediaRepository): OfflineCatalogReader - @Binds - abstract fun bindOfflineCatalogStore(impl: RoomOfflineCatalogStore): OfflineCatalogStore - - @Binds - abstract fun bindSmartDownloadStore(impl: RoomSmartDownloadStore): SmartDownloadStore - - @Binds - abstract fun bindPlayableMediaRepository(impl: DefaultPlayableMediaRepository): PlayableMediaRepository - @Binds abstract fun bindEpisodeSeriesLookup(impl: DefaultEpisodeSeriesLookup): EpisodeSeriesLookup - - @Binds - abstract fun bindPlaybackProgressReporter(impl: JellyfinPlaybackProgressReporter): PlaybackProgressReporter - } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/OfflineMediaRepository.kt b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/OfflineMediaRepository.kt similarity index 85% rename from core/data/src/main/java/hu/bbara/purefin/core/data/OfflineMediaRepository.kt rename to data/catalog/src/main/java/hu/bbara/purefin/data/catalog/OfflineMediaRepository.kt index c0b956ce..f9fc93e4 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/OfflineMediaRepository.kt +++ b/data/catalog/src/main/java/hu/bbara/purefin/data/catalog/OfflineMediaRepository.kt @@ -1,6 +1,9 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog -import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource +import hu.bbara.purefin.core.data.MediaCatalogReader +import hu.bbara.purefin.core.data.MediaProgressWriter +import hu.bbara.purefin.core.data.OfflineCatalogReader +import hu.bbara.purefin.data.offline.room.offline.OfflineRoomMediaLocalDataSource import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Series diff --git a/core/data/src/test/java/hu/bbara/purefin/core/data/DefaultEpisodeSeriesLookupTest.kt b/data/catalog/src/test/java/hu/bbara/purefin/data/catalog/DefaultEpisodeSeriesLookupTest.kt similarity index 96% rename from core/data/src/test/java/hu/bbara/purefin/core/data/DefaultEpisodeSeriesLookupTest.kt rename to data/catalog/src/test/java/hu/bbara/purefin/data/catalog/DefaultEpisodeSeriesLookupTest.kt index df92d1c0..800f4cac 100644 --- a/core/data/src/test/java/hu/bbara/purefin/core/data/DefaultEpisodeSeriesLookupTest.kt +++ b/data/catalog/src/test/java/hu/bbara/purefin/data/catalog/DefaultEpisodeSeriesLookupTest.kt @@ -1,5 +1,6 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.catalog +import hu.bbara.purefin.core.data.MediaCatalogReader import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Series diff --git a/data/jellyfin/build.gradle.kts b/data/jellyfin/build.gradle.kts new file mode 100644 index 00000000..4819e491 --- /dev/null +++ b/data/jellyfin/build.gradle.kts @@ -0,0 +1,45 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.data.jellyfin" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:model")) + implementation(project(":core:data")) + implementation(project(":core:image")) + implementation(project(":core:navigation")) + implementation(libs.jellyfin.core) + implementation(libs.media3.common) + implementation(libs.kotlinx.serialization.json) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.datastore) + implementation(libs.okhttp) + implementation(libs.logging.interceptor) + testImplementation(libs.junit) +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/ConnectivityNetworkMonitor.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/ConnectivityNetworkMonitor.kt similarity index 95% rename from core/data/src/main/java/hu/bbara/purefin/core/data/ConnectivityNetworkMonitor.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/ConnectivityNetworkMonitor.kt index 7aaa894f..5a954123 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/ConnectivityNetworkMonitor.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/ConnectivityNetworkMonitor.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.jellyfin import android.content.Context import android.net.ConnectivityManager @@ -6,6 +6,7 @@ import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest import dagger.hilt.android.qualifiers.ApplicationContext +import hu.bbara.purefin.core.data.NetworkMonitor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/DefaultPlayableMediaRepository.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/DefaultPlayableMediaRepository.kt similarity index 88% rename from core/data/src/main/java/hu/bbara/purefin/core/data/DefaultPlayableMediaRepository.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/DefaultPlayableMediaRepository.kt index 54ffccb9..656ae845 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/DefaultPlayableMediaRepository.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/DefaultPlayableMediaRepository.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.jellyfin import android.util.Log import androidx.annotation.OptIn @@ -6,20 +6,21 @@ import androidx.core.net.toUri import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.util.UnstableApi -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.client.PlaybackDecision -import hu.bbara.purefin.core.data.client.PlaybackReportContext -import hu.bbara.purefin.core.data.client.playbackCustomCacheKey -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.data.PlayableMediaRepository +import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient +import hu.bbara.purefin.data.jellyfin.client.PlaybackDecision +import hu.bbara.purefin.core.data.PlaybackReportContext +import hu.bbara.purefin.data.jellyfin.client.playbackCustomCacheKey import hu.bbara.purefin.core.data.session.UserSessionRepository +import hu.bbara.purefin.core.image.ImageUrlBuilder import java.util.UUID import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import hu.bbara.purefin.core.image.ArtworkKind import org.jellyfin.sdk.model.api.BaseItemDto -import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.api.MediaSourceInfo @Singleton @@ -35,7 +36,7 @@ class DefaultPlayableMediaRepository @Inject constructor( val resumePositionMs = calculateResumePosition(baseItem, playbackDecision.mediaSource) val serverUrl = userSessionRepository.serverUrl.first() - val artworkUrl = JellyfinImageHelper.toImageUrl(serverUrl, mediaId, ImageType.PRIMARY) + val artworkUrl = ImageUrlBuilder.toImageUrl(serverUrl, mediaId, ArtworkKind.PRIMARY) val mediaItem = createMediaItem( mediaId = mediaId.toString(), @@ -64,7 +65,7 @@ class DefaultPlayableMediaRepository @Inject constructor( return@mapNotNull null } val playbackDecision = jellyfinApiClient.getPlaybackDecision(id) ?: return@mapNotNull null - val artworkUrl = JellyfinImageHelper.toImageUrl(serverUrl, id, ImageType.PRIMARY) + val artworkUrl = ImageUrlBuilder.toImageUrl(serverUrl, id, ArtworkKind.PRIMARY) createMediaItem( mediaId = stringId, playbackDecision = playbackDecision, diff --git a/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinBindingsModule.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinBindingsModule.kt new file mode 100644 index 00000000..c80bf30b --- /dev/null +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinBindingsModule.kt @@ -0,0 +1,41 @@ +package hu.bbara.purefin.data.jellyfin + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import hu.bbara.purefin.core.data.AuthenticationRepository +import hu.bbara.purefin.core.data.DownloadMediaSourceResolver +import hu.bbara.purefin.core.data.NetworkMonitor +import hu.bbara.purefin.core.data.PlayableMediaRepository +import hu.bbara.purefin.core.data.PlaybackProgressReporter +import hu.bbara.purefin.core.data.SessionBootstrapper +import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class JellyfinBindingsModule { + + @Binds + @Singleton + abstract fun bindSessionBootstrapper(impl: JellyfinApiClient): SessionBootstrapper + + @Binds + @Singleton + abstract fun bindAuthenticationRepository(impl: JellyfinApiClient): AuthenticationRepository + + @Binds + @Singleton + abstract fun bindDownloadMediaSourceResolver(impl: JellyfinApiClient): DownloadMediaSourceResolver + + @Binds + abstract fun bindPlayableMediaRepository(impl: DefaultPlayableMediaRepository): PlayableMediaRepository + + @Binds + abstract fun bindPlaybackProgressReporter(impl: JellyfinPlaybackProgressReporter): PlaybackProgressReporter + + @Binds + @Singleton + abstract fun bindNetworkMonitor(impl: ConnectivityNetworkMonitor): NetworkMonitor +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/image/ImageModule.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinNetworkModule.kt similarity index 76% rename from core/data/src/main/java/hu/bbara/purefin/core/data/image/ImageModule.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinNetworkModule.kt index 9b132cb9..710e2a3a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/image/ImageModule.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinNetworkModule.kt @@ -1,16 +1,16 @@ -package hu.bbara.purefin.core.data.image +package hu.bbara.purefin.data.jellyfin import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import hu.bbara.purefin.core.data.client.JellyfinAuthInterceptor +import hu.bbara.purefin.data.jellyfin.client.JellyfinAuthInterceptor import okhttp3.OkHttpClient import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object ImageModule { +object JellyfinNetworkModule { @Provides @Singleton @@ -19,4 +19,4 @@ object ImageModule { .addInterceptor(authInterceptor) .build() } -} \ No newline at end of file +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/JellyfinPlaybackProgressReporter.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinPlaybackProgressReporter.kt similarity index 81% rename from core/data/src/main/java/hu/bbara/purefin/core/data/JellyfinPlaybackProgressReporter.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinPlaybackProgressReporter.kt index 1518330a..d1520e86 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/JellyfinPlaybackProgressReporter.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/JellyfinPlaybackProgressReporter.kt @@ -1,7 +1,8 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.jellyfin -import hu.bbara.purefin.core.data.client.JellyfinApiClient -import hu.bbara.purefin.core.data.client.PlaybackReportContext +import hu.bbara.purefin.core.data.PlaybackProgressReporter +import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient +import hu.bbara.purefin.core.data.PlaybackReportContext import java.util.UUID import javax.inject.Inject import javax.inject.Singleton diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/AndroidCodecCatalog.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/AndroidCodecCatalog.kt similarity index 99% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/AndroidCodecCatalog.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/AndroidCodecCatalog.kt index 5987cae0..5d49d2fb 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/AndroidCodecCatalog.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/AndroidCodecCatalog.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import android.media.MediaCodecInfo import android.media.MediaCodecInfo.CodecProfileLevel diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/AndroidDeviceProfileCapabilities.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/AndroidDeviceProfileCapabilities.kt similarity index 99% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/AndroidDeviceProfileCapabilities.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/AndroidDeviceProfileCapabilities.kt index 2fcdff14..f9d14ff7 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/AndroidDeviceProfileCapabilities.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/AndroidDeviceProfileCapabilities.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import android.media.MediaCodecInfo.CodecProfileLevel import android.media.MediaCodecList diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/CodecDebugHelper.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/CodecDebugHelper.kt similarity index 97% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/CodecDebugHelper.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/CodecDebugHelper.kt index b12e9b3d..959b665a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/CodecDebugHelper.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/CodecDebugHelper.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import android.media.MediaCodecList import android.util.Log diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAndroidMobileDeviceProfile.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAndroidMobileDeviceProfile.kt similarity index 99% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAndroidMobileDeviceProfile.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAndroidMobileDeviceProfile.kt index 55a315f5..8ff3483a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAndroidMobileDeviceProfile.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAndroidMobileDeviceProfile.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import org.jellyfin.sdk.model.api.CodecProfile import org.jellyfin.sdk.model.api.CodecType diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAndroidTvDeviceProfile.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAndroidTvDeviceProfile.kt similarity index 99% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAndroidTvDeviceProfile.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAndroidTvDeviceProfile.kt index fd7198e0..4c17c4f7 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAndroidTvDeviceProfile.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAndroidTvDeviceProfile.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import androidx.media3.common.MimeTypes import org.jellyfin.sdk.model.ServerVersion diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinApiClient.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinApiClient.kt similarity index 69% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinApiClient.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinApiClient.kt index e60dc466..38ce420b 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinApiClient.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinApiClient.kt @@ -1,9 +1,22 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import android.content.Context import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext +import hu.bbara.purefin.core.data.AuthenticationRepository +import hu.bbara.purefin.core.data.DownloadMediaSourceResolver +import hu.bbara.purefin.core.data.EpisodeDownloadSource +import hu.bbara.purefin.core.data.MovieDownloadSource +import hu.bbara.purefin.core.data.PlaybackMethod +import hu.bbara.purefin.core.data.PlaybackProfileFamily +import hu.bbara.purefin.core.data.PlaybackReportContext +import hu.bbara.purefin.core.data.SessionBootstrapper import hu.bbara.purefin.core.data.session.UserSessionRepository +import hu.bbara.purefin.core.image.ImageUrlBuilder +import hu.bbara.purefin.core.model.Episode +import hu.bbara.purefin.core.model.Movie +import hu.bbara.purefin.core.model.Season +import hu.bbara.purefin.core.model.Series import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext @@ -34,12 +47,14 @@ import org.jellyfin.sdk.model.api.PlaybackOrder import org.jellyfin.sdk.model.api.PlaybackProgressInfo import org.jellyfin.sdk.model.api.PlaybackStartInfo import org.jellyfin.sdk.model.api.PlaybackStopInfo +import org.jellyfin.sdk.model.api.PlayMethod import org.jellyfin.sdk.model.api.RepeatMode import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetNextUpRequest import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest import java.util.UUID import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -48,7 +63,7 @@ class JellyfinApiClient @Inject constructor( @ApplicationContext private val applicationContext: Context, private val userSessionRepository: UserSessionRepository, private val playbackProfilePolicy: PlaybackProfilePolicy, -) { +) : SessionBootstrapper, AuthenticationRepository, DownloadMediaSourceResolver { private val jellyfin = createJellyfin { context = applicationContext clientInfo = ClientInfo(name = "Purefin", version = "0.0.1") @@ -70,7 +85,7 @@ class JellyfinApiClient @Inject constructor( return true } - suspend fun login(url: String, username: String, password: String): Boolean = withContext(Dispatchers.IO) { + override suspend fun login(url: String, username: String, password: String): Boolean = withContext(Dispatchers.IO) { val trimmedUrl = url.trim() if (trimmedUrl.isBlank()) { return@withContext false @@ -93,10 +108,14 @@ class JellyfinApiClient @Inject constructor( } } - suspend fun updateApiClient() = withContext(Dispatchers.IO) { - ensureConfigured() + override suspend fun initialize() { + withContext(Dispatchers.IO) { + ensureConfigured() + } } + suspend fun updateApiClient() = initialize() + suspend fun getLibraries(): List = withContext(Dispatchers.IO) { if (!ensureConfigured()) { return@withContext emptyList() @@ -366,6 +385,64 @@ class JellyfinApiClient @Inject constructor( url } + override suspend fun resolveMovieDownload(movieId: UUID): MovieDownloadSource? = withContext(Dispatchers.IO) { + val serverUrl = userSessionRepository.serverUrl.first().trim() + val source = getMediaSources(movieId).firstOrNull() ?: return@withContext null + val playbackUrl = getMediaPlaybackUrl(movieId, source) ?: return@withContext null + val itemInfo = getItemInfo(movieId) ?: return@withContext null + val movie = itemInfo.toMovie(serverUrl) + + MovieDownloadSource( + movie = movie, + playbackUrl = playbackUrl, + customCacheKey = source.customCacheKey(movieId, playbackUrl), + ) + } + + override suspend fun resolveEpisodeDownload(episodeId: UUID): EpisodeDownloadSource? = withContext(Dispatchers.IO) { + val serverUrl = userSessionRepository.serverUrl.first().trim() + val source = getMediaSources(episodeId).firstOrNull() ?: return@withContext null + val playbackUrl = getMediaPlaybackUrl(episodeId, source) ?: return@withContext null + val episodeDto = getItemInfo(episodeId) ?: return@withContext null + val episode = episodeDto.toEpisode(serverUrl) + val series = getItemInfo(episode.seriesId)?.toSeries(serverUrl) ?: return@withContext null + val season = getItemInfo(episode.seasonId)?.toSeason(series.id) ?: return@withContext null + + EpisodeDownloadSource( + episode = episode, + series = series, + season = season, + playbackUrl = playbackUrl, + customCacheKey = source.customCacheKey(episodeId, playbackUrl), + ) + } + + override suspend fun isEpisodeWatched(episodeId: UUID): Boolean { + return getItemInfo(episodeId)?.userData?.played == true + } + + override suspend fun getUnwatchedEpisodeIds( + seriesId: UUID, + excludedEpisodeIds: Set, + limit: Int, + ): List = withContext(Dispatchers.IO) { + if (limit <= 0) { + return@withContext emptyList() + } + val seasons = getSeasons(seriesId) + val episodes = buildList { + seasons.forEach { season -> + addAll(getEpisodesInSeason(seriesId, season.id)) + } + } + episodes + .filter { episode -> + episode.userData?.played != true && episode.id !in excludedEpisodeIds + } + .take(limit) + .map { it.id } + } + suspend fun reportPlaybackStart( itemId: UUID, positionTicks: Long = 0L, @@ -384,7 +461,7 @@ class JellyfinApiClient @Inject constructor( subtitleStreamIndex = reportContext.subtitleStreamIndex, liveStreamId = reportContext.liveStreamId, playSessionId = reportContext.playSessionId, - playMethod = reportContext.playMethod, + playMethod = reportContext.playMethod.toJellyfinPlayMethod(), repeatMode = RepeatMode.REPEAT_NONE, playbackOrder = PlaybackOrder.DEFAULT, ) @@ -410,7 +487,7 @@ class JellyfinApiClient @Inject constructor( subtitleStreamIndex = reportContext.subtitleStreamIndex, liveStreamId = reportContext.liveStreamId, playSessionId = reportContext.playSessionId, - playMethod = reportContext.playMethod, + playMethod = reportContext.playMethod.toJellyfinPlayMethod(), repeatMode = RepeatMode.REPEAT_NONE, playbackOrder = PlaybackOrder.DEFAULT, ) @@ -450,6 +527,98 @@ class JellyfinApiClient @Inject constructor( return resolvedVersion } + private fun BaseItemDto.toMovie(serverUrl: String): Movie { + return Movie( + id = id, + libraryId = parentId ?: UUID.randomUUID(), + title = name ?: "Unknown title", + progress = userData?.playedPercentage, + watched = userData?.played ?: false, + year = productionYear?.toString() ?: premiereDate?.year?.toString().orEmpty(), + rating = officialRating ?: "NR", + runtime = formatRuntime(runTimeTicks), + format = container?.uppercase() ?: "VIDEO", + synopsis = overview ?: "No synopsis available", + imageUrlPrefix = ImageUrlBuilder.toPrefixImageUrl(serverUrl, id), + audioTrack = "ENG", + subtitles = "ENG", + cast = emptyList(), + ) + } + + private fun BaseItemDto.toEpisode(serverUrl: String): Episode { + return Episode( + id = id, + seriesId = seriesId ?: UUID.randomUUID(), + seasonId = parentId ?: UUID.randomUUID(), + title = name ?: "Unknown title", + index = indexNumber ?: 0, + synopsis = overview ?: "No synopsis available.", + releaseDate = productionYear?.toString() ?: "—", + rating = officialRating ?: "NR", + runtime = formatRuntime(runTimeTicks), + progress = userData?.playedPercentage, + watched = userData?.played ?: false, + format = container?.uppercase() ?: "VIDEO", + imageUrlPrefix = ImageUrlBuilder.toPrefixImageUrl(serverUrl, id), + cast = emptyList(), + ) + } + + private fun BaseItemDto.toSeries(serverUrl: String): Series { + return Series( + id = id, + libraryId = parentId ?: UUID.randomUUID(), + name = name ?: "Unknown", + synopsis = overview ?: "No synopsis available", + year = productionYear?.toString() ?: premiereDate?.year?.toString().orEmpty(), + imageUrlPrefix = ImageUrlBuilder.toPrefixImageUrl(serverUrl, id), + unwatchedEpisodeCount = userData?.unplayedItemCount ?: 0, + seasonCount = childCount ?: 0, + seasons = emptyList(), + cast = emptyList(), + ) + } + + private fun BaseItemDto.toSeason(seriesId: UUID): Season { + return Season( + id = id, + seriesId = this.seriesId ?: seriesId, + name = name ?: "Unknown", + index = indexNumber ?: 0, + unwatchedEpisodeCount = userData?.unplayedItemCount ?: 0, + episodeCount = childCount ?: 0, + episodes = emptyList(), + ) + } + + private fun MediaSourceInfo.customCacheKey(mediaId: UUID, playbackUrl: String): String? { + val shouldTranscode = supportsTranscoding == true && + (supportsDirectPlay == false || transcodingUrl != null) + if (shouldTranscode) { + return null + } + return playbackCustomCacheKey( + mediaId = mediaId.toString(), + playbackUrl = playbackUrl, + playMethod = PlaybackMethod.DIRECT_PLAY, + ) + } + + private fun formatRuntime(ticks: Long?): String { + if (ticks == null || ticks <= 0) return "—" + val totalSeconds = ticks / 10_000_000 + val hours = TimeUnit.SECONDS.toHours(totalSeconds) + val minutes = TimeUnit.SECONDS.toMinutes(totalSeconds) % 60 + return if (hours > 0) "${hours}h ${minutes}m" else "${minutes}m" + } + + private fun PlaybackMethod.toJellyfinPlayMethod(): PlayMethod = when (this) { + PlaybackMethod.DIRECT_PLAY -> PlayMethod.DIRECT_PLAY + PlaybackMethod.DIRECT_STREAM -> PlayMethod.DIRECT_STREAM + PlaybackMethod.TRANSCODE -> PlayMethod.TRANSCODE + } + private companion object { private const val TAG = "JellyfinApiClient" } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAuthInterceptor.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAuthInterceptor.kt similarity index 95% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAuthInterceptor.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAuthInterceptor.kt index 067b8837..fe954361 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/JellyfinAuthInterceptor.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/JellyfinAuthInterceptor.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import hu.bbara.purefin.core.data.session.UserSessionRepository import kotlinx.coroutines.CoroutineScope diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackCacheKeys.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackCacheKeys.kt similarity index 67% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackCacheKeys.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackCacheKeys.kt index e0278c57..d9bd12c2 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackCacheKeys.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackCacheKeys.kt @@ -1,11 +1,11 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client -import org.jellyfin.sdk.model.api.PlayMethod +import hu.bbara.purefin.core.data.PlaybackMethod fun playbackCustomCacheKey( mediaId: String, playbackUrl: String, - playMethod: PlayMethod + playMethod: PlaybackMethod ): String? { val normalizedUrl = playbackUrl.substringBefore('?').substringBefore('#').lowercase() val isAdaptiveManifest = @@ -14,7 +14,7 @@ fun playbackCustomCacheKey( normalizedUrl.endsWith(".ism") || normalizedUrl.contains(".ism/") - return if (playMethod == PlayMethod.DIRECT_PLAY && !isAdaptiveManifest) { + return if (playMethod == PlaybackMethod.DIRECT_PLAY && !isAdaptiveManifest) { mediaId } else { null diff --git a/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecision.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecision.kt new file mode 100644 index 00000000..3708f95a --- /dev/null +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecision.kt @@ -0,0 +1,10 @@ +package hu.bbara.purefin.data.jellyfin.client + +import hu.bbara.purefin.core.data.PlaybackReportContext +import org.jellyfin.sdk.model.api.MediaSourceInfo + +data class PlaybackDecision( + val url: String, + val mediaSource: MediaSourceInfo, + val reportContext: PlaybackReportContext +) diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackDecisionResolver.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecisionResolver.kt similarity index 78% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackDecisionResolver.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecisionResolver.kt index 4c779429..d7786d8e 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackDecisionResolver.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecisionResolver.kt @@ -1,5 +1,7 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client +import hu.bbara.purefin.core.data.PlaybackMethod +import hu.bbara.purefin.core.data.PlaybackReportContext import org.jellyfin.sdk.model.api.MediaProtocol import org.jellyfin.sdk.model.api.MediaSourceInfo import org.jellyfin.sdk.model.api.PlayMethod @@ -15,16 +17,16 @@ internal object PlaybackDecisionResolver { ?: return null val playMethod = when { - mediaSource.supportsDirectPlay -> PlayMethod.DIRECT_PLAY - mediaSource.supportsDirectStream && !mediaSource.transcodingUrl.isNullOrBlank() -> PlayMethod.DIRECT_STREAM - mediaSource.supportsTranscoding && !mediaSource.transcodingUrl.isNullOrBlank() -> PlayMethod.TRANSCODE + mediaSource.supportsDirectPlay -> PlaybackMethod.DIRECT_PLAY + mediaSource.supportsDirectStream && !mediaSource.transcodingUrl.isNullOrBlank() -> PlaybackMethod.DIRECT_STREAM + mediaSource.supportsTranscoding && !mediaSource.transcodingUrl.isNullOrBlank() -> PlaybackMethod.TRANSCODE else -> return null } val url = when (playMethod) { - PlayMethod.DIRECT_PLAY -> directPlayUrl(mediaSource) - PlayMethod.DIRECT_STREAM, - PlayMethod.TRANSCODE, + PlaybackMethod.DIRECT_PLAY -> directPlayUrl(mediaSource) + PlaybackMethod.DIRECT_STREAM, + PlaybackMethod.TRANSCODE, -> absolutePlaybackUrl(serverUrl, requireNotNull(mediaSource.transcodingUrl)) } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackProfileModule.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackProfileModule.kt similarity index 87% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackProfileModule.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackProfileModule.kt index bc443383..45293f6a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackProfileModule.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackProfileModule.kt @@ -1,9 +1,10 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import hu.bbara.purefin.core.data.PlaybackProfileFamily import javax.inject.Singleton @Module diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackProfilePolicy.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackProfilePolicy.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackProfilePolicy.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackProfilePolicy.kt index 9cb080c1..b72fc755 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/client/PlaybackProfilePolicy.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/client/PlaybackProfilePolicy.kt @@ -1,13 +1,9 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client +import hu.bbara.purefin.core.data.PlaybackProfileFamily import org.jellyfin.sdk.model.ServerVersion import org.jellyfin.sdk.model.api.DeviceProfile -enum class PlaybackProfileFamily { - MOBILE, - TV, -} - interface PlaybackProfilePolicy { fun create(serverVersion: ServerVersion): DeviceProfile } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSession.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSession.kt similarity index 62% rename from core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSession.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSession.kt index 6dcf94f9..e7d51cd2 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSession.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSession.kt @@ -1,14 +1,14 @@ -package hu.bbara.purefin.core.data.session +package hu.bbara.purefin.data.jellyfin.session import kotlinx.serialization.Serializable -import org.jellyfin.sdk.model.serializer.UUIDSerializer +import hu.bbara.purefin.core.navigation.UuidSerializer import java.util.UUID @Serializable data class UserSession( val accessToken: String, val url: String, - @Serializable(with = UUIDSerializer::class) + @Serializable(with = UuidSerializer::class) val userId: UUID?, val loggedIn: Boolean, val isOfflineMode: Boolean = false diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionModule.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionModule.kt similarity index 86% rename from core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionModule.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionModule.kt index 867e826d..19e6c507 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionModule.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionModule.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.session +package hu.bbara.purefin.data.jellyfin.session import android.content.Context import androidx.datastore.core.DataStore @@ -10,6 +10,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import hu.bbara.purefin.core.data.session.UserSessionRepository import javax.inject.Singleton @Module @@ -35,6 +36,6 @@ class UserSessionModule { fun provideUserSessionRepository( userSessionDataStore: DataStore ): UserSessionRepository { - return UserSessionRepository(userSessionDataStore) + return DataStoreUserSessionRepository(userSessionDataStore) } -} \ No newline at end of file +} diff --git a/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionRepository.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionRepository.kt new file mode 100644 index 00000000..f3a67021 --- /dev/null +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionRepository.kt @@ -0,0 +1,55 @@ +package hu.bbara.purefin.data.jellyfin.session + +import androidx.datastore.core.DataStore +import hu.bbara.purefin.core.data.session.UserSessionRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import java.util.UUID +import javax.inject.Inject + +class DataStoreUserSessionRepository @Inject constructor( + private val userSessionDataStore: DataStore +) : UserSessionRepository { + val session: Flow = userSessionDataStore.data + + override val serverUrl: Flow = session + .map { it.url } + + override suspend fun setServerUrl(serverUrl: String) { + userSessionDataStore.updateData { + it.copy(url = serverUrl) + } + } + + override val accessToken: Flow = session + .map { it.accessToken } + + override suspend fun setAccessToken(accessToken: String) { + userSessionDataStore.updateData { + it.copy(accessToken = accessToken) + } + } + + override val userId: Flow = session + .map { it.userId } + + override suspend fun setUserId(userId: UUID?) { + userSessionDataStore.updateData { + it.copy(userId = userId) + } + } + + override suspend fun getUserId(): UUID? = userId.first() + + override val isLoggedIn: Flow = session.map { it.loggedIn }.distinctUntilChanged() + + override suspend fun setLoggedIn(isLoggedIn: Boolean) { + userSessionDataStore.updateData { + it.copy(loggedIn = isLoggedIn) + } + } + + override val isOfflineMode: Flow = session.map { it.isOfflineMode }.distinctUntilChanged() +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionSerializer.kt b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionSerializer.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionSerializer.kt rename to data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionSerializer.kt index f3f173e8..1f393ffc 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/session/UserSessionSerializer.kt +++ b/data/jellyfin/src/main/java/hu/bbara/purefin/data/jellyfin/session/UserSessionSerializer.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.session +package hu.bbara.purefin.data.jellyfin.session import androidx.datastore.core.CorruptionException import androidx.datastore.core.Serializer @@ -23,10 +23,10 @@ object UserSessionSerializer : Serializer { override suspend fun writeTo(t: UserSession, output: OutputStream) { output.write( - Json.encodeToString(t) + Json.encodeToString(UserSession.serializer(), t) .encodeToByteArray() ) } -} \ No newline at end of file +} diff --git a/core/data/src/test/java/hu/bbara/purefin/core/data/client/AndroidDeviceProfileTest.kt b/data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/AndroidDeviceProfileTest.kt similarity index 99% rename from core/data/src/test/java/hu/bbara/purefin/core/data/client/AndroidDeviceProfileTest.kt rename to data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/AndroidDeviceProfileTest.kt index c79dedb3..96c92c5e 100644 --- a/core/data/src/test/java/hu/bbara/purefin/core/data/client/AndroidDeviceProfileTest.kt +++ b/data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/AndroidDeviceProfileTest.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client import androidx.media3.common.MimeTypes import org.jellyfin.sdk.model.ServerVersion diff --git a/core/data/src/test/java/hu/bbara/purefin/core/data/client/PlaybackCacheKeysTest.kt b/data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/PlaybackCacheKeysTest.kt similarity index 78% rename from core/data/src/test/java/hu/bbara/purefin/core/data/client/PlaybackCacheKeysTest.kt rename to data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/PlaybackCacheKeysTest.kt index 68d828d9..0cb43e81 100644 --- a/core/data/src/test/java/hu/bbara/purefin/core/data/client/PlaybackCacheKeysTest.kt +++ b/data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/PlaybackCacheKeysTest.kt @@ -1,6 +1,6 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client -import org.jellyfin.sdk.model.api.PlayMethod +import hu.bbara.purefin.core.data.PlaybackMethod import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Test @@ -14,7 +14,7 @@ class PlaybackCacheKeysTest { playbackCustomCacheKey( mediaId = "movie-1", playbackUrl = "https://example.com/Videos/movie-1.mp4", - playMethod = PlayMethod.DIRECT_PLAY + playMethod = PlaybackMethod.DIRECT_PLAY ) ) } @@ -25,7 +25,7 @@ class PlaybackCacheKeysTest { playbackCustomCacheKey( mediaId = "episode-1", playbackUrl = "https://example.com/Videos/episode-1/master.m3u8", - playMethod = PlayMethod.DIRECT_STREAM + playMethod = PlaybackMethod.DIRECT_STREAM ) ) } @@ -36,7 +36,7 @@ class PlaybackCacheKeysTest { playbackCustomCacheKey( mediaId = "episode-2", playbackUrl = "https://example.com/Videos/episode-2/master.m3u8", - playMethod = PlayMethod.DIRECT_PLAY + playMethod = PlaybackMethod.DIRECT_PLAY ) ) } diff --git a/core/data/src/test/java/hu/bbara/purefin/core/data/client/JellyfinApiClientTest.kt b/data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecisionResolverTest.kt similarity index 94% rename from core/data/src/test/java/hu/bbara/purefin/core/data/client/JellyfinApiClientTest.kt rename to data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecisionResolverTest.kt index cc041aec..b2dd60e9 100644 --- a/core/data/src/test/java/hu/bbara/purefin/core/data/client/JellyfinApiClientTest.kt +++ b/data/jellyfin/src/test/java/hu/bbara/purefin/data/jellyfin/client/PlaybackDecisionResolverTest.kt @@ -1,10 +1,10 @@ -package hu.bbara.purefin.core.data.client +package hu.bbara.purefin.data.jellyfin.client +import hu.bbara.purefin.core.data.PlaybackMethod import org.jellyfin.sdk.model.api.MediaProtocol import org.jellyfin.sdk.model.api.MediaSourceInfo import org.jellyfin.sdk.model.api.MediaSourceType import org.jellyfin.sdk.model.api.MediaStreamProtocol -import org.jellyfin.sdk.model.api.PlayMethod import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertSame @@ -39,7 +39,7 @@ class PlaybackDecisionResolverTest { assertSame(localSource, decision.mediaSource) assertEquals("direct://local", decision.url) - assertEquals(PlayMethod.DIRECT_PLAY, decision.reportContext.playMethod) + assertEquals(PlaybackMethod.DIRECT_PLAY, decision.reportContext.playMethod) assertEquals("local", decision.reportContext.mediaSourceId) assertEquals(2, decision.reportContext.audioStreamIndex) assertEquals(4, decision.reportContext.subtitleStreamIndex) @@ -63,7 +63,7 @@ class PlaybackDecisionResolverTest { ) { error("direct play should not be used") } ) - assertEquals(PlayMethod.DIRECT_STREAM, decision.reportContext.playMethod) + assertEquals(PlaybackMethod.DIRECT_STREAM, decision.reportContext.playMethod) assertEquals("https://example.com/jellyfin/Videos/1/master.m3u8", decision.url) } @@ -83,7 +83,7 @@ class PlaybackDecisionResolverTest { ) { error("direct play should not be used") } ) - assertEquals(PlayMethod.TRANSCODE, decision.reportContext.playMethod) + assertEquals(PlaybackMethod.TRANSCODE, decision.reportContext.playMethod) assertEquals("https://example.com/jellyfin/Videos/2/master.m3u8", decision.url) } diff --git a/data/offline/build.gradle.kts b/data/offline/build.gradle.kts new file mode 100644 index 00000000..110540c1 --- /dev/null +++ b/data/offline/build.gradle.kts @@ -0,0 +1,41 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.data.offline" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:model")) + implementation(project(":core:data")) + implementation(libs.kotlinx.serialization.json) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.datastore) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) + testImplementation(libs.junit) +} diff --git a/data/offline/src/main/java/hu/bbara/purefin/data/offline/OfflineBindingsModule.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/OfflineBindingsModule.kt new file mode 100644 index 00000000..146c13d1 --- /dev/null +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/OfflineBindingsModule.kt @@ -0,0 +1,19 @@ +package hu.bbara.purefin.data.offline + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import hu.bbara.purefin.core.data.OfflineCatalogStore +import hu.bbara.purefin.core.data.SmartDownloadStore + +@Module +@InstallIn(SingletonComponent::class) +abstract class OfflineBindingsModule { + + @Binds + abstract fun bindOfflineCatalogStore(impl: RoomOfflineCatalogStore): OfflineCatalogStore + + @Binds + abstract fun bindSmartDownloadStore(impl: RoomSmartDownloadStore): SmartDownloadStore +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/RoomOfflineCatalogStore.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/RoomOfflineCatalogStore.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/RoomOfflineCatalogStore.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/RoomOfflineCatalogStore.kt index c96ff8ae..b9cbeffb 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/RoomOfflineCatalogStore.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/RoomOfflineCatalogStore.kt @@ -1,6 +1,7 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.offline -import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource +import hu.bbara.purefin.core.data.OfflineCatalogStore +import hu.bbara.purefin.data.offline.room.offline.OfflineRoomMediaLocalDataSource import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Season diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/RoomSmartDownloadStore.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/RoomSmartDownloadStore.kt similarity index 76% rename from core/data/src/main/java/hu/bbara/purefin/core/data/RoomSmartDownloadStore.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/RoomSmartDownloadStore.kt index 73e49765..5ad51c04 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/RoomSmartDownloadStore.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/RoomSmartDownloadStore.kt @@ -1,7 +1,8 @@ -package hu.bbara.purefin.core.data +package hu.bbara.purefin.data.offline -import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao -import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity +import hu.bbara.purefin.core.data.SmartDownloadStore +import hu.bbara.purefin.data.offline.room.dao.SmartDownloadDao +import hu.bbara.purefin.data.offline.room.entity.SmartDownloadEntity import java.util.UUID import javax.inject.Inject import javax.inject.Singleton diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCache.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCache.kt similarity index 91% rename from core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCache.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCache.kt index 5453f5d6..e5aa9426 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCache.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCache.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.cache +package hu.bbara.purefin.data.offline.cache import kotlinx.serialization.Serializable diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCacheModule.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCacheModule.kt similarity index 95% rename from core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCacheModule.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCacheModule.kt index 8aaaa822..809c5a65 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCacheModule.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCacheModule.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.cache +package hu.bbara.purefin.data.offline.cache import android.content.Context import androidx.datastore.core.DataStore diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCacheSerializer.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCacheSerializer.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCacheSerializer.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCacheSerializer.kt index a27d9628..fac8c637 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/cache/HomeCacheSerializer.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/cache/HomeCacheSerializer.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.cache +package hu.bbara.purefin.data.offline.cache import androidx.datastore.core.CorruptionException import androidx.datastore.core.Serializer @@ -23,7 +23,7 @@ object HomeCacheSerializer : Serializer { override suspend fun writeTo(t: HomeCache, output: OutputStream) { output.write( - Json.encodeToString(t) + Json.encodeToString(HomeCache.serializer(), t) .encodeToByteArray() ) } diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/MediaDatabaseModule.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/MediaDatabaseModule.kt similarity index 75% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/MediaDatabaseModule.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/MediaDatabaseModule.kt index 32118749..9e44f3bf 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/MediaDatabaseModule.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/MediaDatabaseModule.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room +package hu.bbara.purefin.data.offline.room import android.content.Context import androidx.room.Room @@ -7,13 +7,13 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import hu.bbara.purefin.core.data.room.dao.EpisodeDao -import hu.bbara.purefin.core.data.room.dao.MovieDao -import hu.bbara.purefin.core.data.room.dao.SeasonDao -import hu.bbara.purefin.core.data.room.dao.SeriesDao -import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao -import hu.bbara.purefin.core.data.room.offline.OfflineMediaDatabase -import hu.bbara.purefin.core.data.room.offline.OfflineRoomMediaLocalDataSource +import hu.bbara.purefin.data.offline.room.dao.EpisodeDao +import hu.bbara.purefin.data.offline.room.dao.MovieDao +import hu.bbara.purefin.data.offline.room.dao.SeasonDao +import hu.bbara.purefin.data.offline.room.dao.SeriesDao +import hu.bbara.purefin.data.offline.room.dao.SmartDownloadDao +import hu.bbara.purefin.data.offline.room.offline.OfflineMediaDatabase +import hu.bbara.purefin.data.offline.room.offline.OfflineRoomMediaLocalDataSource import javax.inject.Singleton @Module diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/RoomRelations.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/RoomRelations.kt similarity index 69% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/RoomRelations.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/RoomRelations.kt index 838635db..47f2b412 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/RoomRelations.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/RoomRelations.kt @@ -1,12 +1,12 @@ -package hu.bbara.purefin.core.data.room +package hu.bbara.purefin.data.offline.room import androidx.room.Embedded import androidx.room.Relation -import hu.bbara.purefin.core.data.room.entity.EpisodeEntity -import hu.bbara.purefin.core.data.room.entity.LibraryEntity -import hu.bbara.purefin.core.data.room.entity.MovieEntity -import hu.bbara.purefin.core.data.room.entity.SeasonEntity -import hu.bbara.purefin.core.data.room.entity.SeriesEntity +import hu.bbara.purefin.data.offline.room.entity.EpisodeEntity +import hu.bbara.purefin.data.offline.room.entity.LibraryEntity +import hu.bbara.purefin.data.offline.room.entity.MovieEntity +import hu.bbara.purefin.data.offline.room.entity.SeasonEntity +import hu.bbara.purefin.data.offline.room.entity.SeriesEntity data class SeasonWithEpisodes( @Embedded val season: SeasonEntity, diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/UuidConverters.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/UuidConverters.kt similarity index 88% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/UuidConverters.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/UuidConverters.kt index 3761c4c3..79f3ef26 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/UuidConverters.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/UuidConverters.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room +package hu.bbara.purefin.data.offline.room import androidx.room.TypeConverter import java.util.UUID diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/CastMemberDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/CastMemberDao.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/CastMemberDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/CastMemberDao.kt index f45ce93d..e805b11a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/CastMemberDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/CastMemberDao.kt @@ -1,9 +1,9 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert -import hu.bbara.purefin.core.data.room.entity.CastMemberEntity +import hu.bbara.purefin.data.offline.room.entity.CastMemberEntity import java.util.UUID @Dao diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/EpisodeDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/EpisodeDao.kt similarity index 93% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/EpisodeDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/EpisodeDao.kt index 4b6cd2f6..b3184d3b 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/EpisodeDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/EpisodeDao.kt @@ -1,9 +1,9 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert -import hu.bbara.purefin.core.data.room.entity.EpisodeEntity +import hu.bbara.purefin.data.offline.room.entity.EpisodeEntity import kotlinx.coroutines.flow.Flow import java.util.UUID diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/LibraryDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/LibraryDao.kt similarity index 78% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/LibraryDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/LibraryDao.kt index 356401d0..294c8fe9 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/LibraryDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/LibraryDao.kt @@ -1,10 +1,10 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert -import hu.bbara.purefin.core.data.room.LibraryWithContent -import hu.bbara.purefin.core.data.room.entity.LibraryEntity +import hu.bbara.purefin.data.offline.room.LibraryWithContent +import hu.bbara.purefin.data.offline.room.entity.LibraryEntity import kotlinx.coroutines.flow.Flow @Dao diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/MovieDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/MovieDao.kt similarity index 88% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/MovieDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/MovieDao.kt index 2056786b..79701f2c 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/MovieDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/MovieDao.kt @@ -1,9 +1,9 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert -import hu.bbara.purefin.core.data.room.entity.MovieEntity +import hu.bbara.purefin.data.offline.room.entity.MovieEntity import kotlinx.coroutines.flow.Flow import java.util.UUID diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SeasonDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SeasonDao.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SeasonDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SeasonDao.kt index d4e57913..5a70479f 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SeasonDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SeasonDao.kt @@ -1,9 +1,9 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert -import hu.bbara.purefin.core.data.room.entity.SeasonEntity +import hu.bbara.purefin.data.offline.room.entity.SeasonEntity import java.util.UUID @Dao diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SeriesDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SeriesDao.kt similarity index 84% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SeriesDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SeriesDao.kt index bd033018..caf82f30 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SeriesDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SeriesDao.kt @@ -1,11 +1,11 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Transaction import androidx.room.Upsert -import hu.bbara.purefin.core.data.room.SeriesWithSeasonsAndEpisodes -import hu.bbara.purefin.core.data.room.entity.SeriesEntity +import hu.bbara.purefin.data.offline.room.SeriesWithSeasonsAndEpisodes +import hu.bbara.purefin.data.offline.room.entity.SeriesEntity import kotlinx.coroutines.flow.Flow import java.util.UUID diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SmartDownloadDao.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SmartDownloadDao.kt similarity index 88% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SmartDownloadDao.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SmartDownloadDao.kt index 068a7e4a..f3c11c4f 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/dao/SmartDownloadDao.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/dao/SmartDownloadDao.kt @@ -1,10 +1,10 @@ -package hu.bbara.purefin.core.data.room.dao +package hu.bbara.purefin.data.offline.room.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity +import hu.bbara.purefin.data.offline.room.entity.SmartDownloadEntity import kotlinx.coroutines.flow.Flow import java.util.UUID diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/CastMemberEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/CastMemberEntity.kt similarity index 90% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/CastMemberEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/CastMemberEntity.kt index f603634e..70a7325c 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/CastMemberEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/CastMemberEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.Index diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/EpisodeEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/EpisodeEntity.kt similarity index 94% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/EpisodeEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/EpisodeEntity.kt index 9ec49886..5877907d 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/EpisodeEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/EpisodeEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.ForeignKey diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/LibraryEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/LibraryEntity.kt similarity index 81% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/LibraryEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/LibraryEntity.kt index d548dc5f..2f7a4ee2 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/LibraryEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/LibraryEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/MovieEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/MovieEntity.kt similarity index 91% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/MovieEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/MovieEntity.kt index f952baf8..55b335c8 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/MovieEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/MovieEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.Index diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SeasonEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SeasonEntity.kt similarity index 92% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SeasonEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SeasonEntity.kt index 33dc6cd2..aea0b39a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SeasonEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SeasonEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.ForeignKey diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SeriesEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SeriesEntity.kt similarity index 89% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SeriesEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SeriesEntity.kt index 94cc1d7e..b6175b69 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SeriesEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SeriesEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.Index diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SmartDownloadEntity.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SmartDownloadEntity.kt similarity index 79% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SmartDownloadEntity.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SmartDownloadEntity.kt index ea3d84e0..0fea564a 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/entity/SmartDownloadEntity.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/entity/SmartDownloadEntity.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.core.data.room.entity +package hu.bbara.purefin.data.offline.room.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/offline/OfflineMediaDatabase.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/offline/OfflineMediaDatabase.kt new file mode 100644 index 00000000..6a946f40 --- /dev/null +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/offline/OfflineMediaDatabase.kt @@ -0,0 +1,36 @@ +package hu.bbara.purefin.data.offline.room.offline + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import hu.bbara.purefin.data.offline.room.UuidConverters +import hu.bbara.purefin.data.offline.room.dao.EpisodeDao +import hu.bbara.purefin.data.offline.room.dao.MovieDao +import hu.bbara.purefin.data.offline.room.dao.SeasonDao +import hu.bbara.purefin.data.offline.room.dao.SeriesDao +import hu.bbara.purefin.data.offline.room.dao.SmartDownloadDao +import hu.bbara.purefin.data.offline.room.entity.EpisodeEntity +import hu.bbara.purefin.data.offline.room.entity.MovieEntity +import hu.bbara.purefin.data.offline.room.entity.SeasonEntity +import hu.bbara.purefin.data.offline.room.entity.SeriesEntity +import hu.bbara.purefin.data.offline.room.entity.SmartDownloadEntity + +@Database( + entities = [ + MovieEntity::class, + SeriesEntity::class, + SeasonEntity::class, + EpisodeEntity::class, + SmartDownloadEntity::class, + ], + version = 9, + exportSchema = false +) +@TypeConverters(UuidConverters::class) +abstract class OfflineMediaDatabase : RoomDatabase() { + abstract fun movieDao(): MovieDao + abstract fun seriesDao(): SeriesDao + abstract fun seasonDao(): SeasonDao + abstract fun episodeDao(): EpisodeDao + abstract fun smartDownloadDao(): SmartDownloadDao +} diff --git a/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineRoomMediaLocalDataSource.kt b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/offline/OfflineRoomMediaLocalDataSource.kt similarity index 95% rename from core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineRoomMediaLocalDataSource.kt rename to data/offline/src/main/java/hu/bbara/purefin/data/offline/room/offline/OfflineRoomMediaLocalDataSource.kt index 3ee60df3..33f2fb04 100644 --- a/core/data/src/main/java/hu/bbara/purefin/core/data/room/offline/OfflineRoomMediaLocalDataSource.kt +++ b/data/offline/src/main/java/hu/bbara/purefin/data/offline/room/offline/OfflineRoomMediaLocalDataSource.kt @@ -1,14 +1,14 @@ -package hu.bbara.purefin.core.data.room.offline +package hu.bbara.purefin.data.offline.room.offline import androidx.room.withTransaction -import hu.bbara.purefin.core.data.room.dao.EpisodeDao -import hu.bbara.purefin.core.data.room.dao.MovieDao -import hu.bbara.purefin.core.data.room.dao.SeasonDao -import hu.bbara.purefin.core.data.room.dao.SeriesDao -import hu.bbara.purefin.core.data.room.entity.EpisodeEntity -import hu.bbara.purefin.core.data.room.entity.MovieEntity -import hu.bbara.purefin.core.data.room.entity.SeasonEntity -import hu.bbara.purefin.core.data.room.entity.SeriesEntity +import hu.bbara.purefin.data.offline.room.dao.EpisodeDao +import hu.bbara.purefin.data.offline.room.dao.MovieDao +import hu.bbara.purefin.data.offline.room.dao.SeasonDao +import hu.bbara.purefin.data.offline.room.dao.SeriesDao +import hu.bbara.purefin.data.offline.room.entity.EpisodeEntity +import hu.bbara.purefin.data.offline.room.entity.MovieEntity +import hu.bbara.purefin.data.offline.room.entity.SeasonEntity +import hu.bbara.purefin.data.offline.room.entity.SeriesEntity import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Season diff --git a/feature/shared/build.gradle.kts b/feature/browse/build.gradle.kts similarity index 76% rename from feature/shared/build.gradle.kts rename to feature/browse/build.gradle.kts index af476056..37c98a3e 100644 --- a/feature/shared/build.gradle.kts +++ b/feature/browse/build.gradle.kts @@ -3,13 +3,12 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) - alias(libs.plugins.kotlin.serialization) alias(libs.plugins.hilt) alias(libs.plugins.ksp) } android { - namespace = "hu.bbara.purefin.feature.shared" + namespace = "hu.bbara.purefin.feature.browse" compileSdk = 36 defaultConfig { @@ -31,12 +30,12 @@ kotlin { dependencies { implementation(project(":core:model")) implementation(project(":core:data")) + implementation(project(":core:image")) + implementation(project(":core:navigation")) + implementation(project(":core:download")) + implementation(libs.androidx.navigation3.runtime) implementation(libs.hilt) ksp(libs.hilt.compiler) - implementation(libs.jellyfin.core) - implementation(libs.kotlinx.serialization.json) implementation(libs.androidx.lifecycle.viewmodel.ktx) - implementation(libs.androidx.lifecycle.viewmodel.compose) - implementation(libs.androidx.navigation3.runtime) testImplementation(libs.junit) } diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/home/AppViewModel.kt b/feature/browse/src/main/java/hu/bbara/purefin/feature/browse/home/AppViewModel.kt similarity index 91% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/home/AppViewModel.kt rename to feature/browse/src/main/java/hu/bbara/purefin/feature/browse/home/AppViewModel.kt index ae0f4db6..43e53834 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/home/AppViewModel.kt +++ b/feature/browse/src/main/java/hu/bbara/purefin/feature/browse/home/AppViewModel.kt @@ -1,22 +1,21 @@ -package hu.bbara.purefin.feature.shared.home +package hu.bbara.purefin.feature.browse.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.HomeRepository import hu.bbara.purefin.core.data.MediaCatalogReader -import hu.bbara.purefin.core.data.domain.usecase.RefreshHomeDataUseCase -import hu.bbara.purefin.core.data.download.MediaDownloadController +import hu.bbara.purefin.core.download.MediaDownloadController import hu.bbara.purefin.core.data.session.UserSessionRepository import hu.bbara.purefin.core.model.LibraryKind import hu.bbara.purefin.core.model.Media import hu.bbara.purefin.core.model.MediaKind -import hu.bbara.purefin.feature.shared.navigation.EpisodeDto -import hu.bbara.purefin.feature.shared.navigation.LibraryDto -import hu.bbara.purefin.feature.shared.navigation.MovieDto -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route -import hu.bbara.purefin.feature.shared.navigation.SeriesDto +import hu.bbara.purefin.core.navigation.EpisodeDto +import hu.bbara.purefin.core.navigation.LibraryDto +import hu.bbara.purefin.core.navigation.MovieDto +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route +import hu.bbara.purefin.core.navigation.SeriesDto import java.util.UUID import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -34,7 +33,6 @@ class AppViewModel @Inject constructor( private val mediaCatalogReader: MediaCatalogReader, private val userSessionRepository: UserSessionRepository, private val navigationManager: NavigationManager, - private val refreshHomeDataUseCase: RefreshHomeDataUseCase, private val mediaDownloadManager: MediaDownloadController, ) : ViewModel() { @@ -207,7 +205,7 @@ class AppViewModel @Inject constructor( fun onResumed() { viewModelScope.launch { try { - refreshHomeDataUseCase() + homeRepository.refreshHomeData() } catch (e: Exception) { // Refresh is best-effort; don't crash on failure } @@ -223,7 +221,7 @@ class AppViewModel @Inject constructor( viewModelScope.launch { _isRefreshing.value = true try { - refreshHomeDataUseCase() + homeRepository.refreshHomeData() } catch (e: Exception) { // Refresh is best-effort; don't crash on failure } finally { diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/home/HomeModels.kt b/feature/browse/src/main/java/hu/bbara/purefin/feature/browse/home/HomeModels.kt similarity index 86% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/home/HomeModels.kt rename to feature/browse/src/main/java/hu/bbara/purefin/feature/browse/home/HomeModels.kt index d74b8885..c861aad6 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/home/HomeModels.kt +++ b/feature/browse/src/main/java/hu/bbara/purefin/feature/browse/home/HomeModels.kt @@ -1,13 +1,13 @@ -package hu.bbara.purefin.feature.shared.home +package hu.bbara.purefin.feature.browse.home -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Series import java.util.UUID import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.model.LibraryKind -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind sealed interface SuggestedItem { @@ -38,9 +38,9 @@ data class SuggestedEpisode ( override val id: UUID = episode.id, override val title: String = episode.title, override val description: String = episode.synopsis, - override val imageUrl: String = JellyfinImageHelper.finishImageUrl( + override val imageUrl: String = ImageUrlBuilder.finishImageUrl( prefixImageUrl = episode.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) ) : SuggestedItem @@ -61,9 +61,9 @@ data class SuggestedSeries ( override val id: UUID = series.id, override val title: String = series.name, override val description: String = series.synopsis, - override val imageUrl: String = JellyfinImageHelper.finishImageUrl( + override val imageUrl: String = ImageUrlBuilder.finishImageUrl( prefixImageUrl = series.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) ) : SuggestedItem @@ -82,9 +82,9 @@ data class SuggestedMovie ( override val id: UUID = movie.id, override val title: String = movie.title, override val description: String = movie.synopsis, - override val imageUrl: String = JellyfinImageHelper.finishImageUrl( + override val imageUrl: String = ImageUrlBuilder.finishImageUrl( prefixImageUrl = movie.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) ) : SuggestedItem @@ -118,13 +118,13 @@ data class ContinueWatchingItem( else -> throw UnsupportedOperationException("Unsupported item type: $type") } override val imageUrl: String = when (type) { - MediaKind.MOVIE -> JellyfinImageHelper.finishImageUrl( + MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = movie!!.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) - MediaKind.EPISODE -> JellyfinImageHelper.finishImageUrl( + MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = episode!!.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) else -> throw IllegalArgumentException("Invalid type: $type") } @@ -147,9 +147,9 @@ data class NextUpItem( override val type: MediaKind get() = MediaKind.EPISODE override val imageUrl: String - get() = JellyfinImageHelper.finishImageUrl( + get() = ImageUrlBuilder.finishImageUrl( prefixImageUrl = episode.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) override val primaryText: String = episode.title override val secondaryText: String = episode.releaseDate @@ -184,17 +184,17 @@ data class PosterItem( else -> throw IllegalArgumentException("Invalid type: $type") } override val imageUrl: String = when (type) { - MediaKind.MOVIE -> JellyfinImageHelper.finishImageUrl( + MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = movie!!.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) - MediaKind.EPISODE -> JellyfinImageHelper.finishImageUrl( + MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = episode!!.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) - MediaKind.SERIES -> JellyfinImageHelper.finishImageUrl( + MediaKind.SERIES -> ImageUrlBuilder.finishImageUrl( prefixImageUrl = series!!.imageUrlPrefix, - imageType = ImageType.PRIMARY + artworkKind = ArtworkKind.PRIMARY ) else -> throw IllegalArgumentException("Invalid type: $type") } diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/library/LibraryViewModel.kt b/feature/browse/src/main/java/hu/bbara/purefin/feature/browse/library/LibraryViewModel.kt similarity index 86% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/library/LibraryViewModel.kt rename to feature/browse/src/main/java/hu/bbara/purefin/feature/browse/library/LibraryViewModel.kt index 05711bb3..9d4e782a 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/library/LibraryViewModel.kt +++ b/feature/browse/src/main/java/hu/bbara/purefin/feature/browse/library/LibraryViewModel.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.library +package hu.bbara.purefin.feature.browse.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -6,11 +6,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.HomeRepository import hu.bbara.purefin.core.model.LibraryKind import hu.bbara.purefin.core.model.MediaKind -import hu.bbara.purefin.feature.shared.home.PosterItem -import hu.bbara.purefin.feature.shared.navigation.MovieDto -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route -import hu.bbara.purefin.feature.shared.navigation.SeriesDto +import hu.bbara.purefin.feature.browse.home.PosterItem +import hu.bbara.purefin.core.navigation.MovieDto +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route +import hu.bbara.purefin.core.navigation.SeriesDto import java.util.UUID import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/feature/content/build.gradle.kts b/feature/content/build.gradle.kts new file mode 100644 index 00000000..8f105118 --- /dev/null +++ b/feature/content/build.gradle.kts @@ -0,0 +1,40 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.feature.content" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:model")) + implementation(project(":core:data")) + implementation(project(":core:navigation")) + implementation(project(":core:download")) + implementation(libs.androidx.navigation3.runtime) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + testImplementation(libs.junit) +} diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt b/feature/content/src/main/java/hu/bbara/purefin/feature/content/episode/EpisodeScreenViewModel.kt similarity index 88% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt rename to feature/content/src/main/java/hu/bbara/purefin/feature/content/episode/EpisodeScreenViewModel.kt index d12fa2e8..cefb2451 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/episode/EpisodeScreenViewModel.kt +++ b/feature/content/src/main/java/hu/bbara/purefin/feature/content/episode/EpisodeScreenViewModel.kt @@ -1,15 +1,15 @@ -package hu.bbara.purefin.feature.shared.content.episode +package hu.bbara.purefin.feature.content.episode import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.MediaCatalogReader -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.download.MediaDownloadController +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.download.MediaDownloadController import hu.bbara.purefin.core.model.Episode -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route -import hu.bbara.purefin.feature.shared.navigation.SeriesDto +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route +import hu.bbara.purefin.core.navigation.SeriesDto import java.util.UUID import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt b/feature/content/src/main/java/hu/bbara/purefin/feature/content/movie/MovieScreenViewModel.kt similarity index 88% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt rename to feature/content/src/main/java/hu/bbara/purefin/feature/content/movie/MovieScreenViewModel.kt index 489539d2..53d223c1 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/movie/MovieScreenViewModel.kt +++ b/feature/content/src/main/java/hu/bbara/purefin/feature/content/movie/MovieScreenViewModel.kt @@ -1,14 +1,14 @@ -package hu.bbara.purefin.feature.shared.content.movie +package hu.bbara.purefin.feature.content.movie import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.MediaCatalogReader -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.download.MediaDownloadController +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.download.MediaDownloadController import hu.bbara.purefin.core.model.Movie -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route import java.util.UUID import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/series/SeriesViewModel.kt b/feature/content/src/main/java/hu/bbara/purefin/feature/content/series/SeriesViewModel.kt similarity index 92% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/series/SeriesViewModel.kt rename to feature/content/src/main/java/hu/bbara/purefin/feature/content/series/SeriesViewModel.kt index b612de92..ce0dadf0 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/content/series/SeriesViewModel.kt +++ b/feature/content/src/main/java/hu/bbara/purefin/feature/content/series/SeriesViewModel.kt @@ -1,16 +1,16 @@ -package hu.bbara.purefin.feature.shared.content.series +package hu.bbara.purefin.feature.content.series import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.MediaCatalogReader -import hu.bbara.purefin.core.data.download.DownloadState -import hu.bbara.purefin.core.data.download.MediaDownloadController +import hu.bbara.purefin.core.download.DownloadState +import hu.bbara.purefin.core.download.MediaDownloadController import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Series -import hu.bbara.purefin.feature.shared.navigation.EpisodeDto -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route +import hu.bbara.purefin.core.navigation.EpisodeDto +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route import java.util.UUID import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow diff --git a/feature/download/src/main/res/drawable/ic_launcher_foreground.xml b/feature/download/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d11..00000000 --- a/feature/download/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/feature/downloads/build.gradle.kts b/feature/downloads/build.gradle.kts new file mode 100644 index 00000000..e2c78df5 --- /dev/null +++ b/feature/downloads/build.gradle.kts @@ -0,0 +1,42 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.feature.downloads" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:model")) + implementation(project(":core:data")) + implementation(project(":core:image")) + implementation(project(":core:navigation")) + implementation(project(":core:download")) + implementation(project(":feature:browse")) + implementation(libs.androidx.navigation3.runtime) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + testImplementation(libs.junit) +} diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/ActiveDownloadItem.kt b/feature/downloads/src/main/java/hu/bbara/purefin/feature/downloads/ActiveDownloadItem.kt similarity index 76% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/ActiveDownloadItem.kt rename to feature/downloads/src/main/java/hu/bbara/purefin/feature/downloads/ActiveDownloadItem.kt index 322574db..b618cbf3 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/ActiveDownloadItem.kt +++ b/feature/downloads/src/main/java/hu/bbara/purefin/feature/downloads/ActiveDownloadItem.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.download +package hu.bbara.purefin.feature.downloads data class ActiveDownloadItem( val contentId: String, diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt b/feature/downloads/src/main/java/hu/bbara/purefin/feature/downloads/DownloadsViewModel.kt similarity index 82% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt rename to feature/downloads/src/main/java/hu/bbara/purefin/feature/downloads/DownloadsViewModel.kt index 9d6bd8dc..78aa6156 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/download/DownloadsViewModel.kt +++ b/feature/downloads/src/main/java/hu/bbara/purefin/feature/downloads/DownloadsViewModel.kt @@ -1,23 +1,23 @@ -package hu.bbara.purefin.feature.shared.download +package hu.bbara.purefin.feature.downloads import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.OfflineCatalogReader -import hu.bbara.purefin.core.data.download.MediaDownloadController -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.download.MediaDownloadController +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.model.MediaKind -import hu.bbara.purefin.feature.shared.navigation.MovieDto -import hu.bbara.purefin.feature.shared.navigation.NavigationManager -import hu.bbara.purefin.feature.shared.navigation.Route -import hu.bbara.purefin.feature.shared.navigation.SeriesDto -import hu.bbara.purefin.feature.shared.home.PosterItem +import hu.bbara.purefin.core.navigation.MovieDto +import hu.bbara.purefin.core.navigation.NavigationManager +import hu.bbara.purefin.core.navigation.Route +import hu.bbara.purefin.core.navigation.SeriesDto +import hu.bbara.purefin.feature.browse.home.PosterItem import java.util.UUID import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import javax.inject.Inject @HiltViewModel @@ -72,7 +72,7 @@ class DownloadsViewModel @Inject constructor( contentId = contentId, title = movie.title, subtitle = "", - imageUrl = JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.PRIMARY), + imageUrl = ImageUrlBuilder.finishImageUrl(movie.imageUrlPrefix, ArtworkKind.PRIMARY), progress = progress ) } else { @@ -82,7 +82,7 @@ class DownloadsViewModel @Inject constructor( contentId = contentId, title = it.title, subtitle = seriesMap[it.seriesId]?.name ?: "", - imageUrl = JellyfinImageHelper.finishImageUrl(it.imageUrlPrefix, ImageType.PRIMARY), + imageUrl = ImageUrlBuilder.finishImageUrl(it.imageUrlPrefix, ArtworkKind.PRIMARY), progress = progress ) } diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts new file mode 100644 index 00000000..e2aebc18 --- /dev/null +++ b/feature/login/build.gradle.kts @@ -0,0 +1,36 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.feature.login" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:data")) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + testImplementation(libs.junit) +} diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/login/LoginViewModel.kt b/feature/login/src/main/java/hu/bbara/purefin/feature/login/LoginViewModel.kt similarity index 87% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/login/LoginViewModel.kt rename to feature/login/src/main/java/hu/bbara/purefin/feature/login/LoginViewModel.kt index 316e22d1..d17c147e 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/login/LoginViewModel.kt +++ b/feature/login/src/main/java/hu/bbara/purefin/feature/login/LoginViewModel.kt @@ -1,9 +1,9 @@ -package hu.bbara.purefin.feature.shared.login +package hu.bbara.purefin.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import hu.bbara.purefin.core.data.client.JellyfinApiClient +import hu.bbara.purefin.core.data.AuthenticationRepository import hu.bbara.purefin.core.data.session.UserSessionRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -15,7 +15,7 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val userSessionRepository: UserSessionRepository, - private val jellyfinApiClient: JellyfinApiClient, + private val authenticationRepository: AuthenticationRepository, ) : ViewModel() { private val _username = MutableStateFlow("") val username: StateFlow = _username.asStateFlow() @@ -57,7 +57,7 @@ class LoginViewModel @Inject constructor( suspend fun login(): Boolean { _errorMessage.value = null userSessionRepository.setServerUrl(url.value) - val success = jellyfinApiClient.login(url.value, username.value, password.value) + val success = authenticationRepository.login(url.value, username.value, password.value) if (!success) { _errorMessage.value = "Login failed. Check your server URL, username, and password." } diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts new file mode 100644 index 00000000..9691f8f5 --- /dev/null +++ b/feature/search/build.gradle.kts @@ -0,0 +1,38 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt) + alias(libs.plugins.ksp) +} + +android { + namespace = "hu.bbara.purefin.feature.search" + compileSdk = 36 + + defaultConfig { + minSdk = 29 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":core:model")) + implementation(project(":core:data")) + implementation(project(":core:image")) + implementation(libs.hilt) + ksp(libs.hilt.compiler) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + testImplementation(libs.junit) +} diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/search/SearchResult.kt b/feature/search/src/main/java/hu/bbara/purefin/feature/search/SearchResult.kt similarity index 94% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/search/SearchResult.kt rename to feature/search/src/main/java/hu/bbara/purefin/feature/search/SearchResult.kt index cc1144dd..e5ba1ccd 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/search/SearchResult.kt +++ b/feature/search/src/main/java/hu/bbara/purefin/feature/search/SearchResult.kt @@ -1,4 +1,4 @@ -package hu.bbara.purefin.feature.shared.search +package hu.bbara.purefin.feature.search import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.model.Movie diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/search/SearchViewModel.kt b/feature/search/src/main/java/hu/bbara/purefin/feature/search/SearchViewModel.kt similarity index 87% rename from feature/shared/src/main/java/hu/bbara/purefin/feature/shared/search/SearchViewModel.kt rename to feature/search/src/main/java/hu/bbara/purefin/feature/search/SearchViewModel.kt index f5c72085..233b8aee 100644 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/search/SearchViewModel.kt +++ b/feature/search/src/main/java/hu/bbara/purefin/feature/search/SearchViewModel.kt @@ -1,10 +1,10 @@ -package hu.bbara.purefin.feature.shared.search +package hu.bbara.purefin.feature.search import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import hu.bbara.purefin.core.data.MediaCatalogReader -import hu.bbara.purefin.core.data.image.JellyfinImageHelper +import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.data.session.UserSessionRepository import java.util.UUID import kotlinx.coroutines.FlowPreview @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn -import org.jellyfin.sdk.model.api.ImageType +import hu.bbara.purefin.core.image.ArtworkKind import javax.inject.Inject @HiltViewModel @@ -55,7 +55,7 @@ class SearchViewModel @Inject constructor( } private suspend fun createImageUrl(id: UUID) : String { - return JellyfinImageHelper.toImageUrl(userSessionRepository.serverUrl.first(), id, - ImageType.PRIMARY) + return ImageUrlBuilder.toImageUrl(userSessionRepository.serverUrl.first(), id, + ArtworkKind.PRIMARY) } } diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/EpisodeDto.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/EpisodeDto.kt deleted file mode 100644 index 32fa1473..00000000 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/EpisodeDto.kt +++ /dev/null @@ -1,15 +0,0 @@ -package hu.bbara.purefin.feature.shared.navigation - -import java.util.UUID -import kotlinx.serialization.Serializable -import org.jellyfin.sdk.model.serializer.UUIDSerializer - -@Serializable -data class EpisodeDto( - @Serializable(with = UUIDSerializer::class) - val id: UUID, - @Serializable(with = UUIDSerializer::class) - val seasonId: UUID, - @Serializable(with = UUIDSerializer::class) - val seriesId: UUID, -) diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/LibraryDto.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/LibraryDto.kt deleted file mode 100644 index 13dbbb85..00000000 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/LibraryDto.kt +++ /dev/null @@ -1,12 +0,0 @@ -package hu.bbara.purefin.feature.shared.navigation - -import java.util.UUID -import kotlinx.serialization.Serializable -import org.jellyfin.sdk.model.serializer.UUIDSerializer - -@Serializable -data class LibraryDto( - @Serializable(with = UUIDSerializer::class) - val id: UUID, - val name: String, -) diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/MovieDto.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/MovieDto.kt deleted file mode 100644 index 4498b4a1..00000000 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/MovieDto.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hu.bbara.purefin.feature.shared.navigation - -import java.util.UUID -import kotlinx.serialization.Serializable -import org.jellyfin.sdk.model.serializer.UUIDSerializer - -@Serializable -data class MovieDto( - @Serializable(with = UUIDSerializer::class) - val id: UUID, -) diff --git a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/SeriesDto.kt b/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/SeriesDto.kt deleted file mode 100644 index 6c6a72a7..00000000 --- a/feature/shared/src/main/java/hu/bbara/purefin/feature/shared/navigation/SeriesDto.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hu.bbara.purefin.feature.shared.navigation - -import java.util.UUID -import kotlinx.serialization.Serializable -import org.jellyfin.sdk.model.serializer.UUIDSerializer - -@Serializable -data class SeriesDto( - @Serializable(with = UUIDSerializer::class) - val id: UUID, -) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a92b9da8..78166447 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ hiltNavigationCompose = "1.2.0" ksp = "2.3.2" datastore = "1.1.1" kotlinxSerializationJson = "1.7.3" +kotlinxCoroutines = "1.9.0" okhttp = "4.12.0" foundation = "1.10.1" tvMaterial = "1.0.1" @@ -49,6 +50,7 @@ hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", v androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" } diff --git a/settings.gradle.kts b/settings.gradle.kts index f14b3f7b..0b3c6954 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,7 +26,16 @@ rootProject.name = "Purefin" include(":app") include(":app-tv") include(":core:model") +include(":core:image") include(":core:data") -include(":feature:download") +include(":core:navigation") +include(":core:download") include(":core:player") -include(":feature:shared") +include(":data:jellyfin") +include(":data:offline") +include(":data:catalog") +include(":feature:browse") +include(":feature:search") +include(":feature:content") +include(":feature:downloads") +include(":feature:login")