diff --git a/anilist/src/commonMain/graphql/AiringQuery.graphql b/anilist/src/commonMain/graphql/AiringQuery.graphql index 0f9b0c6..4897c1f 100644 --- a/anilist/src/commonMain/graphql/AiringQuery.graphql +++ b/anilist/src/commonMain/graphql/AiringQuery.graphql @@ -79,7 +79,8 @@ query AiringQuery( } }, mediaListEntry { - score(format: POINT_5) + score(format: POINT_5), + status }, trailer { id, diff --git a/anilist/src/commonMain/graphql/MediaListEntryQuery.graphql b/anilist/src/commonMain/graphql/MediaListEntryQuery.graphql index d3dd588..893e7d6 100644 --- a/anilist/src/commonMain/graphql/MediaListEntryQuery.graphql +++ b/anilist/src/commonMain/graphql/MediaListEntryQuery.graphql @@ -1,5 +1,6 @@ query MediaListEntryQuery($id: Int) { MediaList(mediaId: $id) { - score(format: POINT_5) + score(format: POINT_5), + status } } \ No newline at end of file diff --git a/anilist/src/commonMain/graphql/MediumQuery.graphql b/anilist/src/commonMain/graphql/MediumQuery.graphql index 1c15f19..a4f8062 100644 --- a/anilist/src/commonMain/graphql/MediumQuery.graphql +++ b/anilist/src/commonMain/graphql/MediumQuery.graphql @@ -68,7 +68,8 @@ query MediumQuery($id: Int, $statusVersion: Int, $html: Boolean) { } }, mediaListEntry { - score(format: POINT_5) + score(format: POINT_5), + status }, trailer { id, diff --git a/anilist/src/commonMain/graphql/SeasonQuery.graphql b/anilist/src/commonMain/graphql/SeasonQuery.graphql index 7209818..aac6dbb 100644 --- a/anilist/src/commonMain/graphql/SeasonQuery.graphql +++ b/anilist/src/commonMain/graphql/SeasonQuery.graphql @@ -80,7 +80,8 @@ query SeasonQuery( } }, mediaListEntry { - score(format: POINT_5) + score(format: POINT_5), + status }, trailer { id, diff --git a/anilist/src/commonMain/graphql/TrendingQuery.graphql b/anilist/src/commonMain/graphql/TrendingQuery.graphql index df37538..401b475 100644 --- a/anilist/src/commonMain/graphql/TrendingQuery.graphql +++ b/anilist/src/commonMain/graphql/TrendingQuery.graphql @@ -83,7 +83,8 @@ query TrendingQuery( } }, mediaListEntry { - score(format: POINT_5) + score(format: POINT_5), + status }, trailer { id, diff --git a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt index 53db389..d1e38b4 100644 --- a/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt +++ b/anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/model/Medium.kt @@ -3,10 +3,7 @@ package dev.datlag.aniflow.anilist.model import dev.datlag.aniflow.anilist.* import dev.datlag.aniflow.anilist.AdultContent import dev.datlag.aniflow.anilist.common.lastMonth -import dev.datlag.aniflow.anilist.type.MediaFormat -import dev.datlag.aniflow.anilist.type.MediaRankType -import dev.datlag.aniflow.anilist.type.MediaStatus -import dev.datlag.aniflow.anilist.type.MediaType +import dev.datlag.aniflow.anilist.type.* import dev.datlag.aniflow.model.ifValue import dev.datlag.aniflow.model.toInt import kotlinx.datetime.Clock @@ -379,22 +376,27 @@ data class Medium( @Serializable data class Entry( - val score: Double? + val score: Double?, + val status: MediaListStatus ) { constructor(entry: MediumQuery.MediaListEntry) : this( - score = entry.score + score = entry.score, + status = entry.status ?: MediaListStatus.UNKNOWN__ ) constructor(entry: TrendingQuery.MediaListEntry) : this( - score = entry.score + score = entry.score, + status = entry.status ?: MediaListStatus.UNKNOWN__ ) constructor(entry: AiringQuery.MediaListEntry) : this( - score = entry.score + score = entry.score, + status = entry.status ?: MediaListStatus.UNKNOWN__ ) constructor(entry: SeasonQuery.MediaListEntry) : this( - score = entry.score + score = entry.score, + status = entry.status ?: MediaListStatus.UNKNOWN__ ) } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendMedium.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendMedium.kt index 1713b87..d6144ea 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendMedium.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendMedium.kt @@ -1,14 +1,14 @@ package dev.datlag.aniflow.common +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import dev.datlag.aniflow.LocalDI import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.anilist.model.Character import dev.datlag.aniflow.anilist.model.Medium -import dev.datlag.aniflow.anilist.type.MediaFormat -import dev.datlag.aniflow.anilist.type.MediaRankType -import dev.datlag.aniflow.anilist.type.MediaStatus +import dev.datlag.aniflow.anilist.type.* import dev.datlag.aniflow.settings.Settings import dev.datlag.aniflow.settings.model.AppSettings import dev.datlag.aniflow.trace.model.SearchResponse @@ -149,4 +149,26 @@ fun SearchResponse.Result.AniList.asMedium(): Medium { _isAdult = this.isAdult, title = this.title.asMediumTitle() ) -} \ No newline at end of file +} + +fun MediaListStatus.icon() = when (this) { + MediaListStatus.CURRENT -> Icons.Rounded.Edit + MediaListStatus.COMPLETED -> Icons.Rounded.Check + MediaListStatus.PAUSED -> Icons.Rounded.Pause + MediaListStatus.DROPPED -> Icons.Rounded.Close + MediaListStatus.PLANNING -> Icons.Rounded.WatchLater + MediaListStatus.REPEATING -> Icons.Rounded.Replay + else -> Icons.Rounded.Add +} + +fun MediaListStatus.stringRes(isManga: Boolean) = when (this) { + MediaListStatus.CURRENT -> if (isManga) SharedRes.strings.reading else SharedRes.strings.watching + MediaListStatus.COMPLETED -> SharedRes.strings.completed + MediaListStatus.PAUSED -> SharedRes.strings.paused + MediaListStatus.DROPPED -> SharedRes.strings.dropped + MediaListStatus.PLANNING -> SharedRes.strings.planning + MediaListStatus.REPEATING -> SharedRes.strings.repeating + else -> SharedRes.strings.add +} + +fun MediaListStatus.stringRes(type: MediaType) = this.stringRes(type == MediaType.MANGA) \ 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 666abd2..9746c24 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt @@ -165,14 +165,14 @@ data object NetworkModule { } bindSingleton { CharacterRepository( - client = instance(Constants.AniList.APOLLO_CLIENT), - fallbackClient = instance(Constants.AniList.FALLBACK_APOLLO_CLIENT), + client = instance(Constants.AniList.APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build(), + fallbackClient = instance(Constants.AniList.FALLBACK_APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build(), ) } bindSingleton { MediumRepository( - client = instance(Constants.AniList.APOLLO_CLIENT), - fallbackClient = instance(Constants.AniList.FALLBACK_APOLLO_CLIENT) + client = instance(Constants.AniList.APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build(), + fallbackClient = instance(Constants.AniList.FALLBACK_APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build() ) } bindSingleton { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/DialogConfig.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/DialogConfig.kt index 70f41f9..1cb21b7 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/DialogConfig.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/DialogConfig.kt @@ -10,4 +10,7 @@ sealed class DialogConfig { data class Character( val initial: Char ) : DialogConfig() + + @Serializable + data object Edit : DialogConfig() } \ 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 daea17a..d91afa3 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 @@ -6,7 +6,9 @@ import dev.datlag.aniflow.anilist.MediumRepository import dev.datlag.aniflow.anilist.model.Character import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.type.MediaFormat +import dev.datlag.aniflow.anilist.type.MediaListStatus import dev.datlag.aniflow.anilist.type.MediaStatus +import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.other.Series import dev.datlag.aniflow.settings.model.AppSettings import dev.datlag.aniflow.ui.navigation.Component @@ -50,8 +52,8 @@ interface MediumComponent : ContentHolderComponent { val isFavoriteBlocked: Flow val siteUrl: Flow - val bsAvailable: Boolean - val bsOptions: Flow> + val type: Flow + val listStatus: Flow val dialog: Value> @@ -64,4 +66,5 @@ interface MediumComponent : ContentHolderComponent { fun descriptionTranslation(text: String?) fun showCharacter(character: Character) fun toggleFavorite() + fun edit() } \ No newline at end of file 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 9ac14af..8a9c9ba 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 @@ -38,6 +38,7 @@ import dev.datlag.aniflow.LocalDI import dev.datlag.aniflow.LocalHaze import dev.datlag.aniflow.LocalPaddingValues import dev.datlag.aniflow.SharedRes +import dev.datlag.aniflow.anilist.type.MediaListStatus import dev.datlag.aniflow.anilist.type.MediaStatus import dev.datlag.aniflow.common.* import dev.datlag.aniflow.other.StateSaver @@ -46,6 +47,7 @@ import dev.datlag.aniflow.ui.custom.EditFAB import dev.datlag.aniflow.ui.navigation.screen.medium.component.* import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource import io.github.aakira.napier.Napier import kotlinx.coroutines.launch import org.kodein.di.instance @@ -92,79 +94,24 @@ fun MediumScreen(component: MediumComponent) { ) }, floatingActionButton = { - val userRating by component.rating.collectAsStateWithLifecycle(-1) - val ratingState = rememberUseCaseState() - val bsState = rememberUseCaseState() - - val bsOptions by component.bsOptions.collectAsStateWithLifecycle(emptySet()) - - 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 } - RatingDialog( - state = ratingState, - selection = RatingSelection( - onSelectRating = { rating, _ -> - component.rate(rating) - } - ), - header = Header.Default( - title = "Rate this Anime", - icon = IconSource(Icons.Filled.Star) - ), - body = RatingBody.Default( - bodyText = "" - ), - config = RatingConfig( - ratingOptionsCount = 5, - ratingOptionsSelected = userRating.takeIf { it > 0 }, - ratingZeroValid = true - ) - ) - - OptionDialog( - state = bsState, - selection = OptionSelection.Single( - options = bsOptions.map { - Option( - titleText = it.title - ) - }, - onSelectOption = { option, _ -> - Napier.e("Selected: ${bsOptions.toList()[option]}") - } - ), - header = Header.Default( - icon = IconSource( - painter = painterResource(SharedRes.images.bs) - ), - title = "Connect with BS" - ), - config = OptionConfig( - mode = DisplayMode.LIST - ) - ) - if (!notReleased) { - val uriHandler = LocalUriHandler.current - val userHelper by LocalDI.current.instance() + val status by component.listStatus.collectAsStateWithLifecycle(component.initialMedium.entry?.status ?: MediaListStatus.UNKNOWN__) + val type by component.type.collectAsStateWithLifecycle(component.initialMedium.type) - EditFAB( - displayAdd = !alreadyAdded, - bsAvailable = component.bsAvailable, - expanded = listState.isScrollingUp(), - onBS = { - bsState.show() - }, - onRate = { - uriHandler.openUri(userHelper.loginUrl) + ExtendedFloatingActionButton( + onClick = { component.edit() }, + icon = { + Icon( + imageVector = status.icon(), + contentDescription = null, + ) }, - onProgress = { - // ratingState.show() + text = { + Text(text = stringResource(status.stringRes(type))) } ) } 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 8fb607b..020e20e 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 @@ -14,6 +14,7 @@ import dev.datlag.aniflow.anilist.* import dev.datlag.aniflow.anilist.model.Character import dev.datlag.aniflow.anilist.model.Medium import dev.datlag.aniflow.anilist.type.MediaFormat +import dev.datlag.aniflow.anilist.type.MediaListStatus import dev.datlag.aniflow.anilist.type.MediaStatus import dev.datlag.aniflow.anilist.type.MediaType import dev.datlag.aniflow.common.* @@ -26,6 +27,7 @@ import dev.datlag.aniflow.settings.model.AppSettings import dev.datlag.aniflow.settings.model.CharLanguage import dev.datlag.aniflow.ui.navigation.DialogComponent import dev.datlag.aniflow.ui.navigation.screen.medium.dialog.character.CharacterDialogComponent +import dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit.EditDialogComponent import dev.datlag.tooling.alsoTrue import dev.datlag.tooling.async.suspendCatching import dev.datlag.tooling.compose.ioDispatcher @@ -70,7 +72,7 @@ class MediumScreenComponent( override val isAdultAllowed: Flow = appSettings.adultContent @OptIn(ExperimentalCoroutinesApi::class) - private val type: Flow = mediumSuccessState.mapLatest { + override val type: Flow = mediumSuccessState.mapLatest { it.medium.type } @@ -178,14 +180,9 @@ class MediumScreenComponent( it.medium.siteUrl } - private val burningSeriesResolver by di.instance() - - override val bsAvailable: Boolean - get() = burningSeriesResolver.isAvailable - @OptIn(ExperimentalCoroutinesApi::class) - override val bsOptions = title.mapLatest { - burningSeriesResolver.resolveByName(it.english, it.romaji) + override val listStatus: Flow = mediumSuccessState.mapLatest { + it.medium.entry?.status ?: MediaListStatus.UNKNOWN__ } private val dialogNavigation = SlotNavigation() @@ -200,6 +197,12 @@ class MediumScreenComponent( initialChar = config.initial, onDismiss = dialogNavigation::dismiss ) + is DialogConfig.Edit -> EditDialogComponent( + componentContext = context, + di = di, + titleFlow = title, + onDismiss = dialogNavigation::dismiss + ) } } @@ -294,4 +297,8 @@ class MediumScreenComponent( aniListClient.mutation(mutation).execute() } } + + override fun edit() { + dialogNavigation.activate(DialogConfig.Edit) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt new file mode 100644 index 0000000..0381a4c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditComponent.kt @@ -0,0 +1,11 @@ +package dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit + +import dev.datlag.aniflow.other.Series +import dev.datlag.aniflow.ui.navigation.DialogComponent +import kotlinx.coroutines.flow.Flow + +interface EditComponent : DialogComponent { + + val bsAvailable: Boolean + val bsOptions: Flow> +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt new file mode 100644 index 0000000..dbaf710 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialog.kt @@ -0,0 +1,30 @@ +package dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import dev.datlag.aniflow.LocalEdgeToEdge + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditDialog(component: EditComponent) { + val sheetState = rememberModalBottomSheetState() + val (insets, bottomPadding) = if (LocalEdgeToEdge.current) { + WindowInsets( + left = 0, + top = 0, + right = 0, + bottom = 0 + ) to BottomSheetDefaults.windowInsets.only(WindowInsetsSides.Bottom).asPaddingValues() + } else { + BottomSheetDefaults.windowInsets to PaddingValues() + } + + ModalBottomSheet( + onDismissRequest = component::dismiss, + windowInsets = insets, + sheetState = sheetState + ) { + Text(text = "Edit Dialog") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt new file mode 100644 index 0000000..4e04c0f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/dialog/edit/EditDialogComponent.kt @@ -0,0 +1,41 @@ +package dev.datlag.aniflow.ui.navigation.screen.medium.dialog.edit + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.aniflow.anilist.model.Medium +import dev.datlag.aniflow.common.onRender +import dev.datlag.aniflow.other.BurningSeriesResolver +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapLatest +import org.kodein.di.DI +import org.kodein.di.instance + +class EditDialogComponent( + componentContext: ComponentContext, + override val di: DI, + private val titleFlow: Flow, + private val onDismiss: () -> Unit +) : EditComponent, ComponentContext by componentContext { + + private val burningSeriesResolver by instance() + + override val bsAvailable: Boolean + get() = burningSeriesResolver.isAvailable + + @OptIn(ExperimentalCoroutinesApi::class) + override val bsOptions = titleFlow.mapLatest { + burningSeriesResolver.resolveByName(it.english, it.romaji) + } + + @Composable + override fun render() { + onRender { + EditDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/moko-resources/base/strings.xml b/composeApp/src/commonMain/moko-resources/base/strings.xml index 5de154c..acc80f7 100644 --- a/composeApp/src/commonMain/moko-resources/base/strings.xml +++ b/composeApp/src/commonMain/moko-resources/base/strings.xml @@ -63,4 +63,11 @@ Suggestive Borderline Explicit + Watching + Reading + Planning + Completed + Dropped + Paused + Repeating