Skip to content

Commit

Permalink
Merge pull request #118 from imashnake0/auth
Browse files Browse the repository at this point in the history
Auth
  • Loading branch information
imashnake0 authored Jan 15, 2024
2 parents 4dd5f8c + b19f774 commit 54b14f4
Show file tree
Hide file tree
Showing 30 changed files with 403 additions and 44 deletions.
5 changes: 2 additions & 3 deletions api/anilist/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:Suppress("UnstableApiUsage")

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin)
Expand Down Expand Up @@ -28,14 +26,15 @@ kotlin {
}

dependencies {
implementation(project(":api:preferences"))

// Apollo Kotlin
implementation(libs.apollo.runtime)
implementation(libs.apollo.cache.memory)
implementation(libs.apollo.cache.sqlite)

// Hilt
implementation(libs.hilt.android)
implementation(libs.hilt.navigationCompose)
ksp(libs.hilt.android.compiler)
}

Expand Down
2 changes: 1 addition & 1 deletion api/anilist/src/main/graphql/MediaQuery.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ query MediaQuery(
$id: Int,
$type: MediaType
) {
media: Media (id: $id, type: $type) {
media: Media(id: $id, type: $type) {
bannerImage
coverImage {
extraLarge
Expand Down
35 changes: 35 additions & 0 deletions api/anilist/src/main/graphql/UserQuery.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
query UserQuery(
$id: Int,
$name: String,
$isModerator: Boolean,
$search: String,
$sort: [UserSort],
) {
user: User(
id: $id,
name: $name
isModerator: $isModerator
search: $search
sort: $sort
) {
id
name
about
avatar {
large
}
bannerImage
}
}

query Viewer {
viewer: Viewer {
id
name
about
avatar {
large
}
bannerImage
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ package com.imashnake.animite.api.anilist

import android.content.Context
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.http.HttpRequest
import com.apollographql.apollo3.api.http.HttpResponse
import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.normalizedCache
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo3.network.http.HttpInterceptor
import com.apollographql.apollo3.network.http.HttpInterceptorChain
import com.apollographql.apollo3.network.http.LoggingInterceptor
import com.imashnake.animite.api.preferences.PreferencesRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import javax.inject.Qualifier
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AnilistApiModule {

// TODO name this so we can have other apollo clients for different APIs
@Provides
@Singleton
fun provideApolloClient(
Expand All @@ -35,4 +40,45 @@ object AnilistApiModule {
.normalizedCache(cacheFactory)
.build()
}

@Provides
@Singleton
@AuthorizedClient
fun provideAuthorizedApolloClient(
@ApplicationContext context: Context,
httpInterceptor: HttpInterceptor
): ApolloClient {
val cacheFactory = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024)
.chain(SqlNormalizedCacheFactory(context, "apollo.db"))
return ApolloClient.Builder()
.dispatcher(Dispatchers.IO)
.serverUrl("https://graphql.anilist.co/")
.addHttpInterceptor(httpInterceptor)
.addHttpInterceptor(LoggingInterceptor(LoggingInterceptor.Level.BODY))
.normalizedCache(cacheFactory)
.build()
}

@Singleton
@Provides
fun provideHttpInterceptor(
preferencesRepository: PreferencesRepository
): HttpInterceptor = object : HttpInterceptor {
override suspend fun intercept(
request: HttpRequest,
chain: HttpInterceptorChain
): HttpResponse {
return chain.proceed(
request.newBuilder().apply {
preferencesRepository.accessToken.first()?.let {
addHeader("Authorization", "Bearer $it")
}
}.build()
)
}
}
}

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthorizedClient
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import com.imashnake.animite.api.anilist.type.MediaType
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

/**
* Repository for fetching [MediaQuery.Media] or a list of [MediaListQuery.Medium].
*
* @param apolloClient Default apollo client.
* @property fetchMediaList Fetches a list of [MediaListQuery.Medium].
* @property fetchMedia Fetches detailed media: [MediaQuery.Media].
*/
class AnilistMediaRepository @Inject constructor(
private val apolloClient: ApolloClient
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import com.imashnake.animite.api.anilist.type.MediaType
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

/**
* Repository for fetching media search results (e.g., search bar).
*
* @param apolloClient Default apollo client.
* @property fetchSearch Fetch a list of `search`es.
*/
class AnilistSearchRepository @Inject constructor(
private val apolloClient: ApolloClient
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.imashnake.animite.api.anilist

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

/**
* Repository for anything user related. Including the [ViewerQuery.Viewer].
*
* @param apolloClient Client with the [`Authorization` header](https://anilist.gitbook.io/anilist-apiv2-docs/overview/oauth/implicit-grant#making-authenticated-requests).
* @property fetchViewer Fetches the current user with an authorized [apolloClient].
*/
class AnilistUserRepository @Inject constructor(
@AuthorizedClient private val apolloClient: ApolloClient
) {
fun fetchViewer(): Flow<Result<ViewerQuery.Viewer>> {
return apolloClient
.query(ViewerQuery())
.fetchPolicy(FetchPolicy.CacheAndNetwork).toFlow()
.asResult { it.viewer!! }
}
}
34 changes: 34 additions & 0 deletions api/preferences/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
}

android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

namespace = "com.imashnake.animite.api.preferences"
}

kotlin {
jvmToolchain(17)
}

dependencies {
// Hilt
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)

// DataStore
implementation(libs.datastore)
}
1 change: 1 addition & 0 deletions api/preferences/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.imashnake.animite.api.preferences

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
@Singleton
@Provides
fun providePreferencesDataStore(@ApplicationContext appContext: Context): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
produceFile = { appContext.preferencesDataStoreFile("default") }
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.imashnake.animite.api.preferences

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.stringPreferencesKey
import com.imashnake.animite.api.preferences.ext.getValue
import com.imashnake.animite.api.preferences.ext.setValue
import javax.inject.Inject

class PreferencesRepository @Inject constructor(
private val dataStore: DataStore<Preferences>
) {
private val accessTokenKey = stringPreferencesKey("access_token")
val accessToken = dataStore.getValue(accessTokenKey, null)

suspend fun setAccessToken(accessToken: String?) {
dataStore.setValue(accessTokenKey, accessToken)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.imashnake.animite.api.preferences.ext

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import kotlinx.coroutines.flow.map

fun <T> DataStore<Preferences>.getValue(
key: Preferences.Key<T>,
default: T? = null
) = data.map {
it[key] ?: default
}

suspend fun <T> DataStore<Preferences>.setValue(
key: Preferences.Key<T>,
value: T?
) = edit {
if (value != null) it[key] = value
else it.remove(key)
}
2 changes: 0 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:Suppress("UnstableApiUsage")

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin)
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="jinnah"
android:host="animite" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import com.imashnake.animite.core.extensions.bannerParallax
import com.imashnake.animite.core.extensions.landscapeCutoutPadding
import com.imashnake.animite.core.ui.ProgressIndicator
import com.imashnake.animite.core.ui.TranslucentStatusBarLayout
import com.imashnake.animite.data.Resource
import com.imashnake.animite.core.data.Resource
import com.imashnake.animite.features.destinations.MediaPageDestination
import com.imashnake.animite.features.media.MediaPageArgs
import com.imashnake.animite.features.ui.MediaSmall
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import androidx.lifecycle.viewModelScope
import com.imashnake.animite.api.anilist.AnilistMediaRepository
import com.imashnake.animite.api.anilist.type.MediaSort
import com.imashnake.animite.api.anilist.type.MediaType
import com.imashnake.animite.data.Resource
import com.imashnake.animite.data.Resource.Companion.asResource
import com.imashnake.animite.core.data.Resource
import com.imashnake.animite.core.data.Resource.Companion.asResource
import com.imashnake.animite.dev.ext.nextSeason
import com.imashnake.animite.dev.ext.season
import com.imashnake.animite.dev.internal.Constants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.imashnake.animite.profile.ProfileNavGraph
import com.imashnake.animite.rslash.RslashNavGraph
import com.ramcosta.composedestinations.spec.DestinationSpec
import com.ramcosta.composedestinations.utils.currentDestinationAsState
import com.ramcosta.composedestinations.utils.isRouteOnBackStack
import com.ramcosta.composedestinations.utils.startDestination
import com.imashnake.animite.R as Res

Expand All @@ -52,12 +53,17 @@ fun NavigationBar(
) { WindowInsets.displayCutout } else { WindowInsets(0.dp) }
) {
val currentDestination by navController.currentDestinationAsState()

NavigationBarPaths.values().forEach { destination ->
NavigationBarPaths.entries.forEach { destination ->
val isCurrentDestOnBackStack = navController.isRouteOnBackStack(destination.route)
NavigationBarItem(
modifier = Modifier.navigationBarsPadding(),
selected = currentDestination?.startDestination == destination.route,
onClick = {
if (isCurrentDestOnBackStack) {
navController.popBackStack(destination.route.route, false)
return@NavigationBarItem
}

navController.navigate(destination.route.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.imashnake.animite.api.anilist.AnilistSearchRepository
import com.imashnake.animite.api.anilist.type.MediaType
import com.imashnake.animite.data.Resource
import com.imashnake.animite.data.Resource.Companion.asResource
import com.imashnake.animite.core.data.Resource
import com.imashnake.animite.core.data.Resource.Companion.asResource
import com.imashnake.animite.dev.internal.Constants
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down
2 changes: 0 additions & 2 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:Suppress("UnstableApiUsage")

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.imashnake.animite.data
package com.imashnake.animite.core.data

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
Expand Down
Loading

0 comments on commit 54b14f4

Please sign in to comment.