refactor: change dependency structure. core should not depend on data. Move repository interfaces to core. Move hilt bindings in the data model.

This commit is contained in:
2026-04-24 19:02:26 +02:00
parent 354314710b
commit 2433ececac
44 changed files with 73 additions and 53 deletions

View File

@@ -30,6 +30,7 @@ kotlin {
dependencies {
implementation(project(":core-model"))
implementation(project(":core"))
implementation(libs.media3.common)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.jellyfin.core)

View File

@@ -1,5 +0,0 @@
package hu.bbara.purefin.data
interface AuthenticationRepository {
suspend fun login(url: String, username: String, password: String): Boolean
}

View File

@@ -1,32 +0,0 @@
package hu.bbara.purefin.data
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Season
import hu.bbara.purefin.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<UUID>,
limit: Int,
): List<UUID>
}
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?,
)

View File

@@ -1,16 +0,0 @@
package hu.bbara.purefin.data
import hu.bbara.purefin.model.Library
import hu.bbara.purefin.model.Media
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
interface HomeRepository {
val libraries: StateFlow<List<Library>>
val suggestions: StateFlow<List<Media>>
val continueWatching: StateFlow<List<Media>>
val nextUp: StateFlow<List<Media>>
val latestLibraryContent: StateFlow<Map<UUID, List<Media>>>
fun ensureReady()
fun refreshHomeData()
}

View File

@@ -1,16 +0,0 @@
package hu.bbara.purefin.data
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
//TODO rename MediaRepository
interface MediaCatalogReader {
val movies: StateFlow<Map<UUID, Movie>>
val series: StateFlow<Map<UUID, Series>>
val episodes: StateFlow<Map<UUID, Episode>>
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?>
}

View File

@@ -1,7 +0,0 @@
package hu.bbara.purefin.data
import java.util.UUID
interface MediaProgressWriter {
suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long)
}

View File

@@ -1,7 +0,0 @@
package hu.bbara.purefin.data
import kotlinx.coroutines.flow.Flow
interface NetworkMonitor {
val isOnline: Flow<Boolean>
}

View File

@@ -1,13 +0,0 @@
package hu.bbara.purefin.data
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series
import java.util.UUID
import kotlinx.coroutines.flow.StateFlow
interface OfflineCatalogReader {
val movies: StateFlow<Map<UUID, Movie>>
val series: StateFlow<Map<UUID, Series>>
val episodes: StateFlow<Map<UUID, Episode>>
}

View File

@@ -1,19 +0,0 @@
package hu.bbara.purefin.data
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Season
import hu.bbara.purefin.model.Series
import java.util.UUID
interface OfflineCatalogStore {
suspend fun saveMovies(movies: List<Movie>)
suspend fun saveSeries(series: List<Series>)
suspend fun saveSeason(season: Season)
suspend fun saveEpisode(episode: Episode)
suspend fun getSeriesBasic(seriesId: UUID): Series?
suspend fun getSeason(seasonId: UUID): Season?
suspend fun deleteMovie(movieId: UUID)
suspend fun deleteEpisodeAndCleanup(episodeId: UUID)
suspend fun getEpisodesBySeries(seriesId: UUID): List<Episode>
}

View File

@@ -1,15 +0,0 @@
package hu.bbara.purefin.data
import androidx.media3.common.MediaItem
import hu.bbara.purefin.model.MediaSegment
import java.util.UUID
interface PlayableMediaRepository {
suspend fun getMediaItem(mediaId: UUID): Pair<MediaItem, Long?>?
suspend fun getMediaSegments(mediaId: UUID): List<MediaSegment>
suspend fun getNextUpMediaItems(
episodeId: UUID,
existingIds: Set<String>,
count: Int = 9,
): List<MediaItem>
}

View File

@@ -1,7 +0,0 @@
package hu.bbara.purefin.data
enum class PlaybackMethod {
DIRECT_PLAY,
DIRECT_STREAM,
TRANSCODE,
}

View File

@@ -1,6 +0,0 @@
package hu.bbara.purefin.data
enum class PlaybackProfileFamily {
MOBILE,
TV,
}

View File

@@ -1,14 +0,0 @@
package hu.bbara.purefin.data
import java.util.UUID
interface PlaybackProgressReporter {
suspend fun reportPlaybackStart(itemId: UUID, positionTicks: Long, reportContext: PlaybackReportContext)
suspend fun reportPlaybackProgress(
itemId: UUID,
positionTicks: Long,
isPaused: Boolean,
reportContext: PlaybackReportContext,
)
suspend fun reportPlaybackStopped(itemId: UUID, positionTicks: Long, reportContext: PlaybackReportContext)
}

View File

@@ -1,10 +0,0 @@
package hu.bbara.purefin.data
data class PlaybackReportContext(
val playMethod: PlaybackMethod,
val mediaSourceId: String?,
val audioStreamIndex: Int?,
val subtitleStreamIndex: Int?,
val liveStreamId: String?,
val playSessionId: String?,
)

View File

@@ -1,5 +0,0 @@
package hu.bbara.purefin.data
interface SessionBootstrapper {
suspend fun initialize()
}

View File

@@ -1,11 +0,0 @@
package hu.bbara.purefin.data
import java.util.UUID
import kotlinx.coroutines.flow.Flow
interface SmartDownloadStore {
suspend fun enable(seriesId: UUID)
suspend fun disable(seriesId: UUID)
fun observe(seriesId: UUID): Flow<Boolean>
suspend fun getEnabledSeriesIds(): List<UUID>
}

View File

@@ -4,7 +4,7 @@ import android.util.Log
import androidx.datastore.core.DataStore
import hu.bbara.purefin.data.HomeRepository
import hu.bbara.purefin.data.NetworkMonitor
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.image.ArtworkKind
import hu.bbara.purefin.image.ImageUrlBuilder
import hu.bbara.purefin.model.Episode

View File

@@ -1,21 +1,13 @@
package hu.bbara.purefin.data.catalog
import hu.bbara.purefin.data.MediaCatalogReader
import hu.bbara.purefin.data.MediaProgressWriter
import hu.bbara.purefin.data.MediaRepository
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import hu.bbara.purefin.image.ImageUrlBuilder
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Season
import hu.bbara.purefin.model.Series
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -28,12 +20,19 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class InMemoryMediaRepository @Inject constructor(
private val userSessionRepository: UserSessionRepository,
private val jellyfinApiClient: JellyfinApiClient,
) : MediaCatalogReader, MediaProgressWriter {
) : MediaRepository {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

View File

@@ -1,15 +1,10 @@
package hu.bbara.purefin.data.catalog
import hu.bbara.purefin.data.MediaCatalogReader
import hu.bbara.purefin.data.MediaProgressWriter
import hu.bbara.purefin.data.OfflineCatalogReader
import hu.bbara.purefin.data.MediaRepository
import hu.bbara.purefin.data.offline.room.offline.OfflineRoomMediaLocalDataSource
import hu.bbara.purefin.model.Episode
import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.model.Series
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -17,11 +12,14 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class OfflineMediaRepository @Inject constructor(
private val localDataSource: OfflineRoomMediaLocalDataSource,
) : OfflineCatalogReader, MediaCatalogReader, MediaProgressWriter {
) : MediaRepository {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override val movies: StateFlow<Map<UUID, Movie>> = localDataSource.moviesFlow

View File

@@ -8,7 +8,7 @@ import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import hu.bbara.purefin.data.PlayableMediaRepository
import hu.bbara.purefin.data.PlaybackReportContext
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.image.ArtworkKind
import hu.bbara.purefin.image.ImageUrlBuilder
import hu.bbara.purefin.model.MediaSegment

View File

@@ -5,7 +5,7 @@ import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext
import hu.bbara.purefin.data.PlaybackMethod
import hu.bbara.purefin.data.PlaybackReportContext
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext

View File

@@ -4,7 +4,7 @@ import hu.bbara.purefin.data.DownloadMediaSourceResolver
import hu.bbara.purefin.data.EpisodeDownloadSource
import hu.bbara.purefin.data.MovieDownloadSource
import hu.bbara.purefin.data.PlaybackMethod
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import hu.bbara.purefin.data.jellyfin.playback.PlaybackDecisionResolver
import hu.bbara.purefin.data.jellyfin.playback.playbackCustomCacheKey

View File

@@ -1,7 +1,7 @@
package hu.bbara.purefin.data.jellyfin.playback
import android.util.Log
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

View File

@@ -2,7 +2,7 @@ package hu.bbara.purefin.data.jellyfin.session
import android.util.Log
import hu.bbara.purefin.data.AuthenticationRepository
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import javax.inject.Inject
import javax.inject.Singleton

View File

@@ -10,7 +10,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import javax.inject.Singleton
@Module

View File

@@ -1,7 +1,7 @@
package hu.bbara.purefin.data.jellyfin.session
import androidx.datastore.core.DataStore
import hu.bbara.purefin.data.session.UserSessionRepository
import hu.bbara.purefin.data.UserSessionRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first

View File

@@ -4,7 +4,12 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import hu.bbara.purefin.data.OfflineCatalogReader
import hu.bbara.purefin.Offline
import hu.bbara.purefin.Online
import hu.bbara.purefin.data.HomeRepository
import hu.bbara.purefin.data.MediaRepository
import hu.bbara.purefin.data.catalog.InMemoryAppContentRepository
import hu.bbara.purefin.data.catalog.InMemoryMediaRepository
import hu.bbara.purefin.data.catalog.OfflineMediaRepository
@Module
@@ -12,5 +17,13 @@ import hu.bbara.purefin.data.catalog.OfflineMediaRepository
abstract class MediaRepositoryModule {
@Binds
abstract fun bindOfflineCatalogReader(impl: OfflineMediaRepository): OfflineCatalogReader
@Online
abstract fun bindOnlineRepository(impl: InMemoryMediaRepository): MediaRepository
@Binds
@Offline
abstract fun bindOfflineRepository(impl: OfflineMediaRepository): MediaRepository
@Binds
abstract fun bindHomeRepository(impl: InMemoryAppContentRepository): HomeRepository
}

View File

@@ -1,21 +0,0 @@
package hu.bbara.purefin.data.session
import java.util.UUID
import kotlinx.coroutines.flow.Flow
interface UserSessionRepository {
val serverUrl: Flow<String>
suspend fun setServerUrl(serverUrl: String)
val accessToken: Flow<String>
suspend fun setAccessToken(accessToken: String)
val userId: Flow<UUID?>
suspend fun setUserId(userId: UUID?)
suspend fun getUserId(): UUID?
val isLoggedIn: Flow<Boolean>
suspend fun setLoggedIn(isLoggedIn: Boolean)
val isOfflineMode: Flow<Boolean>
}

View File

@@ -1,8 +0,0 @@
package hu.bbara.purefin.image
enum class ArtworkKind(
internal val pathSegment: String,
) {
PRIMARY("Primary"),
BACKDROP("Backdrop"),
}

View File

@@ -1,40 +0,0 @@
package hu.bbara.purefin.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()
}
}

View File

@@ -1,20 +0,0 @@
package hu.bbara.purefin.navigation
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
import java.util.UUID
object UuidSerializer : KSerializer<UUID> {
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())
}
}