feat: Added library to the database scheme and to the MediaRepository. LibraryViewModel now uses it.

This commit is contained in:
2026-02-18 17:56:29 +01:00
parent e7d2fa3d62
commit a0dbef3dc1
15 changed files with 227 additions and 51 deletions

View File

@@ -17,44 +17,38 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.UUID
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.api.ImageType
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.emptyList
@HiltViewModel @HiltViewModel
class LibraryViewModel @Inject constructor( class LibraryViewModel @Inject constructor(
private val mediaRepository: MediaRepository, private val mediaRepository: MediaRepository,
private val userSessionRepository: UserSessionRepository,
private val jellyfinApiClient: JellyfinApiClient,
private val navigationManager: NavigationManager private val navigationManager: NavigationManager
) : ViewModel() { ) : ViewModel() {
private val _url = userSessionRepository.serverUrl.stateIn( private val selectedLibrary = MutableStateFlow<UUID?>(null)
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = ""
)
private val _libraryItems = MutableStateFlow<List<Media>>(emptyList()) val contents: StateFlow<List<PosterItem>> = combine(selectedLibrary, mediaRepository.libraries) {
libraryId, libraries ->
val contents: StateFlow<List<PosterItem>> = combine( if (libraryId == null) {
_libraryItems, return@combine emptyList()
mediaRepository.movies, }
mediaRepository.series val library = libraries.find { it.id == libraryId } ?: return@combine emptyList()
) { items, moviesMap, seriesMap -> when (library.type) {
items.mapNotNull { media -> CollectionType.TVSHOWS -> library.series!!.map { series ->
when (media) { PosterItem(type = BaseItemKind.SERIES, series = series)
is Media.MovieMedia -> moviesMap[media.movieId]?.let {
PosterItem(type = BaseItemKind.MOVIE, movie = it)
}
is Media.SeriesMedia -> seriesMap[media.seriesId]?.let {
PosterItem(type = BaseItemKind.SERIES, series = it)
}
else -> null
} }
CollectionType.MOVIES -> library.movies!!.map { movie ->
PosterItem(type = BaseItemKind.MOVIE, movie = movie)
}
else -> emptyList()
} }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
@@ -86,22 +80,7 @@ class LibraryViewModel @Inject constructor(
fun selectLibrary(libraryId: UUID) { fun selectLibrary(libraryId: UUID) {
viewModelScope.launch { viewModelScope.launch {
val libraryItems = jellyfinApiClient.getLibraryContent(libraryId) selectedLibrary.value = libraryId
_libraryItems.value = libraryItems.map {
when (it.type) {
BaseItemKind.MOVIE -> Media.MovieMedia(movieId = it.id)
BaseItemKind.SERIES -> Media.SeriesMedia(seriesId = it.id)
else -> throw UnsupportedOperationException("Unsupported item type: ${it.type}")
}
}
} }
} }
fun getImageUrl(itemId: UUID, type: ImageType): String {
return JellyfinImageHelper.toImageUrl(
url = _url.value,
itemId = itemId,
type = type
)
}
} }

View File

@@ -3,6 +3,7 @@ package hu.bbara.purefin.data
import hu.bbara.purefin.data.local.room.OfflineRepository import hu.bbara.purefin.data.local.room.OfflineRepository
import hu.bbara.purefin.data.local.room.OnlineRepository import hu.bbara.purefin.data.local.room.OnlineRepository
import hu.bbara.purefin.data.model.Episode import hu.bbara.purefin.data.model.Episode
import hu.bbara.purefin.data.model.Library
import hu.bbara.purefin.data.model.Media import hu.bbara.purefin.data.model.Media
import hu.bbara.purefin.data.model.Movie import hu.bbara.purefin.data.model.Movie
import hu.bbara.purefin.data.model.Series import hu.bbara.purefin.data.model.Series
@@ -44,6 +45,10 @@ class ActiveMediaRepository @Inject constructor(
.stateIn(scope, SharingStarted.Eagerly, onlineRepository) .stateIn(scope, SharingStarted.Eagerly, onlineRepository)
// Delegate all MediaRepository interface methods to the active repository // Delegate all MediaRepository interface methods to the active repository
override val libraries: StateFlow<List<Library>> =
activeRepository.flatMapLatest { it.libraries }
.stateIn(scope, SharingStarted.Eagerly, emptyList())
override val movies: StateFlow<Map<UUID, Movie>> = override val movies: StateFlow<Map<UUID, Movie>> =
activeRepository.flatMapLatest { it.movies } activeRepository.flatMapLatest { it.movies }
.stateIn(scope, SharingStarted.Eagerly, emptyMap()) .stateIn(scope, SharingStarted.Eagerly, emptyMap())

View File

@@ -47,6 +47,8 @@ class InMemoryMediaRepository @Inject constructor(
private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Loading) private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Loading)
override val state: StateFlow<MediaRepositoryState> = _state.asStateFlow() override val state: StateFlow<MediaRepositoryState> = _state.asStateFlow()
override val libraries: StateFlow<List<Library>> = localDataSource.librariesFlow
.stateIn(scope, SharingStarted.Eagerly, emptyList())
override val movies: StateFlow<Map<UUID, Movie>> = localDataSource.moviesFlow override val movies: StateFlow<Map<UUID, Movie>> = localDataSource.moviesFlow
.stateIn(scope, SharingStarted.Eagerly, emptyMap()) .stateIn(scope, SharingStarted.Eagerly, emptyMap())
@@ -108,6 +110,8 @@ class InMemoryMediaRepository @Inject constructor(
val emptyLibraries = filteredLibraries.map { val emptyLibraries = filteredLibraries.map {
it.toLibrary() it.toLibrary()
} }
localDataSource.saveLibraries(emptyLibraries)
val filledLibraries = emptyLibraries.map { library -> val filledLibraries = emptyLibraries.map { library ->
return@map loadLibrary(library) return@map loadLibrary(library)
} }

View File

@@ -1,6 +1,7 @@
package hu.bbara.purefin.data package hu.bbara.purefin.data
import hu.bbara.purefin.data.model.Episode import hu.bbara.purefin.data.model.Episode
import hu.bbara.purefin.data.model.Library
import hu.bbara.purefin.data.model.Media import hu.bbara.purefin.data.model.Media
import hu.bbara.purefin.data.model.Movie import hu.bbara.purefin.data.model.Movie
import hu.bbara.purefin.data.model.Series import hu.bbara.purefin.data.model.Series
@@ -10,6 +11,7 @@ import java.util.UUID
interface MediaRepository { interface MediaRepository {
val libraries: StateFlow<List<Library>>
val movies: StateFlow<Map<UUID, Movie>> val movies: StateFlow<Map<UUID, Movie>>
val series: StateFlow<Map<UUID, Series>> val series: StateFlow<Map<UUID, Series>>
val episodes: StateFlow<Map<UUID, Episode>> val episodes: StateFlow<Map<UUID, Episode>>

View File

@@ -3,6 +3,7 @@ package hu.bbara.purefin.data
import hu.bbara.purefin.data.local.room.OfflineDatabase import hu.bbara.purefin.data.local.room.OfflineDatabase
import hu.bbara.purefin.data.local.room.OfflineRoomMediaLocalDataSource import hu.bbara.purefin.data.local.room.OfflineRoomMediaLocalDataSource
import hu.bbara.purefin.data.model.Episode import hu.bbara.purefin.data.model.Episode
import hu.bbara.purefin.data.model.Library
import hu.bbara.purefin.data.model.Media import hu.bbara.purefin.data.model.Media
import hu.bbara.purefin.data.model.Movie import hu.bbara.purefin.data.model.Movie
import hu.bbara.purefin.data.model.Series import hu.bbara.purefin.data.model.Series
@@ -34,6 +35,9 @@ class OfflineMediaRepository @Inject constructor(
private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Ready) private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Ready)
override val state: StateFlow<MediaRepositoryState> = _state.asStateFlow() override val state: StateFlow<MediaRepositoryState> = _state.asStateFlow()
override val libraries: StateFlow<List<Library>> = localDataSource.librariesFlow
.stateIn(scope, SharingStarted.Eagerly, emptyList())
override val movies: StateFlow<Map<UUID, Movie>> = localDataSource.moviesFlow override val movies: StateFlow<Map<UUID, Movie>> = localDataSource.moviesFlow
.stateIn(scope, SharingStarted.Eagerly, emptyMap()) .stateIn(scope, SharingStarted.Eagerly, emptyMap())

View File

@@ -0,0 +1,13 @@
package hu.bbara.purefin.data.local.room
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.UUID
@Entity(tableName = "library")
data class LibraryEntity (
@PrimaryKey
val id: UUID,
val name: String,
val type: String,
)

View File

@@ -8,16 +8,17 @@ import hu.bbara.purefin.data.local.room.dao.EpisodeDao
import hu.bbara.purefin.data.local.room.dao.MovieDao import hu.bbara.purefin.data.local.room.dao.MovieDao
import hu.bbara.purefin.data.local.room.dao.SeasonDao import hu.bbara.purefin.data.local.room.dao.SeasonDao
import hu.bbara.purefin.data.local.room.dao.SeriesDao import hu.bbara.purefin.data.local.room.dao.SeriesDao
import hu.bbara.purefin.data.local.room.dao.LibraryDao
@Database( @Database(
entities = [ entities = [
MovieEntity::class, MovieEntity::class,
SeriesEntity::class, SeriesEntity::class,
SeasonEntity::class, SeasonEntity::class,
EpisodeEntity::class, EpisodeEntity::class,
LibraryEntity::class,
CastMemberEntity::class CastMemberEntity::class
], ],
version = 1, version = 3,
exportSchema = false exportSchema = false
) )
@TypeConverters(UuidConverters::class) @TypeConverters(UuidConverters::class)
@@ -26,5 +27,6 @@ abstract class MediaDatabase : RoomDatabase() {
abstract fun seriesDao(): SeriesDao abstract fun seriesDao(): SeriesDao
abstract fun seasonDao(): SeasonDao abstract fun seasonDao(): SeasonDao
abstract fun episodeDao(): EpisodeDao abstract fun episodeDao(): EpisodeDao
abstract fun libraryDao(): LibraryDao
abstract fun castMemberDao(): CastMemberDao abstract fun castMemberDao(): CastMemberDao
} }

View File

@@ -9,6 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import hu.bbara.purefin.data.local.room.dao.CastMemberDao import hu.bbara.purefin.data.local.room.dao.CastMemberDao
import hu.bbara.purefin.data.local.room.dao.EpisodeDao import hu.bbara.purefin.data.local.room.dao.EpisodeDao
import hu.bbara.purefin.data.local.room.dao.LibraryDao
import hu.bbara.purefin.data.local.room.dao.MovieDao import hu.bbara.purefin.data.local.room.dao.MovieDao
import hu.bbara.purefin.data.local.room.dao.SeasonDao import hu.bbara.purefin.data.local.room.dao.SeasonDao
import hu.bbara.purefin.data.local.room.dao.SeriesDao import hu.bbara.purefin.data.local.room.dao.SeriesDao
@@ -47,6 +48,10 @@ object MediaDatabaseModule {
@OnlineDatabase @OnlineDatabase
fun provideOnlineCastMemberDao(@OnlineDatabase db: MediaDatabase) = db.castMemberDao() fun provideOnlineCastMemberDao(@OnlineDatabase db: MediaDatabase) = db.castMemberDao()
@Provides
@OnlineDatabase
fun provideOnlineLibraryDao(@OnlineDatabase db: MediaDatabase) = db.libraryDao()
// Offline Database and DAOs // Offline Database and DAOs
@Provides @Provides
@Singleton @Singleton
@@ -76,6 +81,10 @@ object MediaDatabaseModule {
@OfflineDatabase @OfflineDatabase
fun provideOfflineCastMemberDao(@OfflineDatabase db: OfflineMediaDatabase) = db.castMemberDao() fun provideOfflineCastMemberDao(@OfflineDatabase db: OfflineMediaDatabase) = db.castMemberDao()
@Provides
@OfflineDatabase
fun provideOfflineLibraryDao(@OfflineDatabase db: OfflineMediaDatabase) = db.libraryDao()
// Data Sources // Data Sources
@Provides @Provides
@Singleton @Singleton
@@ -86,9 +95,10 @@ object MediaDatabaseModule {
@OnlineDatabase seriesDao: SeriesDao, @OnlineDatabase seriesDao: SeriesDao,
@OnlineDatabase seasonDao: SeasonDao, @OnlineDatabase seasonDao: SeasonDao,
@OnlineDatabase episodeDao: EpisodeDao, @OnlineDatabase episodeDao: EpisodeDao,
@OnlineDatabase castMemberDao: CastMemberDao @OnlineDatabase castMemberDao: CastMemberDao,
@OnlineDatabase libraryDao: LibraryDao
): RoomMediaLocalDataSource = RoomMediaLocalDataSource( ): RoomMediaLocalDataSource = RoomMediaLocalDataSource(
database, movieDao, seriesDao, seasonDao, episodeDao, castMemberDao database, movieDao, seriesDao, seasonDao, episodeDao, castMemberDao, libraryDao
) )
@Provides @Provides
@@ -100,9 +110,10 @@ object MediaDatabaseModule {
@OfflineDatabase seriesDao: SeriesDao, @OfflineDatabase seriesDao: SeriesDao,
@OfflineDatabase seasonDao: SeasonDao, @OfflineDatabase seasonDao: SeasonDao,
@OfflineDatabase episodeDao: EpisodeDao, @OfflineDatabase episodeDao: EpisodeDao,
@OfflineDatabase castMemberDao: CastMemberDao @OfflineDatabase castMemberDao: CastMemberDao,
@OfflineDatabase libraryDao: LibraryDao
): OfflineRoomMediaLocalDataSource = OfflineRoomMediaLocalDataSource( ): OfflineRoomMediaLocalDataSource = OfflineRoomMediaLocalDataSource(
database, movieDao, seriesDao, seasonDao, episodeDao, castMemberDao database, movieDao, seriesDao, seasonDao, episodeDao, castMemberDao, libraryDao
) )
// Default (unqualified) data source for backward compatibility // Default (unqualified) data source for backward compatibility

View File

@@ -1,10 +1,22 @@
package hu.bbara.purefin.data.local.room package hu.bbara.purefin.data.local.room
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.util.UUID import java.util.UUID
@Entity(tableName = "movies") @Entity(
tableName = "movies",
foreignKeys = [
ForeignKey(
entity = LibraryEntity::class,
parentColumns = ["id"],
childColumns = ["libraryId"],
),
],
indices = [Index("libraryId")]
)
data class MovieEntity( data class MovieEntity(
@PrimaryKey val id: UUID, @PrimaryKey val id: UUID,
val libraryId: UUID, val libraryId: UUID,

View File

@@ -8,6 +8,7 @@ import hu.bbara.purefin.data.local.room.dao.EpisodeDao
import hu.bbara.purefin.data.local.room.dao.MovieDao import hu.bbara.purefin.data.local.room.dao.MovieDao
import hu.bbara.purefin.data.local.room.dao.SeasonDao import hu.bbara.purefin.data.local.room.dao.SeasonDao
import hu.bbara.purefin.data.local.room.dao.SeriesDao import hu.bbara.purefin.data.local.room.dao.SeriesDao
import hu.bbara.purefin.data.local.room.dao.LibraryDao
@Database( @Database(
entities = [ entities = [
@@ -15,9 +16,10 @@ import hu.bbara.purefin.data.local.room.dao.SeriesDao
SeriesEntity::class, SeriesEntity::class,
SeasonEntity::class, SeasonEntity::class,
EpisodeEntity::class, EpisodeEntity::class,
LibraryEntity::class,
CastMemberEntity::class CastMemberEntity::class
], ],
version = 1, version = 3,
exportSchema = false exportSchema = false
) )
@TypeConverters(UuidConverters::class) @TypeConverters(UuidConverters::class)
@@ -26,5 +28,6 @@ abstract class OfflineMediaDatabase : RoomDatabase() {
abstract fun seriesDao(): SeriesDao abstract fun seriesDao(): SeriesDao
abstract fun seasonDao(): SeasonDao abstract fun seasonDao(): SeasonDao
abstract fun episodeDao(): EpisodeDao abstract fun episodeDao(): EpisodeDao
abstract fun libraryDao(): LibraryDao
abstract fun castMemberDao(): CastMemberDao abstract fun castMemberDao(): CastMemberDao
} }

View File

@@ -3,16 +3,19 @@ package hu.bbara.purefin.data.local.room
import androidx.room.withTransaction import androidx.room.withTransaction
import hu.bbara.purefin.data.local.room.dao.CastMemberDao import hu.bbara.purefin.data.local.room.dao.CastMemberDao
import hu.bbara.purefin.data.local.room.dao.EpisodeDao import hu.bbara.purefin.data.local.room.dao.EpisodeDao
import hu.bbara.purefin.data.local.room.dao.LibraryDao
import hu.bbara.purefin.data.local.room.dao.MovieDao import hu.bbara.purefin.data.local.room.dao.MovieDao
import hu.bbara.purefin.data.local.room.dao.SeasonDao import hu.bbara.purefin.data.local.room.dao.SeasonDao
import hu.bbara.purefin.data.local.room.dao.SeriesDao import hu.bbara.purefin.data.local.room.dao.SeriesDao
import hu.bbara.purefin.data.model.CastMember import hu.bbara.purefin.data.model.CastMember
import hu.bbara.purefin.data.model.Episode import hu.bbara.purefin.data.model.Episode
import hu.bbara.purefin.data.model.Library
import hu.bbara.purefin.data.model.Movie import hu.bbara.purefin.data.model.Movie
import hu.bbara.purefin.data.model.Season import hu.bbara.purefin.data.model.Season
import hu.bbara.purefin.data.model.Series import hu.bbara.purefin.data.model.Series
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.jellyfin.sdk.model.api.CollectionType
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -24,10 +27,21 @@ class OfflineRoomMediaLocalDataSource(
private val seriesDao: SeriesDao, private val seriesDao: SeriesDao,
private val seasonDao: SeasonDao, private val seasonDao: SeasonDao,
private val episodeDao: EpisodeDao, private val episodeDao: EpisodeDao,
private val castMemberDao: CastMemberDao private val castMemberDao: CastMemberDao,
private val libraryDao: LibraryDao
) { ) {
// Lightweight Flows for list screens (home, library) // Lightweight Flows for list screens (home, library)
val librariesFlow: Flow<List<Library>> = libraryDao.observeAllWithContent()
.map { relation ->
relation.map {
it.library.toDomain(
movies = it.movies.map { e -> e.toDomain(cast = emptyList()) },
series = it.series.map { e -> e.toDomain(seasons = emptyList(), cast = emptyList()) }
)
}
}
val moviesFlow: Flow<Map<UUID, Movie>> = movieDao.observeAll() val moviesFlow: Flow<Map<UUID, Movie>> = movieDao.observeAll()
.map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } } .map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } }
@@ -54,6 +68,13 @@ class OfflineRoomMediaLocalDataSource(
} }
} }
suspend fun saveLibraries(libraries: List<Library>) {
database.withTransaction {
libraryDao.deleteAll()
libraryDao.upsertAll(libraries.map { it.toEntity() })
}
}
suspend fun saveMovies(movies: List<Movie>) { suspend fun saveMovies(movies: List<Movie>) {
database.withTransaction { database.withTransaction {
movieDao.upsertAll(movies.map { it.toEntity() }) movieDao.upsertAll(movies.map { it.toEntity() })
@@ -184,6 +205,28 @@ class OfflineRoomMediaLocalDataSource(
} }
} }
private fun Library.toEntity() = LibraryEntity(
id = id,
name = name,
type = when (type) {
CollectionType.MOVIES -> "MOVIES"
CollectionType.TVSHOWS -> "TVSHOWS"
else -> throw UnsupportedOperationException("Unsupported library type: $type")
}
)
private fun LibraryEntity.toDomain(series: List<Series>, movies: List<Movie>) = Library(
id = id,
name = name,
type = when (type) {
"MOVIES" -> CollectionType.MOVIES
"TVSHOWS" -> CollectionType.TVSHOWS
else -> throw UnsupportedOperationException("Unsupported library type: $type")
},
movies = if (type == "MOVIES") movies else null,
series = if (type == "TVSHOWS") series else null,
)
private fun Movie.toEntity() = MovieEntity( private fun Movie.toEntity() = MovieEntity(
id = id, id = id,
libraryId = libraryId, libraryId = libraryId,

View File

@@ -3,19 +3,22 @@ package hu.bbara.purefin.data.local.room
import androidx.room.withTransaction import androidx.room.withTransaction
import hu.bbara.purefin.data.local.room.dao.CastMemberDao import hu.bbara.purefin.data.local.room.dao.CastMemberDao
import hu.bbara.purefin.data.local.room.dao.EpisodeDao import hu.bbara.purefin.data.local.room.dao.EpisodeDao
import hu.bbara.purefin.data.local.room.dao.LibraryDao
import hu.bbara.purefin.data.local.room.dao.MovieDao import hu.bbara.purefin.data.local.room.dao.MovieDao
import hu.bbara.purefin.data.local.room.dao.SeasonDao import hu.bbara.purefin.data.local.room.dao.SeasonDao
import hu.bbara.purefin.data.local.room.dao.SeriesDao import hu.bbara.purefin.data.local.room.dao.SeriesDao
import hu.bbara.purefin.data.model.CastMember import hu.bbara.purefin.data.model.CastMember
import hu.bbara.purefin.data.model.Episode import hu.bbara.purefin.data.model.Episode
import hu.bbara.purefin.data.model.Library
import hu.bbara.purefin.data.model.Movie import hu.bbara.purefin.data.model.Movie
import hu.bbara.purefin.data.model.Season import hu.bbara.purefin.data.model.Season
import hu.bbara.purefin.data.model.Series import hu.bbara.purefin.data.model.Series
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.jellyfin.sdk.model.api.CollectionType
import java.util.UUID import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.collections.map
@Singleton @Singleton
class RoomMediaLocalDataSource( class RoomMediaLocalDataSource(
@@ -24,10 +27,21 @@ class RoomMediaLocalDataSource(
private val seriesDao: SeriesDao, private val seriesDao: SeriesDao,
private val seasonDao: SeasonDao, private val seasonDao: SeasonDao,
private val episodeDao: EpisodeDao, private val episodeDao: EpisodeDao,
private val castMemberDao: CastMemberDao private val castMemberDao: CastMemberDao,
private val libraryDao: LibraryDao
) { ) {
// Lightweight Flows for list screens (home, library) // Lightweight Flows for list screens (home, library)
val librariesFlow: Flow<List<Library>> = libraryDao.observeAllWithContent()
.map { relation ->
relation.map { libraryEntity ->
libraryEntity.library.toDomain(
movies = libraryEntity.movies.map { it.toDomain(listOf()) },
series = libraryEntity.series.map { it.toDomain(listOf(), listOf()) }
)
}
}
val moviesFlow: Flow<Map<UUID, Movie>> = movieDao.observeAll() val moviesFlow: Flow<Map<UUID, Movie>> = movieDao.observeAll()
.map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } } .map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } }
@@ -54,6 +68,13 @@ class RoomMediaLocalDataSource(
} }
} }
suspend fun saveLibraries(libraries: List<Library>) {
database.withTransaction {
libraryDao.deleteAll()
libraryDao.upsertAll(libraries.map { it.toEntity() })
}
}
suspend fun saveMovies(movies: List<Movie>) { suspend fun saveMovies(movies: List<Movie>) {
database.withTransaction { database.withTransaction {
movieDao.upsertAll(movies.map { it.toEntity() }) movieDao.upsertAll(movies.map { it.toEntity() })
@@ -184,6 +205,28 @@ class RoomMediaLocalDataSource(
} }
} }
private fun Library.toEntity() = LibraryEntity(
id = id,
name = name,
type = when (type) {
CollectionType.MOVIES -> "MOVIES"
CollectionType.TVSHOWS -> "TVSHOWS"
else -> throw UnsupportedOperationException("Unsupported library type: $type")
}
)
private fun LibraryEntity.toDomain(series: List<Series>, movies: List<Movie>) = Library(
id = id,
name = name,
type = when (type) {
"MOVIES" -> CollectionType.MOVIES
"TVSHOWS" -> CollectionType.TVSHOWS
else -> throw UnsupportedOperationException("Unsupported library type: $type")
},
movies = if (type == "MOVIES") movies else null,
series = if (type == "TVSHOWS") series else null,
)
private fun Movie.toEntity() = MovieEntity( private fun Movie.toEntity() = MovieEntity(
id = id, id = id,
libraryId = libraryId, libraryId = libraryId,

View File

@@ -21,3 +21,17 @@ data class SeriesWithSeasonsAndEpisodes(
) )
val seasons: List<SeasonWithEpisodes> val seasons: List<SeasonWithEpisodes>
) )
data class LibraryWithContent(
@Embedded val library: LibraryEntity,
@Relation(
parentColumn = "id",
entityColumn = "libraryId"
)
val series: List<SeriesEntity>,
@Relation(
parentColumn = "id",
entityColumn = "libraryId"
)
val movies: List<MovieEntity>
)

View File

@@ -1,10 +1,22 @@
package hu.bbara.purefin.data.local.room package hu.bbara.purefin.data.local.room
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.util.UUID import java.util.UUID
@Entity(tableName = "series") @Entity(
tableName = "series",
foreignKeys = [
ForeignKey(
entity = LibraryEntity::class,
parentColumns = ["id"],
childColumns = ["libraryId"],
),
],
indices = [Index("libraryId")]
)
data class SeriesEntity( data class SeriesEntity(
@PrimaryKey val id: UUID, @PrimaryKey val id: UUID,
val libraryId: UUID, val libraryId: UUID,

View File

@@ -0,0 +1,29 @@
package hu.bbara.purefin.data.local.room.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import hu.bbara.purefin.data.local.room.LibraryEntity
import hu.bbara.purefin.data.local.room.LibraryWithContent
import kotlinx.coroutines.flow.Flow
@Dao
interface LibraryDao {
@Upsert
suspend fun upsert(library: LibraryEntity)
@Upsert
suspend fun upsertAll(libraries: List<LibraryEntity>)
@Query("SELECT * FROM library")
fun observeAll(): Flow<List<LibraryEntity>>
@Query("SELECT * FROM library")
fun observeAllWithContent(): Flow<List<LibraryWithContent>>
@Query("SELECT * FROM library")
suspend fun getAll(): List<LibraryEntity>
@Query("DELETE FROM library")
suspend fun deleteAll()
}