Migrate deprecated Google SignIn to Credentials Manager

This commit is contained in:
Mohamed
2024-06-30 18:04:38 +03:00
parent f582497956
commit c4b0404795
8 changed files with 80 additions and 70 deletions

View File

@ -104,8 +104,9 @@ dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.gms.playServicesBase)
implementation(libs.gms.playServicesAuth)
implementation(libs.androidx.credentials.auth)
implementation(libs.androidx.credentials)
implementation(libs.googleid)
val bom = platform(libs.androidx.compose.bom)
implementation(bom)

View File

@ -19,3 +19,8 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}

View File

@ -1,15 +1,9 @@
package app.omnivore.omnivore.feature.library
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.widget.Toast
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -39,7 +33,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.time.Instant
import java.util.jar.Manifest
import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)

View File

@ -35,9 +35,7 @@ import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery
import app.omnivore.omnivore.utils.Constants
import app.omnivore.omnivore.utils.ResourceProvider
import com.apollographql.apollo3.ApolloClient
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import dagger.hilt.android.lifecycle.HiltViewModel
import io.intercom.android.sdk.Intercom
import kotlinx.coroutines.Job
@ -359,9 +357,8 @@ class OnboardingViewModel @Inject constructor(
setErrorMessage(resourceProvider.getString(R.string.login_view_model_google_auth_error_msg))
}
fun handleGoogleAuthTask(task: Task<GoogleSignInAccount>) {
val result = task.getResult(ApiException::class.java)
val googleIdToken = result?.idToken ?: ""
fun handleGoogleAuthCredential(credential: GoogleIdTokenCredential) {
val googleIdToken = credential.idToken
// If token is missing then set the error message
if (googleIdToken.isEmpty()) {

View File

@ -17,7 +17,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
@ -27,28 +26,25 @@ import app.omnivore.omnivore.feature.onboarding.OnboardingViewModel
import app.omnivore.omnivore.feature.onboarding.auth.provider.AppleAuthButton
import app.omnivore.omnivore.feature.onboarding.auth.provider.GoogleAuthButton
import app.omnivore.omnivore.navigation.Routes
import com.google.android.gms.common.GoogleApiAvailability
@Composable
fun AuthProviderScreen(
welcomeNavController: NavHostController,
viewModel: OnboardingViewModel
) {
val isGoogleAuthAvailable: Boolean =
GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(LocalContext.current) == 0
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(bottom = 64.dp)
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 64.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.width(500.dp)
) {
if (isGoogleAuthAvailable) {
GoogleAuthButton(viewModel)
}
GoogleAuthButton(viewModel)
AppleAuthButton(viewModel)
@ -66,7 +62,10 @@ fun AuthProviderScreen(
contentColor = MaterialTheme.colorScheme.onSurface
)
) {
Text(text = stringResource(R.string.welcome_screen_action_continue_with_email), modifier = Modifier.padding(vertical = 6.dp))
Text(
text = stringResource(R.string.welcome_screen_action_continue_with_email),
modifier = Modifier.padding(vertical = 6.dp)
)
}
Spacer(modifier = Modifier.weight(1.0F))
@ -80,7 +79,7 @@ fun AuthProviderScreen(
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.onSurface
)
){
) {
Text(
text = stringResource(R.string.welcome_screen_action_self_hosting_options),
textDecoration = TextDecoration.Underline

View File

@ -1,9 +1,5 @@
package app.omnivore.omnivore.feature.onboarding.auth.provider
import android.app.Activity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -17,53 +13,64 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
import app.omnivore.omnivore.feature.onboarding.OnboardingViewModel
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.tasks.Task
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import kotlinx.coroutines.launch
@Composable
fun GoogleAuthButton(viewModel: OnboardingViewModel) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val credentialManager = remember { CredentialManager.create(context) }
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(BuildConfig.OMNIVORE_GAUTH_SERVER_CLIENT_ID).requestEmail().build()
val googleIdOption = remember {
GetSignInWithGoogleOption.Builder(BuildConfig.OMNIVORE_GAUTH_SERVER_CLIENT_ID)
.build()
}
val startForResult =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data
if (result.data != null) {
val task: Task<GoogleSignInAccount> =
GoogleSignIn.getSignedInAccountFromIntent(intent)
viewModel.handleGoogleAuthTask(task)
}
} else {
viewModel.showGoogleErrorMessage()
}
}
val request = remember {
GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
}
OutlinedButton(
onClick = {
val googleSignIn = GoogleSignIn.getClient(context, signInOptions)
googleSignIn.silentSignIn().addOnCompleteListener { task ->
if (task.isSuccessful) {
viewModel.handleGoogleAuthTask(task)
} else {
startForResult.launch(googleSignIn.signInIntent)
scope.launch {
try {
val credential = credentialManager.getCredential(
request = request,
context = context,
).credential
if (credential is CustomCredential &&
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
) {
viewModel.handleGoogleAuthCredential(
GoogleIdTokenCredential
.createFrom(credential.data)
)
} else {
viewModel.showGoogleErrorMessage()
}
} catch (e: Exception) {
e.printStackTrace()
viewModel.showGoogleErrorMessage()
}
}.addOnFailureListener {
startForResult.launch(googleSignIn.signInIntent)
}
},
modifier = Modifier
@ -80,7 +87,10 @@ fun GoogleAuthButton(viewModel: OnboardingViewModel) {
contentDescription = "",
modifier = Modifier.padding(end = 10.dp)
)
Text(text = stringResource(R.string.google_auth_text), modifier = Modifier.padding(vertical = 6.dp))
Text(
text = stringResource(R.string.google_auth_text),
modifier = Modifier.padding(vertical = 6.dp)
)
if (viewModel.isLoading) {
Spacer(modifier = Modifier.width(16.dp))
CircularProgressIndicator(

View File

@ -6,16 +6,22 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CredentialManager
import app.omnivore.omnivore.R
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import kotlinx.coroutines.launch
@Composable
fun LogoutDialog(onClose: (Boolean) -> Unit) {
val context = LocalContext.current
val credentialManager = remember { CredentialManager.create(context) }
val scope = rememberCoroutineScope()
AlertDialog(onDismissRequest = { onClose(false) },
title = { Text(text = stringResource(R.string.logout_dialog_title)) },
text = {
@ -28,12 +34,10 @@ fun LogoutDialog(onClose: (Boolean) -> Unit) {
contentColor = MaterialTheme.colorScheme.onSurface
),
onClick = {
val signInOptions =
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).build()
val googleSignIn = GoogleSignIn.getClient(context, signInOptions)
googleSignIn.signOut()
onClose(true)
scope.launch {
credentialManager.clearCredentialState(ClearCredentialStateRequest())
onClose(true)
}
}) {
Text(stringResource(R.string.logout_dialog_action_confirm))
}

View File

@ -24,14 +24,14 @@ junit4 = "4.13.2"
kotlin = "2.0.0"
ksp = "2.0.0-1.0.21"
kotlinxCoroutines = "1.8.0"
playServices = "18.5.0"
playServicesAuth = "21.2.0"
androidxCredentials = "1.2.2"
posthog = "2.0.3"
pspdfkit = "8.9.1"
retrofit = "2.11.0"
room = "2.6.1"
workManager = "2.9.0"
hiltWork = "1.2.0"
googleid = "1.1.1"
[libraries]
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanistFlowLayout" }
@ -65,8 +65,9 @@ chiptextfield-m3 = { group = "io.github.dokar3", name = "chiptextfield-m3", vers
coil-kt-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
compose-markdown = { group = "com.github.jeziellago", name = "compose-markdown", version.ref = "composeMarkdown" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
gms-playServicesAuth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "playServicesAuth" }
gms-playServicesBase = { group = "com.google.android.gms", name = "play-services-base", version.ref = "playServices" }
androidx-credentials = { group = "androidx.credentials", name = "credentials", version.ref = "androidxCredentials" }
androidx-credentials-auth = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "androidxCredentials" }
googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
intercom = { group = "io.intercom.android", name = "intercom-sdk", version.ref = "intercom" }