diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b3f1b4..f5b5bc6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -123,6 +123,11 @@ dependencies { // Splash Screen support prior to Android 12 implementation("androidx.core:core-splashscreen:1.0.1") + // Google Auth + implementation("androidx.credentials:credentials:1.2.2") + implementation("androidx.credentials:credentials-play-services-auth:1.2.2") + implementation("com.google.android.libraries.identity.googleid:googleid:1.1.0") + // Google service implementation("com.google.android.gms:play-services-auth:21.2.0") implementation( diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt index cd2a6a6..968b454 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt @@ -51,6 +51,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.credentials.exceptions.GetCredentialCancellationException +import androidx.credentials.exceptions.GetCredentialProviderConfigurationException import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalActivity import com.pixelized.rplexicon.LocalSnack @@ -85,10 +87,9 @@ fun AuthenticationScreen( versionVM: VersionViewModel = hiltViewModel(), onSignIn: CoroutineScope.() -> Unit, ) { - val snack = LocalSnack.current val context = LocalContext.current + val snack = LocalSnack.current val activity = LocalActivity.current - val state = authenticationVM.rememberAuthenticationState() Surface { PartyBackground() @@ -105,17 +106,22 @@ fun AuthenticationScreen( ) HandleAuthenticationState( - state = state, + state = authenticationVM.authenticationState, onProgress = { Dialog(onDismissRequest = { }) { CircularProgressIndicator() } }, onSignIn = onSignIn, - onSignInError = { - snack.showSnackbar( - message = it?.message ?: context.getString(R.string.error__generic) - ) + onSignInError = { exception -> + when (exception) { + // Ignore user cancellation. + is GetCredentialCancellationException -> Unit + + else -> snack.showSnackbar( + message = exception?.message ?: context.getString(R.string.error__generic) + ) + } } ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt index 0422e53..752642c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationViewModel.kt @@ -1,96 +1,59 @@ package com.pixelized.rplexicon.ui.screens.authentication import android.app.Activity -import android.app.Application -import android.content.Context -import android.content.IntentSender.SendIntentException -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.ActivityResult -import androidx.activity.result.IntentSenderRequest -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.credentials.Credential import androidx.credentials.CredentialManager -import androidx.lifecycle.AndroidViewModel -import com.google.android.gms.auth.api.identity.GetSignInIntentRequest -import com.google.android.gms.auth.api.identity.Identity -import com.google.android.gms.auth.api.identity.SignInCredential -import com.google.firebase.auth.GoogleAuthProvider -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase +import androidx.credentials.GetCredentialRequest +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.pixelized.rplexicon.R import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject +/** + * https://developer.android.com/identity/sign-in/credential-manager-siwg + */ @HiltViewModel -class AuthenticationViewModel @Inject constructor( - application: Application, -) : AndroidViewModel(application) { +class AuthenticationViewModel @Inject constructor() : ViewModel() { - private val context: Context get() = getApplication() - private var launcher: ManagedActivityResultLauncher? = null - - private val state = mutableStateOf( + val authenticationState = mutableStateOf( AuthenticationStateUio.Initial ) - @Composable - fun rememberAuthenticationState(): State { - launcher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartIntentSenderForResult(), - onResult = { - if (it.resultCode == Activity.RESULT_OK) { - state.value = AuthenticationStateUio.Progress - // sign in request succeed. retrieve google credential - val googleCredential: SignInCredential = Identity - .getSignInClient(context) - .getSignInCredentialFromIntent(it.data) - // build firebase credential - val firebaseCredential = GoogleAuthProvider - .getCredential(googleCredential.googleIdToken, null) - // sign in to Firebase - Firebase.auth - .signInWithCredential(firebaseCredential) - .addOnCompleteListener { task -> - if (task.isSuccessful) { - state.value = AuthenticationStateUio.Success - } else { - state.value = AuthenticationStateUio.Failure(task.exception) - } - } - } else { - state.value = AuthenticationStateUio.Initial - } - }, - ) - return state - } - fun signIn(activity: Activity) { - state.value = AuthenticationStateUio.Initial + val credentialManager = CredentialManager.create(context = activity) - // build a request to sign in with google credential. - // At that point we do only use google sign in service - val request: GetSignInIntentRequest = GetSignInIntentRequest.builder() - .setServerClientId(context.getString(R.string.google_sign_in_id)) + val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption + .Builder(serverClientId = activity.getString(R.string.google_sign_in_id)) .build() - // use the pre register launcher to start the sign in request intent. - Identity.getSignInClient(activity).getSignInIntent(request) - .addOnSuccessListener { result -> - try { - launcher?.launch( - IntentSenderRequest.Builder(result.intentSender).build() + val request: GetCredentialRequest = GetCredentialRequest.Builder() + .addCredentialOption(credentialOption = signInWithGoogleOption) + .build() + + viewModelScope.launch(Dispatchers.IO) { + withContext(Dispatchers.Main) { + authenticationState.value = AuthenticationStateUio.Initial + } + try { + credentialManager.getCredential( + request = request, + context = activity, + ) + withContext(Dispatchers.Main) { + authenticationState.value = AuthenticationStateUio.Success + } + } catch (exception: Exception) { + withContext(Dispatchers.Main) { + authenticationState.value = AuthenticationStateUio.Failure( + exception = exception, ) - } catch (exception: SendIntentException) { - state.value = AuthenticationStateUio.Failure(exception = exception) } } - .addOnFailureListener { exception -> - state.value = AuthenticationStateUio.Failure(exception = exception) - } + } } } \ No newline at end of file