From de0e7758c9e9374dcd6cff1a7cb2d34f90fd268a Mon Sep 17 00:00:00 2001 From: DatLag Date: Fri, 10 May 2024 13:41:11 +0200 Subject: [PATCH] improve flow performance --- .../aniflow/anilist/AiringTodayRepository.kt | 18 ++++++---- .../anilist/PopularNextSeasonRepository.kt | 17 ++++++---- .../anilist/PopularSeasonRepository.kt | 21 ++++++++---- .../aniflow/anilist/TrendingRepository.kt | 21 ++++++++---- .../dev/datlag/aniflow/other/UserHelper.kt | 8 +++-- .../screen/component/CollapsingToolbar.kt | 3 +- .../ui/navigation/screen/nekos/NekosScreen.kt | 34 +++++++++++++++---- .../datlag/aniflow/nekos/NekosRepository.kt | 21 ++++-------- .../datlag/aniflow/trace/TraceRepository.kt | 7 ++-- 9 files changed, 97 insertions(+), 53 deletions(-) diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/AiringTodayRepository.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/AiringTodayRepository.kt index 5be5087..426c5a9 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/AiringTodayRepository.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/AiringTodayRepository.kt @@ -4,6 +4,7 @@ import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.type.AiringSort +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.datetime.Clock import kotlinx.serialization.Serializable @@ -23,12 +24,16 @@ class AiringTodayRepository( ) }.distinctUntilChanged() - private val airingPreFilter = query.transform { - return@transform emitAll(apolloClient.query(it.toGraphQL()).toFlow()) + @OptIn(ExperimentalCoroutinesApi::class) + private val airingPreFilter = query.transformLatest { + return@transformLatest emitAll(apolloClient.query(it.toGraphQL()).toFlow()) } - private val fallbackPreFilter = query.transform { - return@transform emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) + + @OptIn(ExperimentalCoroutinesApi::class) + private val fallbackPreFilter = query.transformLatest { + return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) } + private val fallbackQuery = combine(fallbackPreFilter, nsfw.distinctUntilChanged()) { q, n -> val data = q.data if (data == null) { @@ -42,6 +47,7 @@ class AiringTodayRepository( } }.filterNotNull() + @OptIn(ExperimentalCoroutinesApi::class) val airing = combine(airingPreFilter, nsfw.distinctUntilChanged()) { q, n -> val data = q.data if (data == null) { @@ -53,8 +59,8 @@ class AiringTodayRepository( } else { State.fromGraphQL(data, n) } - }.filterNotNull().transform { - return@transform if (it is State.Error) { + }.filterNotNull().transformLatest { + return@transformLatest if (it is State.Error) { emitAll(fallbackQuery) } else { emit(it) diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularNextSeasonRepository.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularNextSeasonRepository.kt index 9149513..3a291c6 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularNextSeasonRepository.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularNextSeasonRepository.kt @@ -7,6 +7,7 @@ import dev.datlag.aniflow.anilist.state.CollectionState import dev.datlag.aniflow.anilist.type.MediaSeason import dev.datlag.aniflow.anilist.type.MediaSort import dev.datlag.aniflow.anilist.type.MediaType +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.datetime.Clock @@ -17,6 +18,7 @@ class PopularNextSeasonRepository( ) { private val page = MutableStateFlow(0) + private val query = combine(page, nsfw.distinctUntilChanged()) { p, n -> val (season, year) = Clock.System.now().nextSeason @@ -27,8 +29,10 @@ class PopularNextSeasonRepository( year = year ) }.distinctUntilChanged() - private val fallbackQuery = query.transform { - return@transform emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) + + @OptIn(ExperimentalCoroutinesApi::class) + private val fallbackQuery = query.transformLatest { + return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) }.mapNotNull { val data = it.data if (data == null) { @@ -42,8 +46,9 @@ class PopularNextSeasonRepository( } } - val popularNextSeason = query.transform { - return@transform emitAll(apolloClient.query(it.toGraphQL()).toFlow()) + @OptIn(ExperimentalCoroutinesApi::class) + val popularNextSeason = query.transformLatest { + return@transformLatest emitAll(apolloClient.query(it.toGraphQL()).toFlow()) }.mapNotNull { val data = it.data if (data == null) { @@ -55,8 +60,8 @@ class PopularNextSeasonRepository( } else { CollectionState.fromSeasonGraphQL(data) } - }.transform { - return@transform if (it.isError) { + }.transformLatest { + return@transformLatest if (it.isError) { emitAll(fallbackQuery) } else { emit(it) diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularSeasonRepository.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularSeasonRepository.kt index 7ad69b7..4d4750c 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularSeasonRepository.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/PopularSeasonRepository.kt @@ -5,6 +5,7 @@ import com.apollographql.apollo3.api.Optional import dev.datlag.aniflow.anilist.state.CollectionState import dev.datlag.aniflow.anilist.type.MediaSort import dev.datlag.aniflow.anilist.type.MediaType +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* class PopularSeasonRepository( @@ -15,7 +16,9 @@ class PopularSeasonRepository( ) { private val page = MutableStateFlow(0) - private val type = viewManga.distinctUntilChanged().map { + + @OptIn(ExperimentalCoroutinesApi::class) + private val type = viewManga.distinctUntilChanged().mapLatest { page.update { 0 } if (it) { MediaType.MANGA @@ -23,6 +26,7 @@ class PopularSeasonRepository( MediaType.ANIME } } + private val query = combine(page, type, nsfw.distinctUntilChanged()) { p, t, n -> Query( page = p, @@ -30,8 +34,10 @@ class PopularSeasonRepository( nsfw = n ) }.distinctUntilChanged() - private val fallbackQuery = query.transform { - return@transform emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) + + @OptIn(ExperimentalCoroutinesApi::class) + private val fallbackQuery = query.transformLatest { + return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) }.mapNotNull { val data = it.data if (data == null) { @@ -45,8 +51,9 @@ class PopularSeasonRepository( } } - val popularThisSeason = query.transform { - return@transform emitAll(apolloClient.query(it.toGraphQL()).toFlow()) + @OptIn(ExperimentalCoroutinesApi::class) + val popularThisSeason = query.transformLatest { + return@transformLatest emitAll(apolloClient.query(it.toGraphQL()).toFlow()) }.mapNotNull { val data = it.data if (data == null) { @@ -58,8 +65,8 @@ class PopularSeasonRepository( } else { CollectionState.fromSeasonGraphQL(data) } - }.transform { - return@transform if (it.isError) { + }.transformLatest { + return@transformLatest if (it.isError) { emitAll(fallbackQuery) } else { emit(it) diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/TrendingRepository.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/TrendingRepository.kt index a9a54d9..28f2b03 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/TrendingRepository.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/TrendingRepository.kt @@ -6,6 +6,7 @@ import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.state.CollectionState import dev.datlag.aniflow.anilist.type.MediaSort import dev.datlag.aniflow.anilist.type.MediaType +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.serialization.Serializable @@ -17,7 +18,9 @@ class TrendingRepository( ) { private val page = MutableStateFlow(0) - private val type = viewManga.distinctUntilChanged().map { + + @OptIn(ExperimentalCoroutinesApi::class) + private val type = viewManga.distinctUntilChanged().mapLatest { page.update { 0 } if (it) { MediaType.MANGA @@ -25,6 +28,7 @@ class TrendingRepository( MediaType.ANIME } } + private val query = combine(page, type, nsfw.distinctUntilChanged()) { p, t, n -> Query( page = p, @@ -32,8 +36,10 @@ class TrendingRepository( nsfw = n ) }.distinctUntilChanged() - private val fallbackQuery = query.transform { - return@transform emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) + + @OptIn(ExperimentalCoroutinesApi::class) + private val fallbackQuery = query.transformLatest { + return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) }.mapNotNull { val data = it.data if (data == null) { @@ -47,8 +53,9 @@ class TrendingRepository( } } - val trending = query.transform { - return@transform emitAll(apolloClient.query(it.toGraphQL()).toFlow()) + @OptIn(ExperimentalCoroutinesApi::class) + val trending = query.transformLatest { + return@transformLatest emitAll(apolloClient.query(it.toGraphQL()).toFlow()) }.mapNotNull { val data = it.data if (data == null) { @@ -60,8 +67,8 @@ class TrendingRepository( } else { CollectionState.fromTrendingGraphQL(data) } - }.transform { - return@transform if (it.isError) { + }.transformLatest { + return@transformLatest if (it.isError) { emitAll(fallbackQuery) } else { emit(it) diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt index 3bd28fc..1292d8c 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt @@ -23,6 +23,7 @@ import dev.datlag.tooling.compose.withIOContext import dev.datlag.tooling.compose.withMainContext import io.github.aakira.napier.Napier import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking @@ -43,8 +44,8 @@ class UserHelper( val isLoggedIn: Flow = userSettings.isAniListLoggedIn.flowOn(ioDispatcher()).distinctUntilChanged() val loginUrl: String = "https://anilist.co/api/v2/oauth/authorize?client_id=$clientId&response_type=token" - @OptIn(DelicateCoroutinesApi::class) - private val updatableUser = isLoggedIn.transform { loggedIn -> + @OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) + private val updatableUser = isLoggedIn.transformLatest { loggedIn -> if (loggedIn) { emitAll( client.query(ViewerQuery()).fetchPolicy(FetchPolicy.NetworkFirst).toFlow().map { @@ -59,7 +60,8 @@ class UserHelper( initialValue = null ) - val user = updatableUser.map { user -> + @OptIn(ExperimentalCoroutinesApi::class) + val user = updatableUser.mapLatest { user -> user?.also { appSettings.setData( adultContent = it.displayAdultContent, diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/CollapsingToolbar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/CollapsingToolbar.kt index 6210273..ac6de54 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/CollapsingToolbar.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/CollapsingToolbar.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.MenuBook +import androidx.compose.material.icons.automirrored.rounded.MenuBook import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.PlayCircleFilled import androidx.compose.material3.* @@ -158,7 +159,7 @@ fun CollapsingToolbar( enabled = !isManga ) { Icon( - imageVector = Icons.AutoMirrored.Default.MenuBook, + imageVector = Icons.AutoMirrored.Rounded.MenuBook, contentDescription = null, tint = if (isManga) { MaterialTheme.colorScheme.primary diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/nekos/NekosScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/nekos/NekosScreen.kt index db33622..c828041 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/nekos/NekosScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/nekos/NekosScreen.kt @@ -1,14 +1,12 @@ package dev.datlag.aniflow.ui.navigation.screen.nekos import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBackIos import androidx.compose.material.icons.rounded.ArrowBackIos @@ -16,7 +14,9 @@ import androidx.compose.material.icons.rounded.FilterList import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalUriHandler @@ -142,8 +142,30 @@ fun NekosScreen(component: NekosComponent) { val state by component.state.collectAsStateWithLifecycle(NekosRepository.State.None) when (val current = state) { - is NekosRepository.State.None -> Text(text = "Loading") - is NekosRepository.State.Error -> Text(text = "Error") + is NekosRepository.State.None -> { + Box( + modifier = Modifier.fillMaxSize().padding(padding), + contentAlignment = Alignment.Center + ) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(0.2F).clip(CircleShape) + ) + } + } + is NekosRepository.State.Error -> { + Box( + modifier = Modifier.fillMaxSize().padding(padding), + contentAlignment = Alignment.Center + ) { + Button( + onClick = { + component.back() + } + ) { + Text(text = stringResource(SharedRes.strings.back)) + } + } + } is NekosRepository.State.Success -> { val uriHandler = LocalUriHandler.current diff --git a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt index f1e4d84..98fc69f 100644 --- a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt +++ b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt @@ -3,6 +3,7 @@ package dev.datlag.aniflow.nekos import dev.datlag.aniflow.model.CatchResult import dev.datlag.aniflow.nekos.model.ImagesResponse import dev.datlag.aniflow.nekos.model.Rating +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.serialization.Serializable @@ -11,17 +12,16 @@ class NekosRepository( private val nsfw: Flow = flowOf(false), ) { - private val offset = MutableStateFlow(null) val rating = MutableStateFlow(Rating.Safe) - private val result = combine(offset, rating) { o, r -> + @OptIn(ExperimentalCoroutinesApi::class) + private val result = rating.mapLatest { CatchResult.repeat(2) { nekos.images( - rating = r.query, - offset = o, + rating = it.query ) } - }.map { + }.mapLatest { State.fromResponse(it.asNullableSuccess()) } val response = combine(nsfw.distinctUntilChanged(), result, rating) { n, q, r -> @@ -46,17 +46,8 @@ class NekosRepository( } } - fun offset(value: Int) { - offset.update { value } - } - fun rating(value: Rating) { - rating.update { - if (it != value) { - offset.update { null } - } - value - } + rating.update { value } } @Serializable diff --git a/trace/src/commonMain/kotlin/dev/datlag/aniflow/trace/TraceRepository.kt b/trace/src/commonMain/kotlin/dev/datlag/aniflow/trace/TraceRepository.kt index 5ff5893..81244a9 100644 --- a/trace/src/commonMain/kotlin/dev/datlag/aniflow/trace/TraceRepository.kt +++ b/trace/src/commonMain/kotlin/dev/datlag/aniflow/trace/TraceRepository.kt @@ -2,6 +2,7 @@ package dev.datlag.aniflow.trace import dev.datlag.aniflow.model.CatchResult import dev.datlag.aniflow.trace.model.SearchResponse +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.serialization.Serializable @@ -10,8 +11,10 @@ class TraceRepository( ) { private val byteArray = MutableStateFlow(null) - val response: Flow = byteArray.transform { - return@transform if (it == null || it.isEmpty()) { + + @OptIn(ExperimentalCoroutinesApi::class) + val response: Flow = byteArray.transformLatest { + return@transformLatest if (it == null || it.isEmpty()) { emit(State.None) } else { emit(