diff --git a/android/Omnivore/app/build.gradle.kts b/android/Omnivore/app/build.gradle.kts index add94196f..92b30e242 100644 --- a/android/Omnivore/app/build.gradle.kts +++ b/android/Omnivore/app/build.gradle.kts @@ -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) diff --git a/android/Omnivore/app/proguard-rules.pro b/android/Omnivore/app/proguard-rules.pro index 2f9dc5a47..5519fb0ba 100644 --- a/android/Omnivore/app/proguard-rules.pro +++ b/android/Omnivore/app/proguard-rules.pro @@ -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.** { + *; +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt index db98ed21d..c241f36a2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt @@ -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) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/OnboardingViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/OnboardingViewModel.kt index 008aaf4ac..5887807ff 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/OnboardingViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/OnboardingViewModel.kt @@ -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) { - 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()) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/AuthProviderScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/AuthProviderScreen.kt index 1bc1e64e2..82e27ad4e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/AuthProviderScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/AuthProviderScreen.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/provider/GoogleAuthButton.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/provider/GoogleAuthButton.kt index e97cb2d40..7f86642fe 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/provider/GoogleAuthButton.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/onboarding/auth/provider/GoogleAuthButton.kt @@ -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 = - 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( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/LogoutDialog.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/LogoutDialog.kt index f48ead0dc..285a82dd7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/LogoutDialog.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/profile/LogoutDialog.kt @@ -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)) } diff --git a/android/Omnivore/gradle/libs.versions.toml b/android/Omnivore/gradle/libs.versions.toml index eeb8cec32..5c3ec3cbd 100644 --- a/android/Omnivore/gradle/libs.versions.toml +++ b/android/Omnivore/gradle/libs.versions.toml @@ -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" }