Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add account switcher #28

Merged
merged 13 commits into from
Sep 6, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fragment UserAccount on User {
login
name
avatarUrl
id
notificationListsWithThreadCount(statuses: UNREAD) {
totalCount
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query AccountInfo {
viewer {
...UserAccount
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class GraphQLRepository(
private val service: GraphQLService
) {

suspend fun getAccountInfo(token: String) =
service.getAccountInfo(token).transform { it.viewer.userAccount }

suspend fun getCurrentProfile() =
service.getCurrentProfile().transform { ModelUser.fromProfileQuery(it) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import com.apollographql.apollo3.ApolloCall
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Optional
import com.apollographql.apollo3.cache.normalized.doNotStore
import com.materiiapps.gloom.api.utils.response
import com.materiiapps.gloom.api.utils.toOptional
import com.materiiapps.gloom.domain.manager.AuthManager
import com.materiiapps.gloom.gql.AccountInfoQuery
import com.materiiapps.gloom.gql.DefaultBranchQuery
import com.materiiapps.gloom.gql.FeedQuery
import com.materiiapps.gloom.gql.FollowUserMutation
Expand Down Expand Up @@ -46,6 +48,13 @@ class GraphQLService(
private fun <D : Operation.Data> ApolloCall<D>.addToken() =
addHttpHeader(HttpHeaders.Authorization, "Bearer ${authManager.authToken}")

suspend fun getAccountInfo(token: String) = withContext(Dispatchers.IO) {
client.query(AccountInfoQuery())
.doNotStore(true)
.addHttpHeader(HttpHeaders.Authorization, "Bearer $token")
.response()
}

suspend fun getCurrentProfile() = withContext(Dispatchers.IO) {
client.query(ProfileQuery())
.addToken()
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/com/materiiapps/gloom/Gloom.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import com.materiiapps.gloom.di.modules.settingsModule
import com.materiiapps.gloom.di.repositoryModule
import com.materiiapps.gloom.di.serviceModule
import com.materiiapps.gloom.di.viewModelModule
import com.materiiapps.gloom.ui.viewmodels.main.MainViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModelOf
import org.koin.core.context.startKoin
import org.koin.dsl.module

class Gloom : Application() {

Expand All @@ -25,7 +28,8 @@ class Gloom : Application() {
repositoryModule(),
settingsModule(),
managerModule(),
viewModelModule()
viewModelModule(),
module { viewModelOf(::MainViewModel) } // Cant group with the rest
rushiiMachine marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
Expand Down
40 changes: 23 additions & 17 deletions app/src/main/java/com/materiiapps/gloom/ui/GloomActivity.kt
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
package com.materiiapps.gloom.ui

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.materiiapps.gloom.api.repository.GithubAuthRepository
import com.materiiapps.gloom.api.utils.ifSuccessful
import com.materiiapps.gloom.domain.manager.AuthManager
import com.materiiapps.gloom.domain.manager.PreferenceManager
import com.materiiapps.gloom.domain.manager.Theme
import com.materiiapps.gloom.ui.screens.auth.LandingScreen
import com.materiiapps.gloom.ui.screens.root.RootScreen
import com.materiiapps.gloom.ui.theme.GloomTheme
import com.materiiapps.gloom.ui.transitions.SlideTransition
import com.materiiapps.gloom.ui.viewmodels.main.MainViewModel
import com.materiiapps.gloom.utils.LinkHandler
import com.materiiapps.gloom.utils.LocalLinkHandler
import com.materiiapps.gloom.utils.deeplinks.DeepLinkWrapper
import com.materiiapps.gloom.utils.deeplinks.addAllRoutes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.materiiapps.gloom.utils.getOAuthCode
import com.materiiapps.gloom.utils.isOAuthUri
import org.koin.android.ext.android.getKoin
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel

class GloomActivity : ComponentActivity() {

private val authRepo: GithubAuthRepository by inject()
private val viewModel: MainViewModel by viewModel()
private val auth: AuthManager by inject()
private val prefs: PreferenceManager by inject()

private lateinit var navigator: Navigator
private var isLastIntentOauth: Boolean = intent?.isOAuthUri() ?: false

@OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)

WindowCompat.setDecorFitsSystemWindows(window, false)
getKoin().declare<Context>(this@GloomActivity)
rushiiMachine marked this conversation as resolved.
Show resolved Hide resolved

setContent {
val isDark = when (prefs.theme) {
Expand Down Expand Up @@ -94,20 +96,24 @@ class GloomActivity : ComponentActivity() {

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
isLastIntentOauth = intent?.isOAuthUri() ?: false
intent?.let {
if (it.data.toString().startsWith("github://com.github.android/oauth")) {
println(it.data.toString())
it.data!!.getQueryParameter("code")?.let {
lifecycleScope.launch(Dispatchers.IO) {
val res = authRepo.getAccessToken(it)
res.ifSuccessful { token ->
auth.authToken = token.accessToken
navigator.replace(RootScreen())
}
if (it.isOAuthUri()) {
if (viewModel.authManager.awaitingAuthType == null) return
it.getOAuthCode()?.let { code ->
viewModel.loginWithOAuthCode(code) {
navigator.replaceAll(RootScreen())
}
}
}
}
}

override fun onResume() {
super.onResume()
if (!isLastIntentOauth) {
auth.setAuthState(authType = null)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.materiiapps.gloom.ui.viewmodels.main

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.materiiapps.gloom.api.repository.GithubAuthRepository
import com.materiiapps.gloom.api.repository.GraphQLRepository
import com.materiiapps.gloom.api.utils.fold
import com.materiiapps.gloom.api.utils.ifSuccessful
import com.materiiapps.gloom.domain.manager.AuthManager
import com.materiiapps.gloom.ui.utils.clearRootNavigation
import kotlinx.coroutines.launch

class MainViewModel(
private val gqlRepository: GraphQLRepository,
private val authRepository: GithubAuthRepository,
val authManager: AuthManager
) : ViewModel() {

fun loginWithOAuthCode(code: String, onLoggedIn: () -> Unit) {
viewModelScope.launch {
authManager.setAuthState(loading = true)
authRepository.getAccessToken(code).fold(
success = { token ->
gqlRepository.getAccountInfo(token.accessToken).ifSuccessful { account ->
authManager.addAccount(
id = account.id,
token = token.accessToken,
type = authManager.awaitingAuthType!!,
username = account.login,
avatarUrl = account.avatarUrl,
displayName = account.name,
notificationCount = account.notificationListsWithThreadCount.totalCount
)
authManager.switchToAccount(account.id)
clearRootNavigation()
onLoggedIn()
}
clearAuthState()
},
error = { clearAuthState() },
failure = { clearAuthState() },
empty = { clearAuthState() }
)
}
}

private fun clearAuthState() = authManager.setAuthState(authType = null, loading = false)

}
11 changes: 11 additions & 0 deletions app/src/main/java/com/materiiapps/gloom/utils/OAuthUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.materiiapps.gloom.utils

import android.content.Intent

fun Intent.isOAuthUri() = data.toString().startsWith("github://com.github.android/oauth")

fun Intent.getOAuthCode(): String? {
if(!isOAuthUri()) return null
val code = data?.getQueryParameter("code")
return code?.ifBlank { null }
}
1 change: 1 addition & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.compose)
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
}

android {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fun Context.openCustomTab(url: String, force: Boolean) = CustomTabsIntent.Builde
launchUrl(this@openCustomTab, Uri.parse(url))
}

fun Context.openLink(url: Uri) {
fun Context.openLink(url: Uri, forceCustomTab: Boolean = false) {
// TODO: Setting to disable custom tabs
openCustomTab(url.toString(), force = false)
openCustomTab(url.toString(), force = forceCustomTab)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import androidx.core.net.toUri

actual class LinkHandler(private val ctx: Context) {

actual fun openLink(link: String) {
actual fun openLink(link: String, forceCustomTab: Boolean) {
try {
ctx.openLink(link.toUri())
ctx.openLink(link.toUri(), forceCustomTab)
} catch (e: Throwable) {
Log.e("LinkHandler", "Failed to open link", e)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
package com.materiiapps.gloom.di.modules

import com.apollographql.apollo3.ApolloClient
import com.materiiapps.gloom.domain.manager.AuthManager
import com.materiiapps.gloom.domain.manager.DialogManager
import com.materiiapps.gloom.domain.manager.DownloadManager
import com.materiiapps.gloom.domain.manager.PreferenceManager
import com.materiiapps.gloom.domain.manager.ShareManager
import com.materiiapps.gloom.domain.manager.ToastManager
import com.materiiapps.gloom.utils.Logger
import com.materiiapps.gloom.utils.SettingsProvider
import kotlinx.serialization.json.Json
import org.koin.core.module.dsl.singleOf
import org.koin.core.qualifier.named
import org.koin.dsl.module

fun managerModule() = module {

singleOf(::AuthManager)
singleOf(::DownloadManager)
singleOf(::ShareManager)
singleOf(::ToastManager)

fun providePreferenceManager(settings: SettingsProvider) = PreferenceManager(settings)
fun provideDialogManager(settings: SettingsProvider) = DialogManager(settings)
fun provideAuthManager(settings: SettingsProvider, apollo: ApolloClient, json: Json, logger: Logger) = AuthManager(settings, apollo, json, logger)

single { providePreferenceManager(get(named("prefs"))) }
single { provideDialogManager(get(named("dialogs"))) }
single { provideAuthManager(get(named("auth")), get(), get(), get()) }

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ fun settingsModule() = module {
SettingsProvider(get(), "dialogs")
}

single(named("auth")) {
SettingsProvider(get(), "auth")
}

}
Loading
Loading