Collapse shared modules into data domain and ui-common

This commit is contained in:
2026-04-23 20:42:59 +02:00
parent 59a471c535
commit 3f7c1966af
180 changed files with 21 additions and 476 deletions

View File

@@ -9,7 +9,7 @@ plugins {
}
android {
namespace = "hu.bbara.purefin.data.jellyfin"
namespace = "hu.bbara.purefin.data"
compileSdk = 36
defaultConfig {
@@ -29,12 +29,9 @@ kotlin {
}
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.coroutines.core)
implementation(libs.jellyfin.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.slf4j.api)
implementation(libs.hilt)
@@ -42,5 +39,7 @@ dependencies {
implementation(libs.datastore)
implementation(libs.okhttp)
implementation(libs.logging.interceptor)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
testImplementation(libs.junit)
}

View File

@@ -1,41 +0,0 @@
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)
}

View File

@@ -1,41 +0,0 @@
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)
}

View File

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

View File

@@ -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<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

@@ -0,0 +1,7 @@
package hu.bbara.purefin.core.data
import java.util.UUID
interface EpisodeSeriesLookup {
suspend fun preferenceKeyFor(mediaId: UUID): String
}

View File

@@ -0,0 +1,16 @@
package hu.bbara.purefin.core.data
import hu.bbara.purefin.core.model.Library
import hu.bbara.purefin.core.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

@@ -0,0 +1,15 @@
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.Series
import java.util.UUID
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
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

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

View File

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

View File

@@ -0,0 +1,13 @@
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.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

@@ -0,0 +1,19 @@
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 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

@@ -0,0 +1,15 @@
package hu.bbara.purefin.core.data
import androidx.media3.common.MediaItem
import hu.bbara.purefin.core.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

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

View File

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

View File

@@ -0,0 +1,14 @@
package hu.bbara.purefin.core.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

@@ -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?,
)

View File

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

View File

@@ -0,0 +1,11 @@
package hu.bbara.purefin.core.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

@@ -0,0 +1,21 @@
package hu.bbara.purefin.core.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

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

View File

@@ -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()
}
}

View File

@@ -0,0 +1,7 @@
package hu.bbara.purefin.core.model
data class CastMember(
val name: String,
val role: String,
val imageUrl: String?
)

View File

@@ -0,0 +1,20 @@
package hu.bbara.purefin.core.model
import java.util.UUID
data class Episode(
val id: UUID,
val seriesId: UUID,
val seasonId: UUID,
val index: Int,
val title: String,
val synopsis: String,
val releaseDate: String,
val rating: String,
val runtime: String,
val progress: Double?,
val watched: Boolean,
val format: String,
val imageUrlPrefix: String,
val cast: List<CastMember>
)

View File

@@ -0,0 +1,18 @@
package hu.bbara.purefin.core.model
import java.util.UUID
data class Library(
val id: UUID,
val name: String,
val type: LibraryKind,
val posterUrl: String,
val series: List<Series>? = null,
val movies: List<Movie>? = null,
) {
init {
require(series != null || movies != null) { "Either series or movie must be provided" }
require(series == null || movies == null) { "Only one of series or movie can be provided" }
require(type == LibraryKind.SERIES || type == LibraryKind.MOVIES) { "Invalid type: $type" }
}
}

View File

@@ -0,0 +1,6 @@
package hu.bbara.purefin.core.model
enum class LibraryKind {
MOVIES,
SERIES,
}

View File

@@ -0,0 +1,13 @@
package hu.bbara.purefin.core.model
import java.util.UUID
sealed class Media(
val id: UUID,
val type: MediaKind,
) {
class MovieMedia(val movieId: UUID) : Media(movieId, MediaKind.MOVIE)
class SeriesMedia(val seriesId: UUID) : Media(seriesId, MediaKind.SERIES)
class SeasonMedia(val seasonId: UUID, val seriesId: UUID) : Media(seasonId, MediaKind.SEASON)
class EpisodeMedia(val episodeId: UUID, val seriesId: UUID) : Media(episodeId, MediaKind.EPISODE)
}

View File

@@ -0,0 +1,8 @@
package hu.bbara.purefin.core.model
enum class MediaKind {
MOVIE,
SERIES,
SEASON,
EPISODE,
}

View File

@@ -0,0 +1,23 @@
package hu.bbara.purefin.core.model
import java.util.UUID
enum class SegmentType(val value: Int) {
INTRO(0),
OUTRO(1),
MAIN_CONTENT(2),
PREVIEW(3),
RECAP(4);
companion object {
fun fromValue(value: Int): SegmentType? =
entries.firstOrNull { it.value == value }
}
}
data class MediaSegment(
val id: UUID,
val type: SegmentType,
val startMs: Long,
val endMs: Long
)

View File

@@ -0,0 +1,20 @@
package hu.bbara.purefin.core.model
import java.util.UUID
data class Movie(
val id: UUID,
val libraryId: UUID,
val title: String,
val progress: Double?,
val watched: Boolean,
val year: String,
val rating: String,
val runtime: String,
val format: String,
val synopsis: String,
val imageUrlPrefix: String,
val audioTrack: String,
val subtitles: String,
val cast: List<CastMember>
)

View File

@@ -0,0 +1,13 @@
package hu.bbara.purefin.core.model
import java.util.UUID
data class Season(
val id: UUID,
val seriesId: UUID,
val name: String,
val index: Int,
val unwatchedEpisodeCount: Int,
val episodeCount: Int,
val episodes: List<Episode>
)

View File

@@ -0,0 +1,16 @@
package hu.bbara.purefin.core.model
import java.util.UUID
data class Series(
val id: UUID,
val libraryId: UUID,
val name: String,
val synopsis: String,
val year: String,
val imageUrlPrefix: String,
val unwatchedEpisodeCount: Int,
val seasonCount: Int,
val seasons: List<Season>,
val cast: List<CastMember>
)

View File

@@ -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<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())
}
}