Merge pull request #4122 from mhss1/main

Migrate deprecated Google SignIn + disable android backup
This commit is contained in:
Jackson Harper
2024-07-05 18:28:47 +08:00
committed by GitHub
10 changed files with 92 additions and 80 deletions

View File

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

View File

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

View File

@ -11,9 +11,9 @@
<application <application
android:name=".OmnivoreApplication" android:name=".OmnivoreApplication"
android:allowBackup="true" android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"

View File

@ -1,15 +1,9 @@
package app.omnivore.omnivore.feature.library package app.omnivore.omnivore.feature.library
import android.content.Context 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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue 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.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -39,7 +33,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.time.Instant import java.time.Instant
import java.util.jar.Manifest
import javax.inject.Inject import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class) @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.Constants
import app.omnivore.omnivore.utils.ResourceProvider import app.omnivore.omnivore.utils.ResourceProvider
import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.ApolloClient
import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import io.intercom.android.sdk.Intercom import io.intercom.android.sdk.Intercom
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -359,9 +357,8 @@ class OnboardingViewModel @Inject constructor(
setErrorMessage(resourceProvider.getString(R.string.login_view_model_google_auth_error_msg)) setErrorMessage(resourceProvider.getString(R.string.login_view_model_google_auth_error_msg))
} }
fun handleGoogleAuthTask(task: Task<GoogleSignInAccount>) { fun handleGoogleAuthCredential(credential: GoogleIdTokenCredential) {
val result = task.getResult(ApiException::class.java) val googleIdToken = credential.idToken
val googleIdToken = result?.idToken ?: ""
// If token is missing then set the error message // If token is missing then set the error message
if (googleIdToken.isEmpty()) { if (googleIdToken.isEmpty()) {

View File

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

View File

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

View File

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

View File

@ -5,15 +5,17 @@
--> -->
<data-extraction-rules> <data-extraction-rules>
<cloud-backup> <cloud-backup>
<!-- Use <include> and <exclude> to control what is backed up. <exclude domain="root" />
<include .../> <exclude domain="file" />
<exclude .../> <exclude domain="database" />
--> <exclude domain="sharedpref" />
<exclude domain="external" />
</cloud-backup> </cloud-backup>
<!--
<device-transfer> <device-transfer>
<include .../> <exclude domain="root" />
<exclude .../> <exclude domain="file" />
<exclude domain="database" />
<exclude domain="sharedpref" />
<exclude domain="external" />
</device-transfer> </device-transfer>
-->
</data-extraction-rules> </data-extraction-rules>

View File

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