mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
Separate mobile downloads from TV playback
This commit is contained in:
@@ -36,4 +36,5 @@ dependencies {
|
||||
implementation(libs.media3.datasource.okhttp)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.jellyfin.core)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package hu.bbara.purefin.feature.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 javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class DownloadControllerModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMediaDownloadController(
|
||||
impl: MediaDownloadManager
|
||||
): MediaDownloadController
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.database.StandaloneDatabaseProvider
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
import androidx.media3.datasource.cache.CacheEvictor
|
||||
import androidx.media3.datasource.cache.NoOpCacheEvictor
|
||||
import androidx.media3.datasource.cache.SimpleCache
|
||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
@@ -15,11 +17,25 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Singleton
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal object DownloadModuleFactories {
|
||||
fun createDownloadCacheEvictor(): CacheEvictor {
|
||||
return NoOpCacheEvictor()
|
||||
}
|
||||
|
||||
fun newPhonePlaybackDataSourceFactory(
|
||||
upstreamDataSourceFactory: DataSource.Factory
|
||||
): CacheDataSource.Factory {
|
||||
return CacheDataSource.Factory()
|
||||
.setUpstreamDataSourceFactory(upstreamDataSourceFactory)
|
||||
.setCacheWriteDataSinkFactory(null)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@@ -31,13 +47,11 @@ object DownloadModule {
|
||||
@Singleton
|
||||
fun provideDownloadCache(@ApplicationContext context: Context): SimpleCache {
|
||||
val downloadDir = File(context.getExternalFilesDir(null), "downloads")
|
||||
return SimpleCache(downloadDir, NoOpCacheEvictor(), StandaloneDatabaseProvider(context))
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpDataSourceFactory(okHttpClient: OkHttpClient): OkHttpDataSource.Factory {
|
||||
return OkHttpDataSource.Factory(okHttpClient)
|
||||
return SimpleCache(
|
||||
downloadDir,
|
||||
DownloadModuleFactories.createDownloadCacheEvictor(),
|
||||
StandaloneDatabaseProvider(context)
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -64,12 +78,12 @@ object DownloadModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCacheDataSourceFactory(
|
||||
fun providePlaybackDataSourceFactory(
|
||||
cache: SimpleCache,
|
||||
okHttpDataSourceFactory: OkHttpDataSource.Factory
|
||||
): CacheDataSource.Factory {
|
||||
return CacheDataSource.Factory()
|
||||
): DataSource.Factory {
|
||||
return DownloadModuleFactories
|
||||
.newPhonePlaybackDataSourceFactory(okHttpDataSourceFactory)
|
||||
.setCache(cache)
|
||||
.setUpstreamDataSourceFactory(okHttpDataSourceFactory)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package hu.bbara.purefin.feature.download
|
||||
|
||||
sealed class DownloadState {
|
||||
data object NotDownloaded : DownloadState()
|
||||
data class Downloading(val progressPercent: Float) : DownloadState()
|
||||
data object Downloaded : DownloadState()
|
||||
data object Failed : DownloadState()
|
||||
}
|
||||
@@ -10,8 +10,11 @@ import androidx.media3.exoplayer.offline.DownloadManager
|
||||
import androidx.media3.exoplayer.offline.DownloadRequest
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import hu.bbara.purefin.core.data.InMemoryMediaRepository
|
||||
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.room.dao.MovieDao
|
||||
import hu.bbara.purefin.core.data.room.dao.SmartDownloadDao
|
||||
import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity
|
||||
@@ -33,9 +36,11 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import java.util.UUID
|
||||
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
|
||||
@@ -51,7 +56,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
private val smartDownloadDao: SmartDownloadDao,
|
||||
private val userSessionRepository: UserSessionRepository,
|
||||
private val inMemoryMediaRepository: InMemoryMediaRepository,
|
||||
) {
|
||||
) : MediaDownloadController {
|
||||
|
||||
private val stateFlows = ConcurrentHashMap<String, MutableStateFlow<DownloadState>>()
|
||||
|
||||
@@ -87,7 +92,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
* Uses the download index directly so it always reflects the true download state,
|
||||
* regardless of listener callback timing.
|
||||
*/
|
||||
fun observeActiveDownloads(): Flow<Map<String, Float>> = flow {
|
||||
override fun observeActiveDownloads(): Flow<Map<String, Float>> = flow {
|
||||
while (true) {
|
||||
try {
|
||||
val result = buildMap<String, Float> {
|
||||
@@ -112,7 +117,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).distinctUntilChanged()
|
||||
|
||||
fun observeDownloadState(contentId: String): StateFlow<DownloadState> {
|
||||
override fun observeDownloadState(contentId: String): StateFlow<DownloadState> {
|
||||
val flow = getOrCreateStateFlow(contentId)
|
||||
// Initialize from current download index
|
||||
val download = downloadManager.downloadIndex.getDownload(contentId)
|
||||
@@ -124,7 +129,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
return downloadManager.downloadIndex.getDownload(contentId)?.state == Download.STATE_COMPLETED
|
||||
}
|
||||
|
||||
suspend fun downloadMovie(movieId: UUID) {
|
||||
override suspend fun downloadMovie(movieId: UUID) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val sources = jellyfinApiClient.getMediaSources(movieId)
|
||||
@@ -132,6 +137,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
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")
|
||||
@@ -168,9 +174,12 @@ class MediaDownloadManager @Inject constructor(
|
||||
offlineDataSource.saveMovies(listOf(movie))
|
||||
|
||||
Log.d(TAG, "Starting download for '${movie.title}' from: $url")
|
||||
val request = DownloadRequest.Builder(movieId.toString(), url.toUri())
|
||||
.setData(movie.title.toByteArray(Charsets.UTF_8))
|
||||
.build()
|
||||
val request = buildDownloadRequest(
|
||||
mediaId = movieId,
|
||||
playbackUrl = url,
|
||||
title = movie.title,
|
||||
isDirectPlay = !shouldTranscode
|
||||
)
|
||||
PurefinDownloadService.sendAddDownload(context, request)
|
||||
Log.d(TAG, "Download request sent for $movieId")
|
||||
} catch (e: Exception) {
|
||||
@@ -180,7 +189,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cancelDownload(movieId: UUID) {
|
||||
override suspend fun cancelDownload(movieId: UUID) {
|
||||
withContext(Dispatchers.IO) {
|
||||
PurefinDownloadService.sendRemoveDownload(context, movieId.toString())
|
||||
try {
|
||||
@@ -191,7 +200,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadEpisode(episodeId: UUID) {
|
||||
override suspend fun downloadEpisode(episodeId: UUID) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val serverUrl = userSessionRepository.serverUrl.first().trim()
|
||||
@@ -200,6 +209,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
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")
|
||||
@@ -232,9 +242,12 @@ class MediaDownloadManager @Inject constructor(
|
||||
offlineDataSource.saveEpisode(episode)
|
||||
|
||||
Log.d(TAG, "Starting download for episode '${episode.title}' from: $url")
|
||||
val request = DownloadRequest.Builder(episodeId.toString(), url.toUri())
|
||||
.setData(episode.title.toByteArray(Charsets.UTF_8))
|
||||
.build()
|
||||
val request = buildDownloadRequest(
|
||||
mediaId = episodeId,
|
||||
playbackUrl = url,
|
||||
title = episode.title,
|
||||
isDirectPlay = !shouldTranscode
|
||||
)
|
||||
PurefinDownloadService.sendAddDownload(context, request)
|
||||
Log.d(TAG, "Download request sent for episode $episodeId")
|
||||
} catch (e: Exception) {
|
||||
@@ -244,7 +257,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadEpisodes(episodeIds: List<UUID>) {
|
||||
override suspend fun downloadEpisodes(episodeIds: List<UUID>) {
|
||||
coroutineScope {
|
||||
for (episodeId in episodeIds) {
|
||||
launch { downloadEpisode(episodeId) }
|
||||
@@ -252,7 +265,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cancelEpisodeDownload(episodeId: UUID) {
|
||||
override suspend fun cancelEpisodeDownload(episodeId: UUID) {
|
||||
withContext(Dispatchers.IO) {
|
||||
PurefinDownloadService.sendRemoveDownload(context, episodeId.toString())
|
||||
try {
|
||||
@@ -265,7 +278,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
|
||||
// ── Smart Download ──────────────────────────────────────────────────
|
||||
|
||||
suspend fun enableSmartDownload(seriesId: UUID) {
|
||||
override suspend fun enableSmartDownload(seriesId: UUID) {
|
||||
smartDownloadDao.insert(SmartDownloadEntity(seriesId))
|
||||
syncSmartDownloadsForSeries(seriesId)
|
||||
}
|
||||
@@ -276,7 +289,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
|
||||
fun isSmartDownloadEnabled(seriesId: UUID): Flow<Boolean> = smartDownloadDao.observe(seriesId)
|
||||
|
||||
suspend fun syncSmartDownloads() {
|
||||
override suspend fun syncSmartDownloads() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val enabled = smartDownloadDao.getAll()
|
||||
for (entry in enabled) {
|
||||
@@ -337,6 +350,26 @@ class MediaDownloadManager @Inject constructor(
|
||||
return stateFlows.getOrPut(contentId) { MutableStateFlow(DownloadState.NotDownloaded) }
|
||||
}
|
||||
|
||||
private fun buildDownloadRequest(
|
||||
mediaId: UUID,
|
||||
playbackUrl: String,
|
||||
title: String,
|
||||
isDirectPlay: Boolean
|
||||
): 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)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun Download.toDownloadState(): DownloadState = when (state) {
|
||||
Download.STATE_COMPLETED -> DownloadState.Downloaded
|
||||
Download.STATE_DOWNLOADING -> DownloadState.Downloading(percentDownloaded)
|
||||
@@ -380,7 +413,7 @@ class MediaDownloadManager @Inject constructor(
|
||||
private fun BaseItemDto.toSeries(serverUrl: String): Series {
|
||||
return Series(
|
||||
id = id,
|
||||
libraryId = parentId ?: UUID.randomUUID(),
|
||||
libraryId = parentId ?: java.util.UUID.randomUUID(),
|
||||
name = name ?: "Unknown",
|
||||
synopsis = overview ?: "No synopsis available",
|
||||
year = productionYear?.toString() ?: premiereDate?.year?.toString().orEmpty(),
|
||||
@@ -412,3 +445,8 @@ class MediaDownloadManager @Inject constructor(
|
||||
private const val SMART_DOWNLOAD_COUNT = 5
|
||||
}
|
||||
}
|
||||
|
||||
private fun MediaSourceInfo.shouldUseTranscodingUrl(): Boolean {
|
||||
return supportsTranscoding == true &&
|
||||
(supportsDirectPlay == false || transcodingUrl != null)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package hu.bbara.purefin.feature.download
|
||||
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
import androidx.media3.datasource.cache.NoOpCacheEvictor
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class DownloadModuleTest {
|
||||
|
||||
@Test
|
||||
fun `download cache keeps no-op evictor`() {
|
||||
assertTrue(DownloadModuleFactories.createDownloadCacheEvictor() is NoOpCacheEvictor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `phone playback data source reads download cache and writes upstream only`() {
|
||||
val upstreamFactory = FakeDataSourceFactory()
|
||||
val factory = DownloadModuleFactories.newPhonePlaybackDataSourceFactory(upstreamFactory)
|
||||
|
||||
assertTrue(readPrivateBoolean(factory, "cacheIsReadOnly"))
|
||||
assertNull(readPrivateField(factory, "cacheWriteDataSinkFactory"))
|
||||
assertSame(upstreamFactory, readPrivateField(factory, "upstreamDataSourceFactory"))
|
||||
}
|
||||
|
||||
private fun readPrivateBoolean(instance: CacheDataSource.Factory, fieldName: String): Boolean {
|
||||
return readPrivateField(instance, fieldName) as Boolean
|
||||
}
|
||||
|
||||
private fun readPrivateField(instance: CacheDataSource.Factory, fieldName: String): Any? {
|
||||
val field = instance.javaClass.getDeclaredField(fieldName)
|
||||
field.isAccessible = true
|
||||
return field.get(instance)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeDataSourceFactory : DataSource.Factory {
|
||||
override fun createDataSource(): DataSource {
|
||||
throw UnsupportedOperationException("Unused in unit tests")
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(project(":core:model"))
|
||||
implementation(project(":core:data"))
|
||||
implementation(project(":feature:download"))
|
||||
implementation(libs.hilt)
|
||||
ksp(libs.hilt.compiler)
|
||||
implementation(libs.jellyfin.core)
|
||||
|
||||
@@ -7,9 +7,9 @@ import hu.bbara.purefin.core.data.AppContentRepository
|
||||
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.data.navigation.SeriesDto
|
||||
import hu.bbara.purefin.core.data.download.DownloadState
|
||||
import hu.bbara.purefin.core.data.download.MediaDownloadController
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -24,7 +24,7 @@ import javax.inject.Inject
|
||||
class EpisodeScreenViewModel @Inject constructor(
|
||||
private val appContentRepository: AppContentRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val mediaDownloadManager: MediaDownloadManager,
|
||||
private val mediaDownloadManager: MediaDownloadController,
|
||||
): ViewModel() {
|
||||
|
||||
private val _episodeId = MutableStateFlow<UUID?>(null)
|
||||
|
||||
@@ -6,9 +6,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import hu.bbara.purefin.core.data.MediaRepository
|
||||
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.data.download.DownloadState
|
||||
import hu.bbara.purefin.core.data.download.MediaDownloadController
|
||||
import hu.bbara.purefin.core.model.Movie
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -23,7 +23,7 @@ import javax.inject.Inject
|
||||
class MovieScreenViewModel @Inject constructor(
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val mediaDownloadManager: MediaDownloadManager
|
||||
private val mediaDownloadManager: MediaDownloadController
|
||||
): ViewModel() {
|
||||
|
||||
private val _movieId = MutableStateFlow<UUID?>(null)
|
||||
|
||||
@@ -7,10 +7,10 @@ import hu.bbara.purefin.core.data.MediaRepository
|
||||
import hu.bbara.purefin.core.data.navigation.EpisodeDto
|
||||
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.data.download.DownloadState
|
||||
import hu.bbara.purefin.core.data.download.MediaDownloadController
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.core.model.Series
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -27,7 +27,7 @@ import javax.inject.Inject
|
||||
class SeriesViewModel @Inject constructor(
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val mediaDownloadManager: MediaDownloadManager,
|
||||
private val mediaDownloadManager: MediaDownloadController,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _seriesId = MutableStateFlow<UUID?>(null)
|
||||
|
||||
@@ -4,12 +4,12 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import hu.bbara.purefin.core.data.OfflineMediaRepository
|
||||
import hu.bbara.purefin.core.data.download.MediaDownloadController
|
||||
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||
import hu.bbara.purefin.core.data.navigation.MovieDto
|
||||
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.data.navigation.SeriesDto
|
||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||
import hu.bbara.purefin.feature.shared.home.PosterItem
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -24,7 +24,7 @@ import javax.inject.Inject
|
||||
class DownloadsViewModel @Inject constructor(
|
||||
private val offlineMediaRepository: OfflineMediaRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val downloadManager: MediaDownloadManager
|
||||
private val downloadManager: MediaDownloadController
|
||||
) : ViewModel() {
|
||||
|
||||
fun onMovieSelected(movieId: UUID) {
|
||||
|
||||
@@ -12,8 +12,8 @@ import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.data.navigation.SeriesDto
|
||||
import hu.bbara.purefin.core.data.session.UserSessionRepository
|
||||
import hu.bbara.purefin.core.data.download.MediaDownloadController
|
||||
import hu.bbara.purefin.core.model.Media
|
||||
import hu.bbara.purefin.feature.download.MediaDownloadManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -33,7 +33,7 @@ class AppViewModel @Inject constructor(
|
||||
private val userSessionRepository: UserSessionRepository,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val refreshHomeDataUseCase: RefreshHomeDataUseCase,
|
||||
private val mediaDownloadManager: MediaDownloadManager,
|
||||
private val mediaDownloadManager: MediaDownloadController,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _isRefreshing = MutableStateFlow(false)
|
||||
|
||||
Reference in New Issue
Block a user