diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/MediumStateMachine.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/MediumStateMachine.kt index 6864a0c..0fa3981 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/MediumStateMachine.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/MediumStateMachine.kt @@ -62,8 +62,8 @@ class MediumStateMachine( } inState { onEnterEffect { - Cache.setMedium(it.query, it.data) currentState = it + Cache.setMedium(it.query, it.data) } } inState { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendComponent.kt index 47640b8..fb1f445 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendComponent.kt @@ -8,6 +8,7 @@ import dev.datlag.aniflow.ui.navigation.Component import dev.datlag.aniflow.ui.theme.SchemeTheme import dev.datlag.tooling.decompose.lifecycle.LocalLifecycleOwner import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -57,4 +58,13 @@ fun StateFlow.mapCollect(transform: (value: T) -> R): State { }.collectAsStateWithLifecycle( initialValue = transform(this.value) ) +} + +@Composable +fun Flow.mapCollect(defaultValue: T, transform: (value: T) -> R): State { + return remember(this) { + this.map(transform) + }.collectAsStateWithLifecycle( + initialValue = transform(defaultValue) + ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt index 42970e3..bdf7d4c 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt @@ -15,34 +15,35 @@ import kotlinx.coroutines.flow.StateFlow interface MediumComponent : ContentHolderComponent { val initialMedium: Medium + val initialState: MediumStateMachine.State - val mediumState: StateFlow - val isAdult: StateFlow + val mediumState: Flow + val isAdult: Flow val isAdultAllowed: Flow - val bannerImage: StateFlow - val coverImage: StateFlow - val title: StateFlow - val description: StateFlow + val bannerImage: Flow + val coverImage: Flow + val title: Flow + val description: Flow val translatedDescription: StateFlow - val genres: StateFlow> - - val format: StateFlow - val episodes: StateFlow - val duration: StateFlow - val status: StateFlow - - val rated: StateFlow - val popular: StateFlow - val score: StateFlow - - val characters: StateFlow> - val rating: StateFlow - val alreadyAdded: StateFlow - val trailer: StateFlow - val isFavorite: StateFlow - val isFavoriteBlocked: StateFlow - val siteUrl: StateFlow + val genres: Flow> + + val format: Flow + val episodes: Flow + val duration: Flow + val status: Flow + + val rated: Flow + val popular: Flow + val score: Flow + + val characters: Flow> + val rating: Flow + val alreadyAdded: Flow + val trailer: Flow + val isFavorite: Flow + val isFavoriteBlocked: Flow + val siteUrl: Flow val bsAvailable: Boolean diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt index 25b1285..21a9ead 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt @@ -41,7 +41,7 @@ import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle @OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class, ExperimentalFoundationApi::class) @Composable fun MediumScreen(component: MediumComponent) { - val coverImage by component.coverImage.collectAsStateWithLifecycle() + val coverImage by component.coverImage.collectAsStateWithLifecycle(component.initialMedium.coverImage) val dialogState by component.dialog.subscribeAsState() dialogState.child?.instance?.render() @@ -64,6 +64,7 @@ fun MediumScreen(component: MediumComponent) { CollapsingToolbar( state = appBarState, scrollBehavior = scrollState, + initialMedium = component.initialMedium, mediumStateFlow = component.mediumState, bannerImageFlow = component.bannerImage, coverImage = coverImage, @@ -72,16 +73,19 @@ fun MediumScreen(component: MediumComponent) { isFavoriteBlockedFlow = component.isFavoriteBlocked, siteUrlFlow = component.siteUrl, showShare = listState.isScrollingUp(), + initialState = { component.initialState }, onBack = { component.back() }, onToggleFavorite = { component.toggleFavorite() } ) }, floatingActionButton = { - val userRating by component.rating.collectAsStateWithLifecycle() + val userRating by component.rating.collectAsStateWithLifecycle(-1) val ratingState = rememberUseCaseState() - val alreadyAdded by component.alreadyAdded.collectAsStateWithLifecycle() - val notReleased by component.status.mapCollect { + val alreadyAdded by component.alreadyAdded.collectAsStateWithLifecycle( + component.initialMedium.entry != null + ) + val notReleased by component.status.mapCollect(component.initialMedium.status) { it == MediaStatus.UNKNOWN__ || it == MediaStatus.NOT_YET_RELEASED } @@ -138,6 +142,7 @@ fun MediumScreen(component: MediumComponent) { item { CoverSection( coverImage = coverImage, + initialMedium = component.initialMedium, formatFlow = component.format, episodesFlow = component.episodes, durationFlow = component.duration, @@ -147,6 +152,7 @@ fun MediumScreen(component: MediumComponent) { } item { RatingSection( + initialMedium = component.initialMedium, ratedFlow = component.rated, popularFlow = component.popular, scoreFlow = component.score, @@ -155,12 +161,14 @@ fun MediumScreen(component: MediumComponent) { } item { GenreSection( + initialMedium = component.initialMedium, genreFlow = component.genres, modifier = Modifier.fillParentMaxWidth() ) } item { DescriptionSection( + initialMedium = component.initialMedium, descriptionFlow = component.description, translatedDescriptionFlow = component.translatedDescription, modifier = Modifier.fillParentMaxWidth() @@ -170,6 +178,7 @@ fun MediumScreen(component: MediumComponent) { } item { CharacterSection( + initialMedium = component.initialMedium, characterFlow = component.characters, modifier = Modifier.fillParentMaxWidth().animateItemPlacement() ) { char -> @@ -178,6 +187,7 @@ fun MediumScreen(component: MediumComponent) { } item { TrailerSection( + initialMedium = component.initialMedium, trailerFlow = component.trailer, modifier = Modifier.fillParentMaxWidth().animateItemPlacement() ) @@ -187,6 +197,7 @@ fun MediumScreen(component: MediumComponent) { } AdultSection( + initialMedium = component.initialMedium, isAdultContentFlow = component.isAdult, isAdultContentAllowedFlow = component.isAdultAllowed, onBack = component::back diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt index 94a9518..50b6522 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt @@ -62,119 +62,84 @@ class MediumScreenComponent( override val mediumState = mediumStateMachine.state.flowOn( context = ioDispatcher() - ).stateIn( + ).shareIn( scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = mediumStateMachine.currentState + started = SharingStarted.WhileSubscribed() ) + override val initialState: MediumStateMachine.State + get() = mediumStateMachine.currentState + private val mediumSuccessState = mediumState.mapNotNull { it.safeCast() }.flowOn( context = ioDispatcher() - ).stateIn( + ).shareIn( scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = null + started = SharingStarted.WhileSubscribed() ) - override val isAdult: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.isAdult + override val isAdult: Flow = mediumSuccessState.mapNotNull { + it.data.isAdult }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.isAdult ) override val isAdultAllowed: Flow = appSettings.adultContent - private val type: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.type + private val type: Flow = mediumSuccessState.mapNotNull { + it.data.type }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.type ) - override val bannerImage: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.bannerImage + override val bannerImage: Flow = mediumSuccessState.mapNotNull { + it.data.bannerImage }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.bannerImage ) - override val coverImage: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.coverImage + override val coverImage: Flow = mediumSuccessState.mapNotNull { + it.data.coverImage }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.coverImage ) - override val title: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.title + override val title: Flow = mediumSuccessState.mapNotNull { + it.data.title }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.title ) - override val description: StateFlow = mediumSuccessState.map { - it?.data?.description?.ifBlank { null } + override val description: Flow = mediumSuccessState.map { + it.data.description?.ifBlank { null } }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.description ) override val translatedDescription: MutableStateFlow = MutableStateFlow(null) - override val genres: StateFlow> = mediumSuccessState.mapNotNull { - it?.data?.genres?.ifEmpty { null } + override val genres: Flow> = mediumSuccessState.mapNotNull { + it.data.genres.ifEmpty { null } }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.genres ) - override val format: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.format + override val format: Flow = mediumSuccessState.mapNotNull { + it.data.format }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.format ) - private val nextAiringEpisode: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.nextAiringEpisode + private val nextAiringEpisode: Flow = mediumSuccessState.mapNotNull { + it.data.nextAiringEpisode }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.nextAiringEpisode ) - override val episodes: StateFlow = combine( + override val episodes: Flow = combine( mediumSuccessState.map { - it?.data?.episodes + it.data.episodes }, nextAiringEpisode ) { episodes, airing -> @@ -191,107 +156,54 @@ class MediumScreenComponent( } }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = run { - val airing = nextAiringEpisode.value ?: return@run -1 - - if (Instant.fromEpochSeconds(airing.airingAt.toLong()) <= Clock.System.now()) { - airing.episodes - } else { - airing.episodes - 1 - } - } ) - override val duration: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.avgEpisodeDurationInMin + override val duration: Flow = mediumSuccessState.mapNotNull { + it.data.avgEpisodeDurationInMin }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.avgEpisodeDurationInMin ) - override val status: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.status + override val status: Flow = mediumSuccessState.mapNotNull { + it.data.status }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.status ) - override val rated: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.rated() + override val rated: Flow = mediumSuccessState.mapNotNull { + it.data.rated() }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.rated() ) - override val popular: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.popular() + override val popular: Flow = mediumSuccessState.mapNotNull { + it.data.popular() }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.popular() ) - override val score: StateFlow = mediumSuccessState.mapNotNull { - val received = it?.data?.averageScore - if (received == null || received == -1) { - null - } else { - received - } + override val score: Flow = mediumSuccessState.mapNotNull { + it.data.averageScore.ifValue(-1) { return@mapNotNull null } }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = run { - val initial = initialMedium.averageScore - - if (initial == -1) { - null - } else { - initial - } - } ) - override val characters: StateFlow> = mediumSuccessState.mapNotNull { - it?.data?.characters + override val characters: Flow> = mediumSuccessState.mapNotNull { + it.data.characters }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = emptySet() ) - private val mediaId: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.id + private val mediaId: Flow = mediumSuccessState.mapNotNull { + it.data.id }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.id ) private val changedRating: MutableStateFlow = MutableStateFlow(initialMedium.entry?.score?.toInt() ?: -1) - override val rating: StateFlow = combine( + override val rating: Flow = combine( mediumSuccessState.map { - it?.data?.entry?.score?.toInt() + it.data.entry?.score?.toInt() }.flowOn(ioDispatcher()), changedRating ) { t1, t2 -> @@ -302,60 +214,36 @@ class MediumScreenComponent( } }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = changedRating.value ) - override val trailer: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.trailer + override val trailer: Flow = mediumSuccessState.mapNotNull { + it.data.trailer }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.trailer ) - override val alreadyAdded: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.entry != null + override val alreadyAdded: Flow = mediumSuccessState.mapNotNull { + it.data.entry != null }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.entry != null ) - override val isFavorite: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.isFavorite + override val isFavorite: Flow = mediumSuccessState.mapNotNull { + it.data.isFavorite }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.isFavorite ) - override val isFavoriteBlocked: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.isFavoriteBlocked + override val isFavoriteBlocked: Flow = mediumSuccessState.mapNotNull { + it.data.isFavoriteBlocked }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.isFavoriteBlocked ) - override val siteUrl: StateFlow = mediumSuccessState.mapNotNull { - it?.data?.siteUrl + override val siteUrl: Flow = mediumSuccessState.mapNotNull { + it.data.siteUrl }.flowOn( context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = initialMedium.siteUrl ) private val burningSeriesResolver by di.instance() @@ -405,7 +293,7 @@ class MediumScreenComponent( private suspend fun requestMediaListEntry() { val query = MediaListEntryQuery( - id = Optional.present(mediaId.safeFirstOrNull() ?: mediaId.value) + id = Optional.present(initialMedium.id) ) val execution = CatchResult.timeout(5.seconds) { aniListClient.query(query).execute() @@ -419,7 +307,7 @@ class MediumScreenComponent( override fun rate(onLoggedIn: () -> Unit) { launchIO { if (userHelper.login()) { - val currentRating = rating.safeFirstOrNull() ?: rating.value + val currentRating = rating.safeFirstOrNull() ?: initialMedium.entry?.score?.toInt() ?: -1 if (currentRating <= -1) { requestMediaListEntry() } @@ -433,7 +321,7 @@ class MediumScreenComponent( override fun rate(value: Int) { val mutation = RatingMutation( - mediaId = Optional.present(mediaId.value), + mediaId = Optional.present(initialMedium.id), rating = Optional.present(value * 20) ) launchIO { @@ -453,12 +341,12 @@ class MediumScreenComponent( override fun toggleFavorite() { launchIO { - val mediaType = type.safeFirstOrNull() ?: type.value + val mediaType = type.safeFirstOrNull() ?: initialMedium.type if (mediaType == MediaType.UNKNOWN__) { return@launchIO } - val id = mediaId.safeFirstOrNull() ?: mediaId.value + val id = initialMedium.id val mutation = FavoriteToggleMutation( animeId = if (mediaType == MediaType.ANIME) { Optional.present(id) diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/AdultSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/AdultSection.kt index 4b9048f..ad9a44f 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/AdultSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/AdultSection.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import dev.datlag.aniflow.SharedRes +import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -24,11 +25,12 @@ import dev.icerock.moko.resources.compose.stringResource @Composable fun AdultSection( - isAdultContentFlow: StateFlow, + initialMedium: Medium, + isAdultContentFlow: Flow, isAdultContentAllowedFlow: Flow, onBack: () -> Unit ) { - val isAdult by isAdultContentFlow.collectAsStateWithLifecycle() + val isAdult by isAdultContentFlow.collectAsStateWithLifecycle(initialMedium.isAdult) val isAdultAllowed by isAdultContentAllowedFlow.collectAsStateWithLifecycle(false) val hideContent = remember(isAdult, isAdultAllowed) { isAdult && !isAdultAllowed } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CharacterSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CharacterSection.kt index 7d21fa6..a2e782d 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CharacterSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CharacterSection.kt @@ -12,17 +12,20 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.anilist.model.Character +import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Composable fun CharacterSection( - characterFlow: StateFlow>, + initialMedium: Medium, + characterFlow: Flow>, modifier: Modifier = Modifier, onClick: (Character) -> Unit ) { - val characters by characterFlow.collectAsStateWithLifecycle() + val characters by characterFlow.collectAsStateWithLifecycle(initialMedium.characters) if (characters.isNotEmpty()) { Column( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CollapsingToolbar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CollapsingToolbar.kt index 9fb99aa..6b6d2f4 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CollapsingToolbar.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CollapsingToolbar.kt @@ -37,6 +37,7 @@ import dev.datlag.aniflow.ui.custom.shareHandler import dev.datlag.tooling.compose.ifFalse import dev.datlag.tooling.compose.ifTrue import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlin.math.max import kotlin.math.min @@ -46,21 +47,23 @@ import kotlin.math.min fun CollapsingToolbar( state: TopAppBarState, scrollBehavior: TopAppBarScrollBehavior, - mediumStateFlow: StateFlow, - bannerImageFlow: StateFlow, + initialMedium: Medium, + mediumStateFlow: Flow, + bannerImageFlow: Flow, coverImage: Medium.CoverImage, - titleFlow: StateFlow, - isFavoriteFlow: StateFlow, - isFavoriteBlockedFlow: StateFlow, - siteUrlFlow: StateFlow, + titleFlow: Flow, + isFavoriteFlow: Flow, + isFavoriteBlockedFlow: Flow, + siteUrlFlow: Flow, showShare: Boolean, + initialState: () -> MediumStateMachine.State, onBack: () -> Unit, onToggleFavorite: () -> Unit ) { Box( modifier = Modifier.fillMaxWidth() ) { - val bannerImage by bannerImageFlow.collectAsStateWithLifecycle() + val bannerImage by bannerImageFlow.collectAsStateWithLifecycle(initialMedium.bannerImage) val isCollapsed by remember(state) { derivedStateOf { state.collapsedFraction >= 0.99F } } @@ -106,7 +109,7 @@ fun CollapsingToolbar( } }, title = { - val title by titleFlow.collectAsStateWithLifecycle() + val title by titleFlow.collectAsStateWithLifecycle(initialMedium.title) Column( modifier = Modifier.fillMaxWidth(), @@ -160,8 +163,8 @@ fun CollapsingToolbar( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, ) { - val mediumState by mediumStateFlow.collectAsStateWithLifecycle() - val siteUrl by siteUrlFlow.collectAsStateWithLifecycle() + val mediumState by mediumStateFlow.collectAsStateWithLifecycle(initialState()) + val siteUrl by siteUrlFlow.collectAsStateWithLifecycle(initialMedium.siteUrl) val shareHandler = shareHandler() AnimatedVisibility( @@ -169,8 +172,8 @@ fun CollapsingToolbar( enter = fadeIn(), exit = fadeOut() ) { - val isFavoriteBlocked by isFavoriteBlockedFlow.collectAsStateWithLifecycle() - val isFavorite by isFavoriteFlow.collectAsStateWithLifecycle() + val isFavoriteBlocked by isFavoriteBlockedFlow.collectAsStateWithLifecycle(initialMedium.isFavoriteBlocked) + val isFavorite by isFavoriteFlow.collectAsStateWithLifecycle(initialMedium.isFavorite) var favoriteChanged by remember(isFavorite) { mutableStateOf(null) } IconButton( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt index 29a9c27..1f8d249 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt @@ -25,15 +25,17 @@ import dev.datlag.aniflow.common.text import dev.datlag.tooling.compose.ifTrue import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Composable fun CoverSection( coverImage: Medium.CoverImage, - formatFlow: StateFlow, - episodesFlow: StateFlow, - durationFlow: StateFlow, - statusFlow: StateFlow, + initialMedium: Medium, + formatFlow: Flow, + episodesFlow: Flow, + durationFlow: Flow, + statusFlow: Flow, modifier: Modifier = Modifier ) { Row( @@ -82,10 +84,10 @@ fun CoverSection( modifier = Modifier.weight(1F).fillMaxHeight(), verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) ) { - val format by formatFlow.collectAsStateWithLifecycle() - val episodes by episodesFlow.collectAsStateWithLifecycle() - val duration by durationFlow.collectAsStateWithLifecycle() - val status by statusFlow.collectAsStateWithLifecycle() + val format by formatFlow.collectAsStateWithLifecycle(initialMedium.format) + val episodes by episodesFlow.collectAsStateWithLifecycle(initialMedium.episodes) + val duration by durationFlow.collectAsStateWithLifecycle(initialMedium.avgEpisodeDurationInMin) + val status by statusFlow.collectAsStateWithLifecycle(initialMedium.status) Row( modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/DescriptionSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/DescriptionSection.kt index d0d910b..c531649 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/DescriptionSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/DescriptionSection.kt @@ -16,21 +16,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import dev.datlag.aniflow.SharedRes +import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.common.htmlToAnnotatedString import dev.datlag.tooling.compose.onClick import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlin.math.max @Composable fun DescriptionSection( - descriptionFlow: StateFlow, + initialMedium: Medium, + descriptionFlow: Flow, translatedDescriptionFlow: StateFlow, modifier: Modifier = Modifier, onTranslation: (String?) -> Unit ) { - val description by descriptionFlow.collectAsStateWithLifecycle() + val description by descriptionFlow.collectAsStateWithLifecycle(initialMedium.description) if (!description.isNullOrBlank()) { Column( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/GenreSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/GenreSection.kt index 861706f..b785e37 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/GenreSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/GenreSection.kt @@ -9,16 +9,19 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.ui.navigation.screen.initial.home.component.GenreChip import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Composable fun GenreSection( - genreFlow: StateFlow>, + initialMedium: Medium, + genreFlow: Flow>, modifier: Modifier = Modifier, ) { - val genres by genreFlow.collectAsStateWithLifecycle() + val genres by genreFlow.collectAsStateWithLifecycle(initialMedium.genres) if (genres.isNotEmpty()) { LazyRow( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/RatingSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/RatingSection.kt index c184697..0ac2c3d 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/RatingSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/RatingSection.kt @@ -11,15 +11,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.anilist.model.Medium +import dev.datlag.aniflow.common.popular +import dev.datlag.aniflow.common.rated import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Composable fun RatingSection( - ratedFlow: StateFlow, - popularFlow: StateFlow, - scoreFlow: StateFlow, + initialMedium: Medium, + ratedFlow: Flow, + popularFlow: Flow, + scoreFlow: Flow, modifier: Modifier = Modifier, ) { Row( @@ -27,9 +31,9 @@ fun RatingSection( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceAround ) { - val rated by ratedFlow.collectAsStateWithLifecycle() - val popular by popularFlow.collectAsStateWithLifecycle() - val score by scoreFlow.collectAsStateWithLifecycle() + val rated by ratedFlow.collectAsStateWithLifecycle(initialMedium.rated()) + val popular by popularFlow.collectAsStateWithLifecycle(initialMedium.popular()) + val score by scoreFlow.collectAsStateWithLifecycle(initialMedium.averageScore) rated?.let { Column( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TrailerSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TrailerSection.kt index 94f6e54..442cc5d 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TrailerSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TrailerSection.kt @@ -19,14 +19,16 @@ import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Composable fun TrailerSection( - trailerFlow: StateFlow, + initialMedium: Medium, + trailerFlow: Flow, modifier: Modifier = Modifier ) { - val trailer by trailerFlow.collectAsStateWithLifecycle() + val trailer by trailerFlow.collectAsStateWithLifecycle(initialMedium.trailer) if (trailer != null) { Column(