Skip to content

Commit

Permalink
prepare character request and bs sync
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed Apr 21, 2024
1 parent b98519b commit eb2701a
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 14 deletions.
25 changes: 25 additions & 0 deletions anilist/src/commonMain/graphql/CharacterQuery.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
query CharacterQuery($id: Int, $html: Boolean) {
Character(id: $id) {
id,
name {
first
middle
last
full
native
userPreferred
},
image {
large
medium
},
description(asHtml:$html)
gender,
dateOfBirth {
year
month
day
},
bloodType
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
query MediaListEntry($id: Int) {
query MediaListEntryQuery($id: Int) {
MediaList(mediaId: $id) {
score(format: POINT_5)
}
Expand Down
20 changes: 20 additions & 0 deletions anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/Cache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.datlag.aniflow.anilist

import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.KacheStrategy
import dev.datlag.aniflow.anilist.model.Character
import dev.datlag.aniflow.anilist.model.Medium
import dev.datlag.tooling.async.suspendCatching
import kotlin.time.Duration.Companion.hours
Expand Down Expand Up @@ -35,6 +36,13 @@ internal object Cache {
expireAfterWriteDuration = 2.hours
}

private val character = InMemoryKache<CharacterQuery, Character>(
maxSize = 5L * 1024 * 1024
) {
strategy = KacheStrategy.LRU
expireAfterWriteDuration = 2.hours
}

suspend fun getTrending(key: TrendingQuery): TrendingQuery.Data? {
return suspendCatching {
trendingAnime.get(key)
Expand Down Expand Up @@ -82,4 +90,16 @@ internal object Cache {
medium.put(key, data)
}.getOrNull() ?: data
}

suspend fun getCharacter(key: CharacterQuery) : Character? {
return suspendCatching {
character.get(key)
}.getOrNull()
}

suspend fun setCharacter(key: CharacterQuery, data: Character): Character {
return suspendCatching {
character.put(key, data)
}.getOrNull() ?: data
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package dev.datlag.aniflow.anilist

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import com.freeletics.flowredux.dsl.FlowReduxStateMachine
import dev.datlag.aniflow.anilist.model.Character
import dev.datlag.aniflow.firebase.FirebaseFactory
import dev.datlag.aniflow.model.CatchResult
import dev.datlag.aniflow.model.mapError
import dev.datlag.aniflow.model.saveFirstOrNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class)
class CharacterStateMachine(
private val client: ApolloClient,
private val fallbackClient: ApolloClient,
private val crashlytics: FirebaseFactory.Crashlytics?
) : FlowReduxStateMachine<CharacterStateMachine.State, CharacterStateMachine.Action>(
initialState = currentState
) {

init {
spec {
inState<State.Waiting> {
onEnterEffect {
currentState = it
}
on<Action.Load> { action, state ->
state.override { State.Loading(action.id) }
}
}
inState<State.Loading> {
onEnterEffect {
currentState = it
}
onEnter { state ->
Cache.getCharacter(state.snapshot.query)?.let {
return@onEnter state.override { State.Success(state.snapshot.query, it) }
}

val response = CatchResult.repeat(2, timeoutDuration = 30.seconds) {
val query = client.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
}.mapError {
val query = fallbackClient.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
}.mapSuccess<State> {
it.Character?.let { data ->
State.Success(state.snapshot.query, Character(data))
}
}

state.override {
response.asSuccess {
crashlytics?.log(it)

State.Error
}
}
}
}
inState<State.Success> {
onEnterEffect {
Cache.setCharacter(it.query, it.character)
currentState = it
}
on<Action.Load> { action, state ->
state.override { State.Loading(action.id) }
}
}
inState<State.Error> {
onEnterEffect {
currentState = it
}
on<Action.Load> { action, state ->
state.override { State.Loading(action.id) }
}
}
}
}

sealed interface State {
data object Waiting : State
data class Loading(internal val query: CharacterQuery) : State {
constructor(id: Int) : this(
query = CharacterQuery(
id = Optional.present(id)
)
)
}
data class Success(
internal val query: CharacterQuery,
val character: Character
) : State
data object Error : State
}

sealed interface Action {
data class Load(val id: Int) : Action
}

companion object {
var currentState: State
get() = StateSaver.character
set(value) {
StateSaver.character = value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ internal object StateSaver {
year = nextYear
)
}
var character: CharacterStateMachine.State = CharacterStateMachine.State.Waiting
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.datlag.aniflow.anilist.model

import dev.datlag.aniflow.anilist.CharacterQuery
import kotlinx.serialization.Serializable

@Serializable
open class Character(
open val id: Int,
open val gender: String?,
open val bloodType: String?,
) {
constructor(char: CharacterQuery.Character) : this(
id = char.id,
gender = char.gender?.ifBlank { null },
bloodType = char.bloodType?.ifBlank { null },
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import dev.datlag.aniflow.BuildKonfig
import dev.datlag.aniflow.Sekret
import dev.datlag.aniflow.firebase.FirebaseFactory
import dev.datlag.aniflow.firebase.initialize
import dev.datlag.aniflow.other.BurningSeriesResolver
import dev.datlag.aniflow.other.Constants
import dev.datlag.aniflow.other.StateSaver
import dev.datlag.aniflow.settings.DataStoreUserSettings
Expand Down Expand Up @@ -89,6 +90,9 @@ actual object PlatformModule {
bindSingleton<Settings.PlatformUserSettings> {
DataStoreUserSettings(instance())
}
bindSingleton<BurningSeriesResolver> {
BurningSeriesResolver(context = instance())
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ actual class BurningSeriesResolver(
constructor(context: Context) : this(context.contentResolver)

actual fun resolveWatchedEpisodes(): Set<Episode> {
if (episodeClient == null || seriesClient == null) {
if (episodeClient == null) {
return emptySet()
}

Expand Down Expand Up @@ -60,13 +60,58 @@ actual class BurningSeriesResolver(
)
)
)

episodeCursor.moveToNext()
}
}

episodeCursor.close()
return episodes
}

actual fun resolveByName(english: String?, romaji: String?) {
val englishTrimmed = english?.trim()?.ifBlank { null }
val romajiTrimmed = romaji?.trim()?.ifBlank { null }

if (seriesClient == null || (englishTrimmed == null && romajiTrimmed == null)) {
return
}

val selection = if (englishTrimmed != null && romajiTrimmed != null) {
"title LIKE '%$englishTrimmed%' OR title LIKE '%$romajiTrimmed%'"
} else if (englishTrimmed != null) {
"title LIKE '%$englishTrimmed%'"
} else {
"title LIKE '%$romajiTrimmed%'"
}
val seriesCursor = seriesClient.query(
seriesContentUri,
null,
selection,
null,
null
) ?: return

if (seriesCursor.moveToFirst()) {
while (!seriesCursor.isAfterLast) {
val titleIndex = seriesCursor.getColumnIndex("title")

if (titleIndex == -1) {
seriesCursor.moveToNext()
continue
}

val title = seriesCursor.getString(titleIndex)
Napier.e("Series matching name: $title")

seriesCursor.moveToNext()
}
}

seriesCursor.close()
return
}

actual fun close() {
episodeClient?.close()
seriesClient?.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import de.jensklingenberg.ktorfit.Ktorfit
import de.jensklingenberg.ktorfit.ktorfitBuilder
import dev.datlag.aniflow.BuildKonfig
import dev.datlag.aniflow.Sekret
import dev.datlag.aniflow.anilist.AiringTodayStateMachine
import dev.datlag.aniflow.anilist.PopularNextSeasonStateMachine
import dev.datlag.aniflow.anilist.PopularSeasonStateMachine
import dev.datlag.aniflow.anilist.TrendingAnimeStateMachine
import dev.datlag.aniflow.anilist.*
import dev.datlag.aniflow.other.Constants
import dev.datlag.tooling.compose.ioDispatcher
import io.ktor.client.*
Expand Down Expand Up @@ -123,6 +120,13 @@ data object NetworkModule {
crashlytics = nullableFirebaseInstance()?.crashlytics
)
}
bindProvider<CharacterStateMachine> {
CharacterStateMachine(
client = instance(Constants.AniList.APOLLO_CLIENT),
fallbackClient = instance(Constants.AniList.FALLBACK_APOLLO_CLIENT),
crashlytics = nullableFirebaseInstance()?.crashlytics
)
}
bindSingleton<OpenIdConnectClient>(Constants.AniList.Auth.CLIENT) {
OpenIdConnectClient {
endpoints {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.datlag.aniflow.other

expect class BurningSeriesResolver {
fun resolveWatchedEpisodes(): Set<Episode>
fun resolveByName(english: String?, romaji: String?)

fun close()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import com.arkivanov.essenty.backhandler.BackCallback
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import dev.chrisbanes.haze.HazeState
import dev.datlag.aniflow.LocalHaze
import dev.datlag.aniflow.anilist.MediaListEntryQuery
import dev.datlag.aniflow.anilist.MediumQuery
import dev.datlag.aniflow.anilist.MediumStateMachine
import dev.datlag.aniflow.anilist.RatingMutation
import dev.datlag.aniflow.anilist.*
import dev.datlag.aniflow.anilist.model.Medium
import dev.datlag.aniflow.anilist.type.MediaFormat
import dev.datlag.aniflow.anilist.type.MediaStatus
Expand All @@ -22,6 +19,7 @@ import dev.datlag.aniflow.common.onRenderApplyCommonScheme
import dev.datlag.aniflow.common.popular
import dev.datlag.aniflow.common.rated
import dev.datlag.aniflow.model.*
import dev.datlag.aniflow.other.BurningSeriesResolver
import dev.datlag.aniflow.other.Constants
import dev.datlag.aniflow.other.TokenRefreshHandler
import dev.datlag.aniflow.settings.Settings
Expand Down Expand Up @@ -302,7 +300,16 @@ class MediumScreenComponent(
)

private val userSettings by di.instance<Settings.PlatformUserSettings>()
private val apolloClient by di.instance<ApolloClient>(Constants.AniList.APOLLO_CLIENT)
private val characterStateMachine by di.instance<CharacterStateMachine>()
private val burningSeriesResolver by di.instance<BurningSeriesResolver>()

init {
launchIO {
title.mapNotNull { it.english to it.romaji }.collect { (english, romaji) ->
burningSeriesResolver.resolveByName(english = english, romaji = romaji)
}
}
}

@Composable
override fun render() {
Expand Down Expand Up @@ -346,7 +353,7 @@ class MediumScreenComponent(
id = Optional.present(mediaId.saveFirstOrNull() ?: mediaId.value)
)
val execution = CatchResult.timeout(5.seconds) {
apolloClient.query(query).execute()
aniListClient.query(query).execute()
}.asNullableSuccess()

execution?.data?.MediaList?.let { entry ->
Expand Down Expand Up @@ -383,7 +390,7 @@ class MediumScreenComponent(
rating = Optional.present(value * 20)
)
launchIO {
apolloClient.mutation(mutation).execute().data?.SaveMediaListEntry?.score?.let {
aniListClient.mutation(mutation).execute().data?.SaveMediaListEntry?.score?.let {
changedRating.emit(it.toInt())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ actual class BurningSeriesResolver {
return emptySet()
}

actual fun resolveByName(english: String?, romaji: String?) { }

actual fun close() { }
}
Loading

0 comments on commit eb2701a

Please sign in to comment.