mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
Collapse shared modules into data domain and ui-common
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package hu.bbara.purefin.core.data
|
||||
|
||||
interface AuthenticationRepository {
|
||||
suspend fun login(url: String, username: String, password: String): Boolean
|
||||
}
|
||||
@@ -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?,
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package hu.bbara.purefin.core.data
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
interface EpisodeSeriesLookup {
|
||||
suspend fun preferenceKeyFor(mediaId: UUID): String
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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?>
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package hu.bbara.purefin.core.data
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworkMonitor {
|
||||
val isOnline: Flow<Boolean>
|
||||
}
|
||||
@@ -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>>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package hu.bbara.purefin.core.data
|
||||
|
||||
enum class PlaybackMethod {
|
||||
DIRECT_PLAY,
|
||||
DIRECT_STREAM,
|
||||
TRANSCODE,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package hu.bbara.purefin.core.data
|
||||
|
||||
enum class PlaybackProfileFamily {
|
||||
MOBILE,
|
||||
TV,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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?,
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package hu.bbara.purefin.core.data
|
||||
|
||||
interface SessionBootstrapper {
|
||||
suspend fun initialize()
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package hu.bbara.purefin.core.image
|
||||
|
||||
enum class ArtworkKind(
|
||||
internal val pathSegment: String,
|
||||
) {
|
||||
PRIMARY("Primary"),
|
||||
BACKDROP("Backdrop"),
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package hu.bbara.purefin.core.model
|
||||
|
||||
data class CastMember(
|
||||
val name: String,
|
||||
val role: String,
|
||||
val imageUrl: String?
|
||||
)
|
||||
20
data/src/main/java/hu/bbara/purefin/core/model/Episode.kt
Normal file
20
data/src/main/java/hu/bbara/purefin/core/model/Episode.kt
Normal 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>
|
||||
)
|
||||
18
data/src/main/java/hu/bbara/purefin/core/model/Library.kt
Normal file
18
data/src/main/java/hu/bbara/purefin/core/model/Library.kt
Normal 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" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package hu.bbara.purefin.core.model
|
||||
|
||||
enum class LibraryKind {
|
||||
MOVIES,
|
||||
SERIES,
|
||||
}
|
||||
13
data/src/main/java/hu/bbara/purefin/core/model/Media.kt
Normal file
13
data/src/main/java/hu/bbara/purefin/core/model/Media.kt
Normal 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)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package hu.bbara.purefin.core.model
|
||||
|
||||
enum class MediaKind {
|
||||
MOVIE,
|
||||
SERIES,
|
||||
SEASON,
|
||||
EPISODE,
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
20
data/src/main/java/hu/bbara/purefin/core/model/Movie.kt
Normal file
20
data/src/main/java/hu/bbara/purefin/core/model/Movie.kt
Normal 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>
|
||||
)
|
||||
13
data/src/main/java/hu/bbara/purefin/core/model/Season.kt
Normal file
13
data/src/main/java/hu/bbara/purefin/core/model/Season.kt
Normal 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>
|
||||
)
|
||||
16
data/src/main/java/hu/bbara/purefin/core/model/Series.kt
Normal file
16
data/src/main/java/hu/bbara/purefin/core/model/Series.kt
Normal 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>
|
||||
)
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user