Skip to content

Commit

Permalink
get rid of token refresh and add user helper
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed Apr 28, 2024
1 parent 46025b3 commit 9985941
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 151 deletions.
14 changes: 14 additions & 0 deletions anilist/src/commonMain/graphql/ViewerMutation.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mutation ViewerMutation($adult: Boolean) {
UpdateUser(displayAdultContent: $adult) {
id,
name,
avatar {
medium,
large
},
bannerImage,
options {
displayAdultContent
}
}
}
14 changes: 14 additions & 0 deletions anilist/src/commonMain/graphql/ViewerQuery.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
query ViewerQuery {
Viewer {
id,
name,
avatar {
medium,
large
},
bannerImage,
options {
displayAdultContent
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.datlag.aniflow.anilist.model

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

@Serializable
data class User(
val id: Int,
val name: String,
val avatar: Avatar = Avatar(),
val banner: String? = null,
val displayAdultContent: Boolean = false
) {
constructor(query: ViewerQuery.Viewer) : this(
id = query.id,
name = query.name,
avatar = query.avatar.let(::Avatar),
banner = query.bannerImage?.ifBlank { null },
displayAdultContent = query.options?.displayAdultContent ?: false
)

@Serializable
data class Avatar(
val medium: String? = null,
val large: String? = null
) {
constructor(query: ViewerQuery.Avatar?) : this(
medium = query?.medium?.ifBlank { null },
large = query?.large?.ifBlank { null }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ import org.kodein.di.DI
import org.kodein.di.bindSingleton
import org.kodein.di.instance
import dev.datlag.aniflow.common.nullableFirebaseInstance
import dev.datlag.aniflow.other.TokenRefreshHandler
import dev.datlag.aniflow.model.safeFirstOrNull
import dev.datlag.aniflow.other.UserHelper
import dev.datlag.aniflow.settings.Settings
import dev.datlag.aniflow.trace.Trace
import dev.datlag.aniflow.trace.TraceStateMachine
import dev.datlag.tooling.async.suspendCatching
import io.github.aakira.napier.Napier
import kotlinx.coroutines.flow.map
import org.kodein.di.bindProvider
import org.publicvalue.multiplatform.oidc.OpenIdConnectClient

Expand Down Expand Up @@ -62,22 +65,17 @@ data object NetworkModule {
.build()
}
bindSingleton<ApolloClient>(Constants.AniList.APOLLO_CLIENT) {
val oidc = instance<OpenIdConnectClient>(Constants.AniList.Auth.CLIENT)
val tokenHandler = instance<TokenRefreshHandler>()
val userSettings = instance<Settings.PlatformUserSettings>()

ApolloClient.Builder()
.dispatcher(ioDispatcher())
.serverUrl(Constants.AniList.SERVER_URL)
.addHttpInterceptor(object : HttpInterceptor {
override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
val req = request.newBuilder().apply {
val refreshedToken = tokenHandler.getAccessToken()?.let {
suspendCatching {
tokenHandler.refreshAndSaveToken(oidc, it).accessToken
}.getOrNull() ?: it
}
val token = userSettings.aniList.map { it.accessToken }.safeFirstOrNull()

refreshedToken?.let {
token?.let {
addHeader("Authorization", "Bearer $it")
}
}.build()
Expand Down Expand Up @@ -133,8 +131,14 @@ data object NetworkModule {
redirectUri = Constants.AniList.Auth.REDIRECT_URL
}
}
bindSingleton<TokenRefreshHandler> {
TokenRefreshHandler(instance())
bindSingleton<UserHelper> {
UserHelper(
userSettings = instance(),
appSettings = instance(),
client = instance(Constants.AniList.APOLLO_CLIENT),
authFlowFactory = instance(),
oidc = instance(Constants.AniList.Auth.CLIENT)
)
}
bindSingleton<Ktorfit.Builder> {
ktorfitBuilder {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dev.datlag.aniflow.other

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dev.datlag.aniflow.anilist.ViewerMutation
import dev.datlag.aniflow.anilist.ViewerQuery
import dev.datlag.aniflow.anilist.model.User
import dev.datlag.aniflow.model.safeFirstOrNull
import dev.datlag.aniflow.settings.Settings
import dev.datlag.tooling.async.suspendCatching
import dev.datlag.tooling.compose.withMainContext
import kotlinx.coroutines.flow.*
import kotlinx.datetime.Clock
import org.publicvalue.multiplatform.oidc.OpenIdConnectClient
import org.publicvalue.multiplatform.oidc.appsupport.CodeAuthFlowFactory
import org.publicvalue.multiplatform.oidc.types.remote.AccessTokenResponse

class UserHelper(
private val userSettings: Settings.PlatformUserSettings,
private val appSettings: Settings.PlatformAppSettings,
private val client: ApolloClient,
private val authFlowFactory: CodeAuthFlowFactory,
private val oidc: OpenIdConnectClient
) {

val isLoggedIn: Flow<Boolean> = userSettings.isAniListLoggedIn.distinctUntilChanged()
val user: Flow<User?> = isLoggedIn.transform { loggedIn ->
if (loggedIn) {
emitAll(
client.query(ViewerQuery()).toFlow().map {
it.data?.Viewer?.let(::User)?.also { user ->
appSettings.setAdultContent(user.displayAdultContent)
}
}
)
} else {
emit(null)
}
}

suspend fun login(): Boolean {
if (isLoggedIn.safeFirstOrNull() == true) {
return true
}

val flow = withMainContext {
authFlowFactory.createAuthFlow(oidc)
}

val tokenResult = suspendCatching {
flow.getAccessToken()
}

tokenResult.getOrNull()?.let {
updateStoredToken(it)
}

return tokenResult.isSuccess
}

suspend fun updateAdultSetting(value: Boolean) {
appSettings.setAdultContent(value)
client.mutation(
ViewerMutation(
adult = Optional.present(value)
)
).execute()
}

private suspend fun updateStoredToken(tokenResponse: AccessTokenResponse) {
userSettings.setAniListTokens(
access = tokenResponse.access_token,
expires = tokenResponse.expires_in?.let {
Clock.System.now().epochSeconds + it
}?.toInt()
)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package dev.datlag.aniflow.ui.navigation.screen.initial.settings

import dev.datlag.aniflow.anilist.model.User
import dev.datlag.aniflow.ui.navigation.Component
import kotlinx.coroutines.flow.Flow

interface SettingsComponent : Component {
val user: Flow<User?>
val adultContent: Flow<Boolean>

fun changeAdultContent(value: Boolean)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dev.datlag.aniflow.ui.navigation.screen.initial.settings
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Check
Expand All @@ -13,8 +14,12 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.compose.rememberAsyncImagePainter
import dev.chrisbanes.haze.haze
import dev.datlag.aniflow.LocalHaze
import dev.datlag.aniflow.LocalPaddingValues
Expand All @@ -39,7 +44,33 @@ fun SettingsScreen(component: SettingsComponent) {
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
item {
Text(
val user by component.user.collectAsStateWithLifecycle(null)

user?.let { u ->
Column(
modifier = Modifier.fillParentMaxWidth().padding(bottom = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
modifier = Modifier.size(96.dp).clip(CircleShape),
model = u.avatar.large,
contentDescription = null,
error = rememberAsyncImagePainter(
model = u.avatar.medium,
contentScale = ContentScale.Crop,
),
contentScale = ContentScale.Crop,
alignment = Alignment.Center
)
Text(
text = u.name,
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
}
} ?: Text(
modifier = Modifier.padding(bottom = 8.dp),
text = "Settings",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package dev.datlag.aniflow.ui.navigation.screen.initial.settings

import androidx.compose.runtime.Composable
import com.arkivanov.decompose.ComponentContext
import dev.datlag.aniflow.anilist.model.User
import dev.datlag.aniflow.common.onRender
import dev.datlag.aniflow.other.UserHelper
import dev.datlag.aniflow.settings.Settings
import dev.datlag.tooling.compose.ioDispatcher
import dev.datlag.tooling.decompose.ioScope
Expand All @@ -16,6 +18,9 @@ class SettingsScreenComponent(
) : SettingsComponent, ComponentContext by componentContext {

private val appSettings by di.instance<Settings.PlatformAppSettings>()
private val userHelper by di.instance<UserHelper>()

override val user: Flow<User?> = userHelper.user.flowOn(ioDispatcher())
override val adultContent: Flow<Boolean> = appSettings.adultContent.flowOn(ioDispatcher())

@Composable
Expand All @@ -27,7 +32,7 @@ class SettingsScreenComponent(

override fun changeAdultContent(value: Boolean) {
launchIO {
appSettings.setAdultContent(value)
userHelper.updateAdultSetting(value)
}
}
}
Loading

0 comments on commit 9985941

Please sign in to comment.