diff --git a/android-app/app/src/main/java/app/tivi/appinitializers/TmdbInitializer.kt b/android-app/app/src/main/java/app/tivi/appinitializers/TmdbInitializer.kt index 872f92d261..ebd15ea18c 100644 --- a/android-app/app/src/main/java/app/tivi/appinitializers/TmdbInitializer.kt +++ b/android-app/app/src/main/java/app/tivi/appinitializers/TmdbInitializer.kt @@ -3,8 +3,8 @@ package app.tivi.appinitializers -import app.tivi.domain.executeSync import app.tivi.domain.interactors.UpdateTmdbConfig +import app.tivi.domain.invoke import app.tivi.util.AppCoroutineDispatchers import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -19,7 +19,7 @@ class TmdbInitializer( override fun init() { @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch(dispatchers.main) { - updateTmdbConfig.executeSync() + updateTmdbConfig.invoke() } } } diff --git a/android-app/app/src/main/java/app/tivi/home/MainActivityViewModel.kt b/android-app/app/src/main/java/app/tivi/home/MainActivityViewModel.kt index e3ef816cb4..f7b87a210b 100644 --- a/android-app/app/src/main/java/app/tivi/home/MainActivityViewModel.kt +++ b/android-app/app/src/main/java/app/tivi/home/MainActivityViewModel.kt @@ -6,9 +6,9 @@ package app.tivi.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.tivi.data.traktauth.TraktAuthState -import app.tivi.domain.executeSync import app.tivi.domain.interactors.ClearTraktAuthState import app.tivi.domain.interactors.UpdateUserDetails +import app.tivi.domain.invoke import app.tivi.domain.observers.ObserveTraktAuthState import app.tivi.domain.observers.ObserveUserDetails import app.tivi.util.Logger @@ -45,11 +45,11 @@ class MainActivityViewModel( private fun refreshMe() { viewModelScope.launch { try { - updateUserDetails.executeSync(UpdateUserDetails.Params("me", false)) + updateUserDetails(UpdateUserDetails.Params("me", false)) } catch (e: ResponseException) { if (e.response.status == HttpStatusCode.Unauthorized) { // If we got a 401 back from Trakt, we should clear out the auth state - clearTraktAuthState.executeSync() + clearTraktAuthState.invoke() } } catch (ce: CancellationException) { throw ce diff --git a/common/ui/view/src/main/java/app/tivi/util/ObservableLoadingCounter.kt b/common/ui/view/src/main/java/app/tivi/util/ObservableLoadingCounter.kt deleted file mode 100644 index 38d854c72e..0000000000 --- a/common/ui/view/src/main/java/app/tivi/util/ObservableLoadingCounter.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019, Google LLC, Christopher Banes and the Tivi project contributors -// SPDX-License-Identifier: Apache-2.0 - -package app.tivi.util - -import app.tivi.api.UiMessage -import app.tivi.api.UiMessageManager -import app.tivi.base.InvokeError -import app.tivi.base.InvokeStarted -import app.tivi.base.InvokeStatus -import app.tivi.base.InvokeSuccess -import java.util.concurrent.atomic.AtomicInteger -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach - -class ObservableLoadingCounter { - private val count = AtomicInteger() - private val loadingState = MutableStateFlow(count.get()) - - val observable: Flow - get() = loadingState.map { it > 0 }.distinctUntilChanged() - - fun addLoader() { - loadingState.value = count.incrementAndGet() - } - - fun removeLoader() { - loadingState.value = count.decrementAndGet() - } -} - -suspend fun Flow.onEachStatus( - counter: ObservableLoadingCounter, - logger: Logger? = null, - uiMessageManager: UiMessageManager? = null, -): Flow = onEach { status -> - when (status) { - InvokeStarted -> counter.addLoader() - InvokeSuccess -> counter.removeLoader() - is InvokeError -> { - logger?.i(status.throwable) - uiMessageManager?.emitMessage(UiMessage(status.throwable)) - counter.removeLoader() - } - } -} - -suspend inline fun Flow.collectStatus( - counter: ObservableLoadingCounter, - logger: Logger? = null, - uiMessageManager: UiMessageManager? = null, -): Unit = onEachStatus(counter, logger, uiMessageManager).collect() diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/Interactor.kt b/domain/src/commonMain/kotlin/app/tivi/domain/Interactor.kt index e3c9f3edc0..d5d6dff75f 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/Interactor.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/Interactor.kt @@ -5,59 +5,59 @@ package app.tivi.domain import app.cash.paging.PagingConfig import app.cash.paging.PagingData -import app.tivi.base.InvokeError -import app.tivi.base.InvokeStarted -import app.tivi.base.InvokeStatus -import app.tivi.base.InvokeSuccess import java.util.concurrent.TimeUnit -import kotlinx.coroutines.TimeoutCancellationException +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withTimeout -abstract class Interactor { - operator fun invoke( - params: P, - timeoutMs: Long = defaultTimeoutMs, - ): Flow = flow { - try { - withTimeout(timeoutMs) { - emit(InvokeStarted) - doWork(params) - emit(InvokeSuccess) - } - } catch (t: TimeoutCancellationException) { - emit(InvokeError(t)) - } - }.catch { t -> emit(InvokeError(t)) } +abstract class Interactor { + private val count = AtomicInteger() + private val loadingState = MutableStateFlow(count.get()) - suspend fun executeSync(params: P) = doWork(params) + val inProgress: Flow + get() = loadingState.map { it > 0 }.distinctUntilChanged() - protected abstract suspend fun doWork(params: P) - - companion object { - private val defaultTimeoutMs = TimeUnit.MINUTES.toMillis(5) + private fun addLoader() { + loadingState.value = count.incrementAndGet() } -} - -suspend inline fun Interactor.executeSync() = executeSync(Unit) -abstract class ResultInteractor { - operator fun invoke(params: P): Flow = flow { - emit(doWork(params)) + private fun removeLoader() { + loadingState.value = count.decrementAndGet() } - suspend fun executeSync(params: P): R = doWork(params) + suspend operator fun invoke( + params: P, + timeoutMs: Long = DefaultTimeoutMs, + ): Result { + return try { + addLoader() + runCatching { + withTimeout(timeoutMs) { + doWork(params) + } + } + } finally { + removeLoader() + } + } protected abstract suspend fun doWork(params: P): R + + companion object { + internal val DefaultTimeoutMs = TimeUnit.MINUTES.toMillis(5) + } } -suspend inline fun ResultInteractor.executeSync(): R = executeSync(Unit) +suspend fun Interactor.invoke( + timeoutMs: Long = Interactor.DefaultTimeoutMs, +) = invoke(Unit, timeoutMs) abstract class PagingInteractor

, T : Any> : SubjectInteractor>() { interface Parameters { @@ -65,14 +65,7 @@ abstract class PagingInteractor

, T : Any> : S } } -abstract class SuspendingWorkInteractor

: SubjectInteractor() { - override fun createObservable(params: P): Flow = flow { - emit(doWork(params)) - } - - abstract suspend fun doWork(params: P): T -} - +@OptIn(ExperimentalCoroutinesApi::class) abstract class SubjectInteractor

{ // Ideally this would be buffer = 0, since we use flatMapLatest below, BUT invoke is not // suspending. This means that we can't suspend while flatMapLatest cancels any diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/AddEpisodeWatch.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/AddEpisodeWatch.kt index 5b313e7e0d..ac54c700f3 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/AddEpisodeWatch.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/AddEpisodeWatch.kt @@ -14,7 +14,7 @@ import me.tatarka.inject.annotations.Inject class AddEpisodeWatch( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { seasonsEpisodesRepository.addEpisodeWatch(params.episodeId, params.timestamp) diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonFollowStatus.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonFollowStatus.kt index a17567d8ae..7748335165 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonFollowStatus.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonFollowStatus.kt @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject class ChangeSeasonFollowStatus( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { when (params.action) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonWatchedStatus.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonWatchedStatus.kt index dddc86e605..31f310218f 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonWatchedStatus.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeSeasonWatchedStatus.kt @@ -14,7 +14,7 @@ import me.tatarka.inject.annotations.Inject class ChangeSeasonWatchedStatus( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeShowFollowStatus.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeShowFollowStatus.kt index d763b662e4..a2992ae8f5 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeShowFollowStatus.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ChangeShowFollowStatus.kt @@ -17,7 +17,7 @@ class ChangeShowFollowStatus( private val followedShowsRepository: FollowedShowsRepository, private val showStore: ShowStore, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { params.showIds.forEach { showId -> diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearTraktAuthState.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearTraktAuthState.kt index 6efb0d2c20..61aba2d45c 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearTraktAuthState.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearTraktAuthState.kt @@ -4,13 +4,13 @@ package app.tivi.domain.interactors import app.tivi.data.traktauth.TraktAuthRepository -import app.tivi.domain.ResultInteractor +import app.tivi.domain.Interactor import me.tatarka.inject.annotations.Inject @Inject class ClearTraktAuthState( private val traktAuthRepository: TraktAuthRepository, -) : ResultInteractor() { +) : Interactor() { override suspend fun doWork(params: Unit) { traktAuthRepository.clearAuth() } diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearUserDetails.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearUserDetails.kt index 395b3bb015..de26e25f7b 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearUserDetails.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/ClearUserDetails.kt @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject class ClearUserDetails( private val userDao: UserDao, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { when (params.username) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/DeleteFolder.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/DeleteFolder.kt index 29cfb1a412..3e75153062 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/DeleteFolder.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/DeleteFolder.kt @@ -12,7 +12,7 @@ import me.tatarka.inject.annotations.Inject @Inject class DeleteFolder( private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { if (params.directory.exists()) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetEpisodeDetails.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetEpisodeDetails.kt deleted file mode 100644 index 5202ed3fca..0000000000 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetEpisodeDetails.kt +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019, Google LLC, Christopher Banes and the Tivi project contributors -// SPDX-License-Identifier: Apache-2.0 - -package app.tivi.domain.interactors - -import app.tivi.data.episodes.SeasonsEpisodesRepository -import app.tivi.data.models.Episode -import app.tivi.domain.ResultInteractor -import app.tivi.util.AppCoroutineDispatchers -import kotlinx.coroutines.withContext -import me.tatarka.inject.annotations.Inject - -@Inject -class GetEpisodeDetails( - private val seasonsEpisodesRepository: SeasonsEpisodesRepository, - private val dispatchers: AppCoroutineDispatchers, -) : ResultInteractor() { - override suspend fun doWork(params: Params): Episode? = withContext(dispatchers.io) { - seasonsEpisodesRepository.getEpisode(params.episodeId) - } - - data class Params(val episodeId: Long) -} diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetTraktAuthState.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetTraktAuthState.kt index 36d72694fc..463e30d053 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetTraktAuthState.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/GetTraktAuthState.kt @@ -5,13 +5,13 @@ package app.tivi.domain.interactors import app.tivi.data.traktauth.TraktAuthRepository import app.tivi.data.traktauth.TraktAuthState -import app.tivi.domain.ResultInteractor +import app.tivi.domain.Interactor import me.tatarka.inject.annotations.Inject @Inject class GetTraktAuthState( private val traktAuthRepository: TraktAuthRepository, -) : ResultInteractor() { +) : Interactor() { override suspend fun doWork(params: Unit): TraktAuthState { return traktAuthRepository.state.value } diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatch.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatch.kt index 2a3533461c..3b0317aeb5 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatch.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatch.kt @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject class RemoveEpisodeWatch( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { seasonsEpisodesRepository.removeEpisodeWatch(params.episodeWatchId) diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatches.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatches.kt index bc044d12f9..781c50581c 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatches.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/RemoveEpisodeWatches.kt @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject class RemoveEpisodeWatches( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { seasonsEpisodesRepository.removeAllEpisodeWatches(params.episodeId) diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/SearchShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/SearchShows.kt index 2c5a9ba20a..42e14f4130 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/SearchShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/SearchShows.kt @@ -6,7 +6,7 @@ package app.tivi.domain.interactors import app.tivi.data.daos.TiviShowDao import app.tivi.data.models.TiviShow import app.tivi.data.search.SearchRepository -import app.tivi.domain.SuspendingWorkInteractor +import app.tivi.domain.Interactor import app.tivi.util.AppCoroutineDispatchers import kotlinx.coroutines.withContext import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ class SearchShows( private val searchRepository: SearchRepository, private val showDao: TiviShowDao, private val dispatchers: AppCoroutineDispatchers, -) : SuspendingWorkInteractor>() { +) : Interactor>() { override suspend fun doWork(params: Params): List = withContext(dispatchers.io) { val remoteResults = searchRepository.search(params.query) when { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateEpisodeDetails.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateEpisodeDetails.kt index c8afd43b98..9403f3f194 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateEpisodeDetails.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateEpisodeDetails.kt @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject class UpdateEpisodeDetails( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateLibraryShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateLibraryShows.kt index 1a6de73f12..8e22b5ab13 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateLibraryShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateLibraryShows.kt @@ -32,7 +32,7 @@ class UpdateLibraryShows( private val watchedShowDao: WatchedShowDao, private val logger: Logger, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params): Unit = withContext(dispatchers.io) { val watchedShowsDeferred = async { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdatePopularShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdatePopularShows.kt index 8d3262eeda..84b5106a86 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdatePopularShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdatePopularShows.kt @@ -20,7 +20,7 @@ class UpdatePopularShows( private val popularDao: PopularDao, private val showStore: ShowStore, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { val page = when { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRecommendedShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRecommendedShows.kt index efcf0173ce..791656654c 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRecommendedShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRecommendedShows.kt @@ -24,7 +24,7 @@ class UpdateRecommendedShows( private val dispatchers: AppCoroutineDispatchers, private val traktAuthRepository: TraktAuthRepository, private val logger: Logger, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { // If we're not logged in, we can't load the recommended shows if (traktAuthRepository.state.value != TraktAuthState.LOGGED_IN) return diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRelatedShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRelatedShows.kt index 1cc098f3b7..2a469db9ca 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRelatedShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateRelatedShows.kt @@ -21,7 +21,7 @@ class UpdateRelatedShows( private val showsStore: ShowStore, private val dispatchers: AppCoroutineDispatchers, private val logger: Logger, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) = withContext(dispatchers.io) { relatedShowsStore.fetch(params.showId, params.forceLoad).parallelForEach { try { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowDetails.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowDetails.kt index a812af8d45..01599fd7e0 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowDetails.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowDetails.kt @@ -18,7 +18,7 @@ class UpdateShowDetails( private val showStore: ShowStore, private val lastRequestStore: ShowLastRequestStore, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { showStore.fetch( diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowImages.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowImages.kt index 7d65da5e88..19b7ab5548 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowImages.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowImages.kt @@ -14,7 +14,7 @@ import me.tatarka.inject.annotations.Inject class UpdateShowImages( private val showImagesStore: ShowImagesStore, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { showImagesStore.fetch(params.showId, params.forceLoad) diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowSeasons.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowSeasons.kt index 5c0fd2084d..99fa44ee8a 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowSeasons.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateShowSeasons.kt @@ -15,7 +15,7 @@ import me.tatarka.inject.annotations.Inject class UpdateShowSeasons( private val seasonsEpisodesRepository: SeasonsEpisodesRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { // Then update the seasons/episodes diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTmdbConfig.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTmdbConfig.kt index d502cd37df..461eba2512 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTmdbConfig.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTmdbConfig.kt @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject class UpdateTmdbConfig( private val tmdbManager: TmdbManager, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Unit) { withContext(dispatchers.io) { tmdbManager.refreshConfiguration() diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTrendingShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTrendingShows.kt index cef93082df..5a06b410cd 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTrendingShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateTrendingShows.kt @@ -20,7 +20,7 @@ class UpdateTrendingShows( private val trendingShowsDao: TrendingDao, private val showStore: ShowStore, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { val page = when { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUpNextEpisodes.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUpNextEpisodes.kt index a6e3be84e8..4662225376 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUpNextEpisodes.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUpNextEpisodes.kt @@ -17,12 +17,10 @@ class UpdateUpNextEpisodes( private val seasonEpisodeRepository: SeasonsEpisodesRepository, private val updateLibraryShows: UpdateLibraryShows, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { - updateLibraryShows.executeSync( - UpdateLibraryShows.Params(params.forceRefresh), - ) + updateLibraryShows(UpdateLibraryShows.Params(params.forceRefresh)) // Now update the next episodes, to fetch images, etc withContext(dispatchers.io) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUserDetails.kt b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUserDetails.kt index 81b206f160..19acbb958e 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUserDetails.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/interactors/UpdateUserDetails.kt @@ -14,7 +14,7 @@ import me.tatarka.inject.annotations.Inject class UpdateUserDetails( private val repository: TraktUsersRepository, private val dispatchers: AppCoroutineDispatchers, -) : Interactor() { +) : Interactor() { override suspend fun doWork(params: Params) { withContext(dispatchers.io) { if (params.forceLoad || repository.needUpdate(params.username)) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedPopularShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedPopularShows.kt index dfef77f9b1..95861e8e88 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedPopularShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedPopularShows.kt @@ -29,9 +29,7 @@ class ObservePagedPopularShows( config = params.pagingConfig, remoteMediator = PaginatedEntryRemoteMediator { page -> try { - updatePopularShows.executeSync( - UpdatePopularShows.Params(page = page, forceRefresh = true), - ) + updatePopularShows(UpdatePopularShows.Params(page = page, forceRefresh = true)) } catch (ce: CancellationException) { throw ce } catch (t: Throwable) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedRecommendedShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedRecommendedShows.kt index 3dc9318ea1..9226eb955f 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedRecommendedShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedRecommendedShows.kt @@ -29,9 +29,7 @@ class ObservePagedRecommendedShows( config = params.pagingConfig, remoteMediator = RefreshOnlyRemoteMediator { try { - updateRecommendedShows.executeSync( - UpdateRecommendedShows.Params(forceRefresh = true), - ) + updateRecommendedShows(UpdateRecommendedShows.Params(forceRefresh = true)) } catch (ce: CancellationException) { throw ce } catch (t: Throwable) { diff --git a/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedTrendingShows.kt b/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedTrendingShows.kt index afac3ebc7d..8d3c152b75 100644 --- a/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedTrendingShows.kt +++ b/domain/src/commonMain/kotlin/app/tivi/domain/observers/ObservePagedTrendingShows.kt @@ -29,7 +29,7 @@ class ObservePagedTrendingShows( config = params.pagingConfig, remoteMediator = PaginatedEntryRemoteMediator { page -> try { - updateTrendingShows.executeSync( + updateTrendingShows( UpdateTrendingShows.Params(page = page, forceRefresh = true), ) } catch (ce: CancellationException) { diff --git a/tasks/src/androidMain/kotlin/app/tivi/tasks/SyncLibraryShows.kt b/tasks/src/androidMain/kotlin/app/tivi/tasks/SyncLibraryShows.kt index b8bd836088..ee8a0eb599 100644 --- a/tasks/src/androidMain/kotlin/app/tivi/tasks/SyncLibraryShows.kt +++ b/tasks/src/androidMain/kotlin/app/tivi/tasks/SyncLibraryShows.kt @@ -25,7 +25,10 @@ class SyncLibraryShows( override suspend fun doWork(): Result { logger.d("$tags worker running") - updateLibraryShows.executeSync(UpdateLibraryShows.Params(true)) - return Result.success() + val result = updateLibraryShows(UpdateLibraryShows.Params(true)) + return when { + result.isSuccess -> Result.success() + else -> Result.failure() + } } } diff --git a/ui/account/src/main/java/app/tivi/account/AccountPresenter.kt b/ui/account/src/main/java/app/tivi/account/AccountPresenter.kt index 7e6dc800f3..757b25ee82 100644 --- a/ui/account/src/main/java/app/tivi/account/AccountPresenter.kt +++ b/ui/account/src/main/java/app/tivi/account/AccountPresenter.kt @@ -20,7 +20,6 @@ import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen import com.slack.circuit.runtime.presenter.Presenter -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject @@ -68,7 +67,7 @@ class AccountPresenter( AccountUiEvent.Logout -> { scope.launch { traktAuthRepository.clearAuth() - clearUserDetails(ClearUserDetails.Params("me")).collect() + clearUserDetails(ClearUserDetails.Params("me")) } } } diff --git a/ui/discover/src/main/java/app/tivi/home/discover/DiscoverPresenter.kt b/ui/discover/src/main/java/app/tivi/home/discover/DiscoverPresenter.kt index 1f5447889c..3f46258f47 100644 --- a/ui/discover/src/main/java/app/tivi/home/discover/DiscoverPresenter.kt +++ b/ui/discover/src/main/java/app/tivi/home/discover/DiscoverPresenter.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope import app.tivi.data.traktauth.TraktAuthState @@ -29,8 +30,6 @@ import app.tivi.screens.ShowDetailsScreen import app.tivi.screens.ShowSeasonsScreen import app.tivi.screens.TrendingShowsScreen import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen @@ -73,20 +72,16 @@ class DiscoverPresenter( @Composable override fun present(): DiscoverUiState { val scope = rememberCoroutineScope() - - val trendingLoadingState = remember { ObservableLoadingCounter() } - val popularLoadingState = remember { ObservableLoadingCounter() } - val recommendedLoadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } - val trendingLoading by trendingLoadingState.observable.collectAsState(false) + val trendingLoading by updateTrendingShows.inProgress.collectAsState(false) val trendingItems by observeTrendingShows.flow.collectAsState(emptyList()) val popularItems by observePopularShows.flow.collectAsState(emptyList()) - val popularLoading by popularLoadingState.observable.collectAsState(false) + val popularLoading by updatePopularShows.inProgress.collectAsState(false) val recommendedItems by observeRecommendedShows.flow.collectAsState(emptyList()) - val recommendedLoading by recommendedLoadingState.observable.collectAsState(false) + val recommendedLoading by updateRecommendedShows.inProgress.collectAsState(false) val nextShow by observeNextShowEpisodeToWatch.flow.collectAsState(null) val authState by observeTraktAuthState.flow.collectAsState(TraktAuthState.LOGGED_OUT) @@ -109,7 +104,12 @@ class DiscoverPresenter( page = UpdatePopularShows.Page.REFRESH, forceRefresh = event.fromUser, ), - ).collectStatus(popularLoadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } scope.launch { updateTrendingShows( @@ -117,12 +117,22 @@ class DiscoverPresenter( page = UpdateTrendingShows.Page.REFRESH, forceRefresh = event.fromUser, ), - ).collectStatus(trendingLoadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } scope.launch { updateRecommendedShows( UpdateRecommendedShows.Params(forceRefresh = event.fromUser), - ).collectStatus(recommendedLoadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } diff --git a/ui/episode/details/src/main/java/app/tivi/episodedetails/EpisodeDetailsPresenter.kt b/ui/episode/details/src/main/java/app/tivi/episodedetails/EpisodeDetailsPresenter.kt index 8a9057d3a3..831b8c14f2 100644 --- a/ui/episode/details/src/main/java/app/tivi/episodedetails/EpisodeDetailsPresenter.kt +++ b/ui/episode/details/src/main/java/app/tivi/episodedetails/EpisodeDetailsPresenter.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope import app.tivi.domain.interactors.RemoveEpisodeWatch @@ -18,8 +19,6 @@ import app.tivi.domain.observers.ObserveEpisodeWatches import app.tivi.screens.EpisodeDetailsScreen import app.tivi.screens.EpisodeTrackScreen import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen @@ -56,11 +55,9 @@ class EpisodeDetailsPresenter( @Composable override fun present(): EpisodeDetailsUiState { val scope = rememberCoroutineScope() - - val loadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } - val refreshing by loadingState.observable.collectAsState(false) + val refreshing by updateEpisodeDetails.inProgress.collectAsState(false) val message by uiMessageManager.message.collectAsState(null) val episodeDetails by observeEpisodeDetails.flow.collectAsState(null) @@ -72,7 +69,12 @@ class EpisodeDetailsPresenter( scope.launch { updateEpisodeDetails( UpdateEpisodeDetails.Params(screen.id, event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -86,7 +88,12 @@ class EpisodeDetailsPresenter( scope.launch { removeEpisodeWatches( RemoveEpisodeWatches.Params(screen.id), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -94,7 +101,12 @@ class EpisodeDetailsPresenter( scope.launch { removeEpisodeWatch( RemoveEpisodeWatch.Params(event.id), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } diff --git a/ui/episode/track/src/main/java/app/tivi/episode/track/EpisodeTrackPresenter.kt b/ui/episode/track/src/main/java/app/tivi/episode/track/EpisodeTrackPresenter.kt index cc053767dd..4cf01c0f52 100644 --- a/ui/episode/track/src/main/java/app/tivi/episode/track/EpisodeTrackPresenter.kt +++ b/ui/episode/track/src/main/java/app/tivi/episode/track/EpisodeTrackPresenter.kt @@ -11,17 +11,14 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager -import app.tivi.base.InvokeSuccess import app.tivi.common.compose.rememberCoroutineScope import app.tivi.domain.interactors.AddEpisodeWatch import app.tivi.domain.interactors.UpdateEpisodeDetails import app.tivi.domain.observers.ObserveEpisodeDetails import app.tivi.screens.EpisodeTrackScreen import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus -import app.tivi.util.onEachStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen @@ -63,9 +60,6 @@ class EpisodeTrackPresenter( @Composable override fun present(): EpisodeTrackUiState { val scope = rememberCoroutineScope() - - val loadingState = remember { ObservableLoadingCounter() } - val submittingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } var selectedDate by remember { mutableStateOf(null) } @@ -74,8 +68,8 @@ class EpisodeTrackPresenter( val episodeDetails by observeEpisodeDetails.flow.collectAsState(initial = null) - val refreshing by loadingState.observable.collectAsState(initial = false) - val submitting by submittingState.observable.collectAsState(initial = false) + val refreshing by updateEpisodeDetails.inProgress.collectAsState(initial = false) + val submitting by addEpisodeWatch.inProgress.collectAsState(initial = false) val message by uiMessageManager.message.collectAsState(initial = null) var dismissed by remember { mutableStateOf(false) } @@ -100,7 +94,12 @@ class EpisodeTrackPresenter( scope.launch { updateEpisodeDetails( UpdateEpisodeDetails.Params(screen.id, event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -139,13 +138,17 @@ class EpisodeTrackPresenter( if (instant != null) { scope.launch { - addEpisodeWatch(AddEpisodeWatch.Params(screen.id, instant)) - .onEachStatus(submittingState, logger, uiMessageManager) - .collect { status -> - if (status == InvokeSuccess) { - dismissed = true - } + addEpisodeWatch( + AddEpisodeWatch.Params(screen.id, instant), + ).also { result -> + if (result.isSuccess) { + dismissed = true + } + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) } + } } } else { // TODO: display error message diff --git a/ui/library/src/main/java/app/tivi/home/library/LibraryPresenter.kt b/ui/library/src/main/java/app/tivi/home/library/LibraryPresenter.kt index 3de40cb74c..6fbeb548f2 100644 --- a/ui/library/src/main/java/app/tivi/home/library/LibraryPresenter.kt +++ b/ui/library/src/main/java/app/tivi/home/library/LibraryPresenter.kt @@ -13,13 +13,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.paging.PagingConfig import androidx.paging.compose.collectAsLazyPagingItems +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope import app.tivi.data.models.SortOption import app.tivi.data.traktauth.TraktAuthState -import app.tivi.domain.executeSync import app.tivi.domain.interactors.GetTraktAuthState import app.tivi.domain.interactors.UpdateLibraryShows +import app.tivi.domain.invoke import app.tivi.domain.observers.ObservePagedLibraryShows import app.tivi.domain.observers.ObserveTraktAuthState import app.tivi.domain.observers.ObserveUserDetails @@ -28,8 +29,6 @@ import app.tivi.screens.LibraryScreen import app.tivi.screens.ShowDetailsScreen import app.tivi.settings.TiviPreferences import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen @@ -68,9 +67,6 @@ class LibraryPresenter( @Composable override fun present(): LibraryUiState { val scope = rememberCoroutineScope() - - val followedLoadingState = remember { ObservableLoadingCounter() } - val watchedLoadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } val items = observePagedLibraryShows.flow.collectAsLazyPagingItems() @@ -78,8 +74,7 @@ class LibraryPresenter( var filter by remember { mutableStateOf(null) } var sort by remember { mutableStateOf(SortOption.LAST_WATCHED) } - val followedLoading by followedLoadingState.observable.collectAsState(false) - val watchedLoading by watchedLoadingState.observable.collectAsState(false) + val loading by updateLibraryShows.inProgress.collectAsState(false) val message by uiMessageManager.message.collectAsState(null) val user by observeUserDetails.flow.collectAsState(null) @@ -103,10 +98,15 @@ class LibraryPresenter( } is LibraryUiEvent.Refresh -> { scope.launch { - if (getTraktAuthState.executeSync() == TraktAuthState.LOGGED_IN) { + if (getTraktAuthState.invoke().getOrThrow() == TraktAuthState.LOGGED_IN) { updateLibraryShows( UpdateLibraryShows.Params(event.fromUser), - ).collectStatus(followedLoadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } } @@ -153,7 +153,7 @@ class LibraryPresenter( items = items, user = user, authState = authState, - isLoading = followedLoading || watchedLoading, + isLoading = loading, filter = filter, filterActive = !filter.isNullOrEmpty(), availableSorts = AVAILABLE_SORT_OPTIONS, diff --git a/ui/search/src/main/java/app/tivi/home/search/SearchPresenter.kt b/ui/search/src/main/java/app/tivi/home/search/SearchPresenter.kt index c2728e6703..93ebc40a53 100644 --- a/ui/search/src/main/java/app/tivi/home/search/SearchPresenter.kt +++ b/ui/search/src/main/java/app/tivi/home/search/SearchPresenter.kt @@ -10,22 +10,20 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope +import app.tivi.data.models.TiviShow import app.tivi.domain.interactors.SearchShows import app.tivi.screens.SearchScreen import app.tivi.screens.ShowDetailsScreen -import app.tivi.util.ObservableLoadingCounter +import app.tivi.util.Logger import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen import com.slack.circuit.runtime.presenter.Presenter -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.onEach +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject @@ -48,6 +46,7 @@ class SearchUiPresenterFactory( class SearchPresenter( @Assisted private val navigator: Navigator, private val searchShows: SearchShows, + private val logger: Logger, ) : Presenter { @Composable @@ -55,28 +54,23 @@ class SearchPresenter( val scope = rememberCoroutineScope() var query by remember { mutableStateOf("") } - val loadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } - val loading by loadingState.observable.collectAsState(false) + val loading by searchShows.inProgress.collectAsState(false) val message by uiMessageManager.message.collectAsState(null) - val results by searchShows.flow.collectAsState(emptyList()) + var results by remember { mutableStateOf(emptyList()) } - LaunchedEffect(Unit) { - snapshotFlow { query } - .debounce(300) - .onEach { query -> - launch { - loadingState.addLoader() - searchShows(SearchShows.Params(query)) - }.invokeOnCompletion { - loadingState.removeLoader() - } - } - .catch { throwable -> - uiMessageManager.emitMessage(UiMessage(throwable)) - } - .collect() + LaunchedEffect(query) { + // delay for 300 milliseconds. This has the same effect as debounce + delay(300.milliseconds) + + val result = searchShows(SearchShows.Params(query)) + results = result.getOrDefault(emptyList()) + + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } } fun eventSink(event: SearchUiEvent) { @@ -86,6 +80,7 @@ class SearchPresenter( uiMessageManager.clearMessage(event.id) } } + is SearchUiEvent.UpdateQuery -> query = event.query is SearchUiEvent.OpenShowDetails -> { navigator.goTo(ShowDetailsScreen(event.showId)) diff --git a/ui/show/details/src/main/java/app/tivi/showdetails/details/ShowDetailsPresenter.kt b/ui/show/details/src/main/java/app/tivi/showdetails/details/ShowDetailsPresenter.kt index 3098210c1d..e010694a3a 100644 --- a/ui/show/details/src/main/java/app/tivi/showdetails/details/ShowDetailsPresenter.kt +++ b/ui/show/details/src/main/java/app/tivi/showdetails/details/ShowDetailsPresenter.kt @@ -7,7 +7,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope import app.tivi.data.models.TiviShow @@ -30,12 +32,11 @@ import app.tivi.screens.EpisodeDetailsScreen import app.tivi.screens.ShowDetailsScreen import app.tivi.screens.ShowSeasonsScreen import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen import com.slack.circuit.runtime.presenter.Presenter +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject @@ -78,12 +79,18 @@ class ShowDetailsPresenter( override fun present(): ShowDetailsUiState { val scope = rememberCoroutineScope() - val loadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } val isFollowed by observeShowFollowStatus.flow.collectAsState(false) val show by observeShowDetails.flow.collectAsState(TiviShow.EMPTY_SHOW) - val refreshing by loadingState.observable.collectAsState(false) + val refreshing by produceState(false) { + combine( + updateShowDetails.inProgress, + updateShowSeasons.inProgress, + updateRelatedShows.inProgress, + transform = { values -> values.any { it } }, + ).collect { value = it } + } val relatedShows by observeRelatedShows.flow.collectAsState(emptyList()) val nextEpisode by observeNextEpisodeToWatch.flow.collectAsState(null) val seasons by observeShowSeasons.flow.collectAsState(emptyList()) @@ -102,17 +109,32 @@ class ShowDetailsPresenter( scope.launch { updateShowDetails( UpdateShowDetails.Params(showId, event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } scope.launch { updateRelatedShows( UpdateRelatedShows.Params(showId, event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } scope.launch { updateShowSeasons( UpdateShowSeasons.Params(showId, event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -123,7 +145,12 @@ class ShowDetailsPresenter( seasonId = event.seasonId, action = ChangeSeasonFollowStatus.Action.FOLLOW, ), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -131,7 +158,12 @@ class ShowDetailsPresenter( scope.launch { changeSeasonWatchedStatus( Params(event.seasonId, Action.UNWATCH), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -139,7 +171,12 @@ class ShowDetailsPresenter( scope.launch { changeSeasonWatchedStatus( Params(event.seasonId, Action.WATCHED, event.onlyAired, event.date), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -147,7 +184,12 @@ class ShowDetailsPresenter( scope.launch { changeShowFollowStatus( ChangeShowFollowStatus.Params(showId, TOGGLE), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -158,7 +200,12 @@ class ShowDetailsPresenter( seasonId = event.seasonId, action = ChangeSeasonFollowStatus.Action.IGNORE_PREVIOUS, ), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } @@ -169,7 +216,12 @@ class ShowDetailsPresenter( seasonId = event.seasonId, action = ChangeSeasonFollowStatus.Action.IGNORE, ), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } ShowDetailsUiEvent.NavigateBack -> navigator.pop() diff --git a/ui/show/seasons/src/main/java/app/tivi/showdetails/seasons/ShowSeasonsPresenter.kt b/ui/show/seasons/src/main/java/app/tivi/showdetails/seasons/ShowSeasonsPresenter.kt index 2897e66140..75a4fac7d0 100644 --- a/ui/show/seasons/src/main/java/app/tivi/showdetails/seasons/ShowSeasonsPresenter.kt +++ b/ui/show/seasons/src/main/java/app/tivi/showdetails/seasons/ShowSeasonsPresenter.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope import app.tivi.data.models.TiviShow @@ -17,8 +18,6 @@ import app.tivi.domain.observers.ObserveShowSeasonsEpisodesWatches import app.tivi.screens.EpisodeDetailsScreen import app.tivi.screens.ShowSeasonsScreen import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen @@ -54,12 +53,11 @@ class ShowSeasonsPresenter( override fun present(): ShowSeasonsUiState { val scope = rememberCoroutineScope() - val loadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } val seasons by observeShowSeasons.flow.collectAsState(emptyList()) val show by observeShowDetails.flow.collectAsState(TiviShow.EMPTY_SHOW) - val refreshing by loadingState.observable.collectAsState(false) + val refreshing by updateShowSeasons.inProgress.collectAsState(false) val message by uiMessageManager.message.collectAsState(null) fun eventSink(event: ShowSeasonsUiEvent) { @@ -74,7 +72,12 @@ class ShowSeasonsPresenter( scope.launch { updateShowSeasons( UpdateShowSeasons.Params(screen.id, event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } diff --git a/ui/upnext/src/main/java/app/tivi/home/upnext/UpNextPresenter.kt b/ui/upnext/src/main/java/app/tivi/home/upnext/UpNextPresenter.kt index 1a6c91c3a4..e2f125c147 100644 --- a/ui/upnext/src/main/java/app/tivi/home/upnext/UpNextPresenter.kt +++ b/ui/upnext/src/main/java/app/tivi/home/upnext/UpNextPresenter.kt @@ -13,13 +13,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.paging.PagingConfig import androidx.paging.compose.collectAsLazyPagingItems +import app.tivi.api.UiMessage import app.tivi.api.UiMessageManager import app.tivi.common.compose.rememberCoroutineScope import app.tivi.data.models.SortOption import app.tivi.data.traktauth.TraktAuthState -import app.tivi.domain.executeSync import app.tivi.domain.interactors.GetTraktAuthState import app.tivi.domain.interactors.UpdateUpNextEpisodes +import app.tivi.domain.invoke import app.tivi.domain.observers.ObservePagedUpNextShows import app.tivi.domain.observers.ObserveTraktAuthState import app.tivi.domain.observers.ObserveUserDetails @@ -30,8 +31,6 @@ import app.tivi.screens.ShowSeasonsScreen import app.tivi.screens.UpNextScreen import app.tivi.settings.TiviPreferences import app.tivi.util.Logger -import app.tivi.util.ObservableLoadingCounter -import app.tivi.util.collectStatus import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.Screen @@ -71,14 +70,13 @@ class UpNextPresenter( override fun present(): UpNextUiState { val scope = rememberCoroutineScope() - val loadingState = remember { ObservableLoadingCounter() } val uiMessageManager = remember { UiMessageManager() } val items = observePagedUpNextShows.flow.collectAsLazyPagingItems() var sort by remember { mutableStateOf(SortOption.LAST_WATCHED) } - val loading by loadingState.observable.collectAsState(false) + val loading by updateUpNextEpisodes.inProgress.collectAsState(false) val message by uiMessageManager.message.collectAsState(null) val user by observeUserDetails.flow.collectAsState(null) @@ -98,10 +96,15 @@ class UpNextPresenter( } is UpNextUiEvent.Refresh -> { scope.launch { - if (getTraktAuthState.executeSync() == TraktAuthState.LOGGED_IN) { + if (getTraktAuthState.invoke().getOrThrow() == TraktAuthState.LOGGED_IN) { updateUpNextEpisodes( UpdateUpNextEpisodes.Params(event.fromUser), - ).collectStatus(loadingState, logger, uiMessageManager) + ).also { result -> + result.exceptionOrNull()?.let { e -> + logger.i(e) + uiMessageManager.emitMessage(UiMessage(e)) + } + } } } }