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.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

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

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

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

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" }