From 11912d5d3730a6375a9e687061292158a06e38fd Mon Sep 17 00:00:00 2001 From: DatLag Date: Sat, 25 May 2024 17:41:59 +0200 Subject: [PATCH] added full discover functionality --- .../aniflow/anilist/DiscoverStateMachine.kt | 226 ++++++++---------- .../dev/datlag/aniflow/anilist/StateSaver.kt | 2 +- .../aniflow/anilist/model/PageMediaQuery.kt | 3 +- .../aniflow/anilist/state/DiscoverState.kt | 132 +++++----- .../datlag/aniflow/common/ExtendDiscovery.kt | 32 +++ .../datlag/aniflow/module/NetworkModule.kt | 2 +- .../default => component}/MediumCard.kt | 4 +- .../screen/discover/DiscoverComponent.kt | 3 + .../screen/discover/DiscoverScreen.kt | 91 ++++++- .../discover/DiscoverScreenComponent.kt | 14 ++ .../screen/discover/DiscoverType.kt | 47 ---- .../discover/component/DiscoverSearchBar.kt | 189 +++++++++++++++ .../discover/component/HidingSearchBar.kt | 206 ---------------- .../screen/discover/component/TypeCard.kt | 39 --- .../screen/home/component/DefaultOverview.kt | 6 +- .../moko-resources/base/strings.xml | 1 + 16 files changed, 492 insertions(+), 505 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendDiscovery.kt rename composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/{home/component/default => component}/MediumCard.kt (95%) delete mode 100644 composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverType.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/DiscoverSearchBar.kt delete mode 100644 composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/HidingSearchBar.kt delete mode 100644 composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/TypeCard.kt diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/DiscoverStateMachine.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/DiscoverStateMachine.kt index 70742f6..c73a0d6 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/DiscoverStateMachine.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/DiscoverStateMachine.kt @@ -1,13 +1,13 @@ package dev.datlag.aniflow.anilist import com.apollographql.apollo3.ApolloClient -import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import dev.datlag.aniflow.anilist.common.hasNonCacheError import dev.datlag.aniflow.anilist.common.season import dev.datlag.aniflow.anilist.model.PageListQuery import dev.datlag.aniflow.anilist.model.PageMediaQuery import dev.datlag.aniflow.anilist.model.User -import dev.datlag.aniflow.anilist.state.DiscoverAction import dev.datlag.aniflow.anilist.state.DiscoverState +import dev.datlag.aniflow.anilist.state.DiscoverListType import dev.datlag.aniflow.anilist.type.MediaSeason import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.firebase.FirebaseFactory @@ -19,12 +19,12 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.update import kotlinx.datetime.Clock -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime @OptIn(ExperimentalCoroutinesApi::class) class DiscoverStateMachine( @@ -34,8 +34,6 @@ class DiscoverStateMachine( private val nsfw: Flow = flowOf(false), private val viewManga: Flow = flowOf(false), private val crashlytics: FirebaseFactory.Crashlytics?, -) : FlowReduxStateMachine( - initialState = currentState ) { var currentState: DiscoverState @@ -44,6 +42,9 @@ class DiscoverStateMachine( Companion.currentState = value } + val currentListType: DiscoverListType + get() = _listType.value + private val _type: MutableStateFlow = MutableStateFlow(MediaType.UNKNOWN__) val type = _type.transformLatest { return@transformLatest if (it == MediaType.UNKNOWN__) { @@ -59,6 +60,18 @@ class DiscoverStateMachine( } }.distinctUntilChanged() + private val _listType: MutableStateFlow = MutableStateFlow(DiscoverListType.Recommendation) + val listType = combine( + _listType, + user.map { it?.id }.distinctUntilChanged() + ) { l, u -> + if (u == null) { + DiscoverListType.Season.fromSeason(Clock.System.now().season) + } else { + l + } + }.distinctUntilChanged() + private val recommendationQuery = combine( type, user.mapNotNull { it?.id }.distinctUntilChanged() @@ -69,140 +82,105 @@ class DiscoverStateMachine( ) }.distinctUntilChanged() - private val _season: MutableStateFlow = MutableStateFlow(MediaSeason.UNKNOWN__) - val season = _season.transformLatest { - return@transformLatest if (it == MediaSeason.UNKNOWN__) { - emit(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).month.season) + private val fallbackRecommendationResponse = combine( + recommendationQuery.transformLatest { + return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) + }, + nsfw.distinctUntilChanged() + ) { r, n -> + DiscoverState.Recommended.Loading.fromList(n, r) + }.transformLatest { + return@transformLatest when (it) { + is DiscoverState.Recommended.Loading.Matching -> { + emitAll(fallbackClient.query(it.query.toGraphQL()).toFlow().mapLatest { r -> + DiscoverState.Recommended.Loading.fromMatching(it, r) + }) + } + else -> emit(it) + } + } + + private val recommendationResponse = combine( + recommendationQuery.transformLatest { + return@transformLatest emitAll(client.query(it.toGraphQL()).toFlow()) + }, + nsfw.distinctUntilChanged() + ) { r, n -> + DiscoverState.Recommended.Loading.fromList(n, r) + }.transformLatest { + return@transformLatest if (it.isFailure) { + emitAll(fallbackRecommendationResponse) } else { - emit(it) + when (it) { + is DiscoverState.Recommended.Loading.Matching -> { + emitAll(client.query(it.query.toGraphQL()).toFlow().mapLatest { r -> + DiscoverState.Recommended.Loading.fromMatching(it, r) + }) + } + else -> emit(it) + } } - }.distinctUntilChanged() + } - private val seasonQuery = combine( - type, - season, - nsfw.distinctUntilChanged(), - ) { t, s, n -> + private fun seasonQuery(season: MediaSeason) = nsfw.distinctUntilChanged().mapLatest { n -> PageMediaQuery.Season( - type = t, - season = s, + season = season, nsfw = n ) }.distinctUntilChanged() - init { - spec { - inState { - onEnterEffect { - currentState = it - } - onActionEffect { action, _ -> - when (action) { - is DiscoverAction.Type.Anime -> _type.update { MediaType.ANIME } - is DiscoverAction.Type.Manga -> _type.update { MediaType.MANGA } - is DiscoverAction.Type.Toggle -> _type.update { - if (it == MediaType.MANGA) { - MediaType.ANIME - } else { - MediaType.MANGA - } - } - } - } - on { _, state -> - if (state is DiscoverState.Recommended) { - state.noChange() - } else { - state.override { - DiscoverState.Recommended.None - } - } - } - } + private fun fallbackSeasonResponse(season: MediaSeason) = seasonQuery(season).transformLatest { + return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) + } - inState { - on { action, state -> - state.override { - DiscoverState.Season.None( - wanted = action.mediaSeason - ) - } - } - collectWhileInState(recommendationQuery) { q, state -> - state.override { - DiscoverState.Recommended.Loading.WatchList( - query = q, - fallback = false - ) - } - } - } - inState { - collectWhileInState( - flowBuilder = { - val usedClient = if (it.fallback) { - fallbackClient - } else { - client - } - - combine( - nsfw.distinctUntilChanged(), - usedClient.query(it.query.toGraphQL()).toFlow() - ) { n, r -> - n to r - } - } - ) { (adult, response), state -> - state.override { - fromGraphQL(adult, response) - } - } - } - inState { - collectWhileInState( - flowBuilder = { - val usedClient = if (it.fallback) { - fallbackClient - } else { - client - } - - usedClient.query(it.query.toGraphQL()).toFlow() - } - ) { response, state -> - state.override { - fromGraphQL(response) - } - } - } + private fun seasonResponse(season: MediaSeason) = seasonQuery(season).transformLatest { + return@transformLatest emitAll(client.query(it.toGraphQL()).toFlow()) + }.transformLatest { + return@transformLatest if (it.hasNonCacheError()) { + emitAll(fallbackSeasonResponse(season)) + } else { + emit(it) + } + }.mapLatest { result -> + DiscoverState.Season.fromSeasonResponse(result) + } - inState { - collectWhileInState( - flowBuilder = { - val usedClient = if (it.fallback) { - fallbackClient - } else { - client - } - - usedClient.query(it.query.toGraphQL()).toFlow() - } - ) { response, state -> - state.override { - fromGraphQL(response) - } - } + val state = listType.transformLatest { type -> + return@transformLatest when (type) { + is DiscoverListType.Recommendation -> emitAll(recommendationResponse) + is DiscoverListType.Season -> emitAll(seasonResponse(type.mediaSeason)) + } + }.mapLatest { m -> + m.also { state -> + (state as? DiscoverState.Failure)?.throwable?.let { + crashlytics?.log(it) } + currentState = state + } + } - inState { - onEnterEffect { - crashlytics?.log(it.throwable) - } + fun viewAnime() { + _type.update { MediaType.ANIME } + } + + fun viewManga() { + _type.update { MediaType.MANGA } + } + + fun toggleType() { + _type.update { + if (it == MediaType.MANGA) { + MediaType.ANIME + } else { + MediaType.MANGA } } } + fun listType(type: DiscoverListType) { + _listType.update { type } + } + companion object { var currentState: DiscoverState get() = StateSaver.discoverState diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/StateSaver.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/StateSaver.kt index 3e1deb2..6ae52c1 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/StateSaver.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/StateSaver.kt @@ -18,5 +18,5 @@ internal object StateSaver { var listState: ListState = ListState.Loading(emptyList()) - var discoverState: DiscoverState = DiscoverState.Recommended.None + var discoverState: DiscoverState = DiscoverState.Recommended.Loading.WatchList } \ No newline at end of file diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/PageMediaQuery.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/PageMediaQuery.kt index fd07e95..63f55fa 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/PageMediaQuery.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/PageMediaQuery.kt @@ -161,13 +161,12 @@ sealed interface PageMediaQuery { data class Season( val season: MediaSeason, - val type: MediaType, val nsfw: Boolean ) : PageMediaQuery { override fun toGraphQL() = PageMediaGraphQL( season = Optional.presentMediaSeason(season), adultContent = Optional.presentIfNot(nsfw), - type = Optional.presentMediaType(type), + type = Optional.presentMediaType(MediaType.ANIME), preventGenres = Optional.presentIfNot(nsfw, AdultContent.Genre.allTags), statusVersion = 2, html = true diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/state/DiscoverState.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/state/DiscoverState.kt index 2b196a1..bc4370d 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/state/DiscoverState.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/state/DiscoverState.kt @@ -2,6 +2,7 @@ package dev.datlag.aniflow.anilist.state import com.apollographql.apollo3.api.ApolloResponse import dev.datlag.aniflow.anilist.ListQuery +import dev.datlag.aniflow.anilist.common.hasNonCacheError import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.model.PageListQuery import dev.datlag.aniflow.anilist.model.PageMediaQuery @@ -9,24 +10,28 @@ import dev.datlag.aniflow.anilist.type.MediaSeason import dev.datlag.aniflow.anilist.PageMediaQuery as PageMediaGraphQL sealed interface DiscoverState { + + val isFailure: Boolean + get() = this is Failure + sealed interface Recommended : DiscoverState { - data object None : Recommended sealed interface Loading : Recommended { - data class WatchList( - internal val query: PageListQuery, - internal val fallback: Boolean - ) : Loading { + data object WatchList : Loading + data class Matching( + internal val query: PageMediaQuery + ) : Loading - fun fromGraphQL(nsfw: Boolean, response: ApolloResponse): DiscoverState { + companion object { + fun fromList(nsfw: Boolean, response: ApolloResponse): DiscoverState { val data = response.data return if (data == null) { - if (fallback) { - Error(throwable = response.exception) + if (response.hasNonCacheError()) { + Failure(response.exception) } else { - copy(fallback = true) + WatchList } } else { val mediumList = data.Page?.mediaListFilterNotNull()?.mapNotNull { @@ -37,46 +42,32 @@ sealed interface DiscoverState { }?.distinctBy { it.id } if (mediumList.isNullOrEmpty()) { - if (fallback) { - Error(throwable = response.exception) - } else { - copy(fallback = true) - } + Failure(response.exception) } else { Matching( query = PageMediaQuery.Recommendation( nsfw = nsfw, collection = mediumList - ), - fallback = false + ) ) } } } - } - data class Matching( - internal val query: PageMediaQuery, - internal val fallback: Boolean - ) : Loading { - fun fromGraphQL(response: ApolloResponse): DiscoverState { + fun fromMatching(defaultState: DiscoverState, response: ApolloResponse): DiscoverState { val data = response.data return if (data == null) { - if (fallback) { - Error(throwable = response.exception) + if (response.hasNonCacheError()) { + Failure(response.exception) } else { - copy(fallback = true) + defaultState } } else { val mediumList = data.Page?.mediaFilterNotNull() if (mediumList.isNullOrEmpty()) { - if (fallback) { - Error(throwable = response.exception) - } else { - copy(fallback = true) - } + Failure(response.exception) } else { Success( collection = mediumList.map(::Medium).distinctBy { it.id } @@ -90,30 +81,23 @@ sealed interface DiscoverState { sealed interface Season : DiscoverState { - data class None(internal val wanted: MediaSeason) : Season + data object Loading : Season - data class Loading( - internal val query: PageMediaQuery, - internal val fallback: Boolean - ) : Season { - fun fromGraphQL(response: ApolloResponse): DiscoverState { + companion object { + fun fromSeasonResponse(response: ApolloResponse): DiscoverState { val data = response.data return if (data == null) { - if (fallback) { - Error(throwable = response.exception) + if (response.hasNonCacheError()) { + Failure(throwable = response.exception) } else { - copy(fallback = true) + Loading } } else { val mediumList = data.Page?.mediaFilterNotNull() if (mediumList.isNullOrEmpty()) { - if (fallback) { - Error(throwable = response.exception) - } else { - copy(fallback = true) - } + Failure(throwable = response.exception) } else { Success( collection = mediumList.map(::Medium).distinctBy { it.id } @@ -130,44 +114,52 @@ sealed interface DiscoverState { val collection: Collection ) : PostLoading - data class Error( + data class Failure( internal val throwable: Throwable? ) : PostLoading } -sealed interface DiscoverAction { - - sealed interface Type : DiscoverAction { +sealed interface DiscoverListType { - data object Anime : Type + data object Recommendation : DiscoverListType - data object Manga : Type + sealed interface Season : DiscoverListType { + val mediaSeason : MediaSeason - data object Toggle : Type + companion object { + fun fromSeason(season: MediaSeason): Season = when (season) { + MediaSeason.SPRING -> Spring + MediaSeason.SUMMER -> Summer + MediaSeason.FALL -> Fall + MediaSeason.WINTER -> Winter + else -> Spring + } + } } - sealed interface ListType : DiscoverAction { - - data object Recommendation : ListType - - sealed interface Season : ListType { - val mediaSeason : MediaSeason - } + data object Spring : Season { + override val mediaSeason: MediaSeason = MediaSeason.SPRING + } - data object Spring : Season { - override val mediaSeason: MediaSeason = MediaSeason.SPRING - } + data object Summer : Season { + override val mediaSeason: MediaSeason = MediaSeason.SUMMER + } - data object Summer : Season { - override val mediaSeason: MediaSeason = MediaSeason.SUMMER - } + data object Fall : Season { + override val mediaSeason: MediaSeason = MediaSeason.FALL + } - data object Fall : Season { - override val mediaSeason: MediaSeason = MediaSeason.FALL - } + data object Winter : Season { + override val mediaSeason: MediaSeason = MediaSeason.WINTER + } - data object Winter : Season { - override val mediaSeason: MediaSeason = MediaSeason.WINTER - } + companion object { + val entries = setOf( + DiscoverListType.Recommendation, + DiscoverListType.Spring, + DiscoverListType.Summer, + DiscoverListType.Fall, + DiscoverListType.Winter + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendDiscovery.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendDiscovery.kt new file mode 100644 index 0000000..3255fd8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendDiscovery.kt @@ -0,0 +1,32 @@ +package dev.datlag.aniflow.common + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.AcUnit +import androidx.compose.material.icons.rounded.LocalFlorist +import androidx.compose.material.icons.rounded.Recommend +import androidx.compose.material.icons.rounded.Thunderstorm +import androidx.compose.material.icons.rounded.WbSunny +import androidx.compose.ui.graphics.vector.ImageVector +import dev.datlag.aniflow.SharedRes +import dev.datlag.aniflow.anilist.state.DiscoverListType +import dev.icerock.moko.resources.StringResource + +fun DiscoverListType.icon(): ImageVector { + return when (this) { + is DiscoverListType.Recommendation -> Icons.Rounded.Recommend + is DiscoverListType.Spring -> Icons.Rounded.LocalFlorist + is DiscoverListType.Summer -> Icons.Rounded.WbSunny + is DiscoverListType.Fall -> Icons.Rounded.Thunderstorm + is DiscoverListType.Winter -> Icons.Rounded.AcUnit + } +} + +fun DiscoverListType.title(): StringResource { + return when (this) { + is DiscoverListType.Recommendation -> SharedRes.strings.recommendation + is DiscoverListType.Spring -> SharedRes.strings.spring + is DiscoverListType.Summer -> SharedRes.strings.summer + is DiscoverListType.Fall -> SharedRes.strings.fall + is DiscoverListType.Winter -> SharedRes.strings.winter + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt index 3236b02..dc2989f 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt @@ -220,7 +220,7 @@ data object NetworkModule { crashlytics = nullableFirebaseInstance()?.crashlytics ) } - bindProvider { + bindSingleton { val appSettings = instance() DiscoverStateMachine( diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/default/MediumCard.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/MediumCard.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/default/MediumCard.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/MediumCard.kt index 2e61f0f..33cf9d6 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/default/MediumCard.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/MediumCard.kt @@ -1,4 +1,4 @@ -package dev.datlag.aniflow.ui.navigation.screen.home.component.default +package dev.datlag.aniflow.ui.navigation.screen.component import androidx.compose.foundation.layout.* import androidx.compose.material3.Card @@ -15,6 +15,8 @@ import coil3.compose.AsyncImage import coil3.compose.rememberAsyncImagePainter import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.common.* +import dev.datlag.aniflow.ui.navigation.screen.home.component.default.GenreChip +import dev.datlag.aniflow.ui.navigation.screen.home.component.default.Rating import dev.datlag.aniflow.ui.theme.SchemeTheme import dev.datlag.aniflow.ui.theme.rememberSchemeThemeDominantColorState import dev.datlag.aniflow.settings.model.TitleLanguage as SettingsTitle diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverComponent.kt index c1fd7ce..6a7d2f1 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverComponent.kt @@ -1,6 +1,7 @@ package dev.datlag.aniflow.ui.navigation.screen.discover import dev.datlag.aniflow.anilist.model.Medium +import dev.datlag.aniflow.anilist.state.DiscoverListType import dev.datlag.aniflow.anilist.state.DiscoverState import dev.datlag.aniflow.anilist.state.SearchState import dev.datlag.aniflow.anilist.type.MediaType @@ -15,6 +16,7 @@ interface DiscoverComponent : Component { val type: Flow val searchResult: StateFlow + val discoverType: StateFlow val state: StateFlow fun viewHome() @@ -23,4 +25,5 @@ interface DiscoverComponent : Component { fun search(query: String) fun toggleView() + fun listType(type: DiscoverListType) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreen.kt index aa822b0..a8ab15a 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreen.kt @@ -1,18 +1,28 @@ package dev.datlag.aniflow.ui.navigation.screen.discover +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.FilterList import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -24,37 +34,95 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection import dev.chrisbanes.haze.haze import dev.datlag.aniflow.LocalHaze +import dev.datlag.aniflow.SharedRes +import dev.datlag.aniflow.anilist.state.DiscoverListType import dev.datlag.aniflow.anilist.state.DiscoverState import dev.datlag.aniflow.common.animatePaddingAsState import dev.datlag.aniflow.common.header +import dev.datlag.aniflow.common.icon import dev.datlag.aniflow.common.merge import dev.datlag.aniflow.common.preferred import dev.datlag.aniflow.common.scrollUpVisible +import dev.datlag.aniflow.common.title import dev.datlag.aniflow.other.rememberSearchBarState import dev.datlag.aniflow.ui.custom.ErrorContent import dev.datlag.aniflow.ui.navigation.screen.component.HidingNavigationBar +import dev.datlag.aniflow.ui.navigation.screen.component.MediumCard import dev.datlag.aniflow.ui.navigation.screen.component.NavigationBarState -import dev.datlag.aniflow.ui.navigation.screen.discover.component.HidingSearchBar -import dev.datlag.aniflow.ui.navigation.screen.discover.component.RecommendationCard -import dev.datlag.aniflow.ui.navigation.screen.discover.component.TypeCard +import dev.datlag.aniflow.ui.navigation.screen.discover.component.DiscoverSearchBar import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource @OptIn(ExperimentalMaterial3Api::class) @Composable fun DiscoverScreen(component: DiscoverComponent) { val listState = rememberLazyGridState() val searchBarState = rememberSearchBarState() + val discoverType by component.discoverType.collectAsStateWithLifecycle() Scaffold( topBar = { - HidingSearchBar( - visible = true, + DiscoverSearchBar( searchBarState = searchBarState, component = component ) }, + floatingActionButton = { + val listTypeDialog = rememberUseCaseState() + val loggedIn by component.loggedIn.collectAsStateWithLifecycle(false) + val list = remember { DiscoverListType.entries.toList() } + + OptionDialog( + state = listTypeDialog, + selection = OptionSelection.Single( + options = list.map { + Option( + icon = IconSource(it.icon()), + titleText = stringResource(it.title()), + selected = it == discoverType, + disabled = (it is DiscoverListType.Recommendation && !loggedIn) + ) + }, + onSelectOption = { option, _ -> + component.listType(list[option]) + } + ), + config = OptionConfig( + mode = DisplayMode.LIST + ), + header = Header.Default( + icon = IconSource(Icons.Rounded.FilterList), + title = stringResource(SharedRes.strings.discover) + ) + ) + + ExtendedFloatingActionButton( + onClick = { + listTypeDialog.show() + }, + expanded = listState.scrollUpVisible(), + icon = { + Icon( + imageVector = discoverType.icon(), + contentDescription = stringResource(discoverType.title()) + ) + }, + text = { + Text(text = stringResource(discoverType.title())) + } + ) + }, bottomBar = { HidingNavigationBar( visible = listState.scrollUpVisible() && !searchBarState.isActive, @@ -72,7 +140,7 @@ fun DiscoverScreen(component: DiscoverComponent) { val discoverState by component.state.collectAsStateWithLifecycle() when (val current = discoverState) { - is DiscoverState.Recommended.None, is DiscoverState.Recommended.Loading -> { + is DiscoverState.Season.Loading, is DiscoverState.Recommended.Loading -> { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -82,7 +150,7 @@ fun DiscoverScreen(component: DiscoverComponent) { ) } } - is DiscoverState.Error -> { + is DiscoverState.Failure -> { ErrorContent( modifier = Modifier.fillMaxSize().padding(smoothPadding) ) @@ -94,10 +162,15 @@ fun DiscoverScreen(component: DiscoverComponent) { contentPadding = smoothPadding, verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), - columns = GridCells.Fixed(2) + columns = GridCells.Adaptive(120.dp) ) { items(current.collection.toList(), key = { it.id }) { - Text(text = it.preferred(null)) + MediumCard( + medium = it, + titleLanguage = null, + modifier = Modifier.fillMaxWidth().aspectRatio(0.65F), + onClick = component::details + ) } } } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreenComponent.kt index 64948f4..607ddd9 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverScreenComponent.kt @@ -9,6 +9,7 @@ import dev.datlag.aniflow.LocalHaze import dev.datlag.aniflow.anilist.DiscoverStateMachine import dev.datlag.aniflow.anilist.SearchStateMachine import dev.datlag.aniflow.anilist.model.Medium +import dev.datlag.aniflow.anilist.state.DiscoverListType import dev.datlag.aniflow.anilist.state.DiscoverState import dev.datlag.aniflow.anilist.state.SearchState import dev.datlag.aniflow.anilist.type.MediaType @@ -59,6 +60,14 @@ class DiscoverScreenComponent( initialValue = discoverStateMachine.currentState ) + override val discoverType: StateFlow = discoverStateMachine.listType.flowOn( + context = ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = discoverStateMachine.currentListType + ) + @Composable override fun render() { val haze = remember { HazeState() } @@ -90,5 +99,10 @@ class DiscoverScreenComponent( override fun toggleView() { searchRepository.toggleType() + discoverStateMachine.toggleType() + } + + override fun listType(type: DiscoverListType) { + discoverStateMachine.listType(type) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverType.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverType.kt deleted file mode 100644 index 0b39dda..0000000 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/DiscoverType.kt +++ /dev/null @@ -1,47 +0,0 @@ -package dev.datlag.aniflow.ui.navigation.screen.discover - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.AcUnit -import androidx.compose.material.icons.rounded.LocalFlorist -import androidx.compose.material.icons.rounded.Movie -import androidx.compose.material.icons.rounded.Star -import androidx.compose.material.icons.rounded.Thunderstorm -import androidx.compose.material.icons.rounded.WbSunny -import androidx.compose.ui.graphics.vector.ImageVector -import dev.datlag.aniflow.SharedRes -import dev.icerock.moko.resources.StringResource - -sealed interface DiscoverType { - val icon: ImageVector - val text: StringResource - - data object Top100 : DiscoverType { - override val icon: ImageVector = Icons.Rounded.Star - override val text: StringResource = SharedRes.strings.top_100 - } - - data object TopMovies : DiscoverType { - override val icon: ImageVector = Icons.Rounded.Movie - override val text: StringResource = SharedRes.strings.top_movies - } - - data object Spring : DiscoverType { - override val icon: ImageVector = Icons.Rounded.LocalFlorist - override val text: StringResource = SharedRes.strings.spring - } - - data object Summer : DiscoverType { - override val icon: ImageVector = Icons.Rounded.WbSunny - override val text: StringResource = SharedRes.strings.summer - } - - data object Fall : DiscoverType { - override val icon: ImageVector = Icons.Rounded.Thunderstorm - override val text: StringResource = SharedRes.strings.fall - } - - data object Winter : DiscoverType { - override val icon: ImageVector = Icons.Rounded.AcUnit - override val text: StringResource = SharedRes.strings.winter - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/DiscoverSearchBar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/DiscoverSearchBar.kt new file mode 100644 index 0000000..9f50a9d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/DiscoverSearchBar.kt @@ -0,0 +1,189 @@ +package dev.datlag.aniflow.ui.navigation.screen.discover.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.MenuBook +import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material.icons.rounded.PlayCircle +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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.platform.LocalDensity +import androidx.compose.ui.unit.dp +import dev.chrisbanes.haze.hazeChild +import dev.datlag.aniflow.LocalHaze +import dev.datlag.aniflow.SharedRes +import dev.datlag.aniflow.anilist.state.SearchState +import dev.datlag.aniflow.anilist.type.MediaType +import dev.datlag.aniflow.other.SearchBarState +import dev.datlag.aniflow.other.rememberSearchBarState +import dev.datlag.aniflow.ui.custom.ErrorContent +import dev.datlag.aniflow.ui.navigation.screen.discover.DiscoverComponent +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.icerock.moko.resources.compose.stringResource + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@Composable +fun DiscoverSearchBar( + searchBarState: SearchBarState = rememberSearchBarState(), + component: DiscoverComponent +) { + val type by component.type.collectAsStateWithLifecycle(MediaType.UNKNOWN__) + var query by remember { mutableStateOf(component.initialSearchValue ?: "") } + + val activePadding by animateDpAsState( + targetValue = if (searchBarState.isActive) 0.dp else 16.dp + ) + + SearchBar( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = activePadding) + .padding(bottom = activePadding), + query = query, + onQueryChange = { + query = it + component.search(query) + }, + active = searchBarState.isActive, + onActiveChange = { + searchBarState.isActive = it + }, + onSearch = { + query = it + component.search(query) + }, + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + }, + placeholder = { + Text( + text = stringResource( + if (type == MediaType.MANGA) { + SharedRes.strings.search_manga + } else { + SharedRes.strings.search_anime + } + ) + ) + }, + trailingIcon = { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + AnimatedVisibility( + visible = query.isNotBlank(), + enter = fadeIn(), + exit = fadeOut() + ) { + IconButton( + onClick = { + query = "" + searchBarState.isActive = false + }, + enabled = query.isNotBlank() + ) { + Icon( + imageVector = Icons.Rounded.Clear, + contentDescription = null + ) + } + } + IconButton( + onClick = { + component.toggleView() + }, + enabled = type != MediaType.UNKNOWN__ + ) { + if (type == MediaType.ANIME) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.MenuBook, + contentDescription = null + ) + } else { + Icon( + imageVector = Icons.Rounded.PlayCircle, + contentDescription = null + ) + } + } + } + }, + content = { + val resultState by component.searchResult.collectAsStateWithLifecycle() + + when (val current = resultState) { + is SearchState.None -> {} + is SearchState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(fraction = 0.2F).clip(CircleShape) + ) + } + } + + is SearchState.Failure -> { + ErrorContent( + modifier = Modifier.fillMaxSize() + ) + } + + is SearchState.Success -> { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(current.collection.toList(), key = { it.id }) { + SearchResult( + medium = it, + modifier = Modifier.fillParentMaxWidth(), + onClick = component::details + ) + } + } + } + } + } + ) + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/HidingSearchBar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/HidingSearchBar.kt deleted file mode 100644 index aa2acc5..0000000 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/HidingSearchBar.kt +++ /dev/null @@ -1,206 +0,0 @@ -package dev.datlag.aniflow.ui.navigation.screen.discover.component - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.rounded.MenuBook -import androidx.compose.material.icons.rounded.Clear -import androidx.compose.material.icons.rounded.PlayCircle -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.SearchBar -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp -import dev.datlag.aniflow.SharedRes -import dev.datlag.aniflow.anilist.state.SearchState -import dev.datlag.aniflow.anilist.type.MediaType -import dev.datlag.aniflow.other.SearchBarState -import dev.datlag.aniflow.other.rememberSearchBarState -import dev.datlag.aniflow.ui.custom.ErrorContent -import dev.datlag.aniflow.ui.navigation.screen.discover.DiscoverComponent -import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle -import dev.icerock.moko.resources.compose.stringResource - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) -@Composable -fun HidingSearchBar( - visible: Boolean, - searchBarState: SearchBarState = rememberSearchBarState(), - component: DiscoverComponent -) { - val density = LocalDensity.current - - AnimatedVisibility( - visible = visible, - enter = slideInVertically( - initialOffsetY = { - with(density) { -it.dp.roundToPx() } - }, - animationSpec = tween( - easing = LinearOutSlowInEasing, - durationMillis = 500 - ) - ), - exit = slideOutVertically( - targetOffsetY = { -it }, - animationSpec = tween( - easing = LinearOutSlowInEasing, - durationMillis = 500 - ) - ) - ) { - val type by component.type.collectAsStateWithLifecycle(MediaType.UNKNOWN__) - var query by remember { mutableStateOf(component.initialSearchValue ?: "") } - - val activePadding by animateDpAsState( - targetValue = if (searchBarState.isActive) 0.dp else 16.dp - ) - - SearchBar( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = activePadding) - .padding(bottom = activePadding), - query = query, - onQueryChange = { - query = it - component.search(query) - }, - active = searchBarState.isActive, - onActiveChange = { - searchBarState.isActive = it - }, - onSearch = { - query = it - component.search(query) - }, - leadingIcon = { - Icon( - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - }, - placeholder = { - Text( - text = stringResource( - if (type == MediaType.MANGA) { - SharedRes.strings.search_manga - } else { - SharedRes.strings.search_anime - } - ) - ) - }, - trailingIcon = { - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - AnimatedVisibility( - visible = query.isNotBlank(), - enter = fadeIn(), - exit = fadeOut() - ) { - IconButton( - onClick = { - query = "" - searchBarState.isActive = false - }, - enabled = query.isNotBlank() - ) { - Icon( - imageVector = Icons.Rounded.Clear, - contentDescription = null - ) - } - } - IconButton( - onClick = { - component.toggleView() - }, - enabled = type != MediaType.UNKNOWN__ - ) { - if (type == MediaType.ANIME) { - Icon( - imageVector = Icons.AutoMirrored.Rounded.MenuBook, - contentDescription = null - ) - } else { - Icon( - imageVector = Icons.Rounded.PlayCircle, - contentDescription = null - ) - } - } - } - }, - content = { - val resultState by component.searchResult.collectAsStateWithLifecycle() - - when (val current = resultState) { - is SearchState.None -> { } - is SearchState.Loading -> { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth(fraction = 0.2F).clip(CircleShape) - ) - } - } - is SearchState.Failure -> { - ErrorContent( - modifier = Modifier.fillMaxSize() - ) - } - is SearchState.Success -> { - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - items(current.collection.toList(), key = { it.id }) { - SearchResult( - medium = it, - modifier = Modifier.fillParentMaxWidth(), - onClick = component::details - ) - } - } - } - } - } - ) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/TypeCard.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/TypeCard.kt deleted file mode 100644 index 0f302b9..0000000 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/discover/component/TypeCard.kt +++ /dev/null @@ -1,39 +0,0 @@ -package dev.datlag.aniflow.ui.navigation.screen.discover.component - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import dev.datlag.aniflow.ui.navigation.screen.discover.DiscoverType -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun TypeCard( - type: DiscoverType, - modifier: Modifier = Modifier, - onClick: (DiscoverType) -> Unit -) { - ElevatedCard( - onClick = { onClick(type) }, - modifier = modifier - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Icon( - imageVector = type.icon, - contentDescription = null - ) - Text(text = stringResource(type.text)) - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/DefaultOverview.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/DefaultOverview.kt index 4b280a3..c3c47f0 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/DefaultOverview.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/home/component/DefaultOverview.kt @@ -1,12 +1,9 @@ package dev.datlag.aniflow.ui.navigation.screen.home.component -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -19,9 +16,8 @@ import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.state.HomeDefaultState import dev.datlag.aniflow.settings.model.TitleLanguage import dev.datlag.aniflow.ui.custom.ErrorContent -import dev.datlag.aniflow.ui.navigation.screen.home.component.default.MediumCard +import dev.datlag.aniflow.ui.navigation.screen.component.MediumCard import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Composable diff --git a/composeApp/src/commonMain/moko-resources/base/strings.xml b/composeApp/src/commonMain/moko-resources/base/strings.xml index 7717d09..605f45a 100644 --- a/composeApp/src/commonMain/moko-resources/base/strings.xml +++ b/composeApp/src/commonMain/moko-resources/base/strings.xml @@ -118,4 +118,5 @@ Summer Fall Winter + Recommendation