Implement Firebase Google sign-in with enum-based auth contracts.
Refactor AuthService to use AuthProvider and User, add Firebase-backed auth wiring for login/manage flows, and fix app-level Google services configuration so Credential Manager sign-in works reliably. Made-with: Cursor
This commit is contained in:
@@ -5,6 +5,8 @@ plugins {
|
||||
alias(libs.plugins.hilt.android)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.google.services)
|
||||
alias(libs.plugins.firebase.crashlytics)
|
||||
}
|
||||
|
||||
fun gitCommitCount(): String {
|
||||
@@ -81,6 +83,9 @@ dependencies {
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.hilt.android)
|
||||
implementation(libs.androidx.hilt.navigation.compose)
|
||||
implementation(libs.androidx.credentials)
|
||||
implementation(libs.androidx.credentials.play.services.auth)
|
||||
implementation(libs.googleid)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
// Tests
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
"package_name": "org.db3.airmq"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "223884730019-0ddlndpmfesrt1vk5vdsqgeudkmoanls.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCcgdvdp8xec2dKUqlQHl9peQAyOWRVga4"
|
||||
@@ -22,7 +27,7 @@
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "223884730019-1hkbacov3tem4snsih7vs218lt9ppvde.apps.googleusercontent.com",
|
||||
"client_id": "223884730019-0ddlndpmfesrt1vk5vdsqgeudkmoanls.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
@@ -52,6 +57,10 @@
|
||||
"package_name": "org.db3.airmq.debug",
|
||||
"certificate_hash": "195356c67cabf18c9ab0a8ccfcec45c3346b6b6c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "223884730019-0ddlndpmfesrt1vk5vdsqgeudkmoanls.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
@@ -63,7 +72,7 @@
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "223884730019-1hkbacov3tem4snsih7vs218lt9ppvde.apps.googleusercontent.com",
|
||||
"client_id": "223884730019-0ddlndpmfesrt1vk5vdsqgeudkmoanls.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,18 @@
|
||||
package org.db3.airmq.features.login
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.CustomCredential
|
||||
import androidx.credentials.GetCredentialRequest
|
||||
import androidx.credentials.exceptions.GetCredentialCancellationException
|
||||
import androidx.credentials.exceptions.GetCredentialException
|
||||
import androidx.credentials.exceptions.GetCredentialInterruptedException
|
||||
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
|
||||
import androidx.credentials.exceptions.NoCredentialException
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
@@ -53,6 +65,10 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
||||
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.login.LoginScreenContract.Action
|
||||
@@ -63,6 +79,7 @@ import org.db3.airmq.ui.theme.AirMQTheme
|
||||
private val LegacyLoginGradientStart = Color(0xFF449CF5)
|
||||
private val LegacyLoginGradientEnd = Color(0xFF5CE4BB)
|
||||
private val LegacyFacebookBlue = Color(0xFF3B5998)
|
||||
private const val LOGIN_TAG = "LoginScreen"
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
@@ -77,6 +94,15 @@ fun LoginScreen(
|
||||
viewModel.actions.collectLatest { action ->
|
||||
when (action) {
|
||||
Action.OpenManage -> onLogInToManage()
|
||||
Action.LaunchGoogleSignIn -> {
|
||||
val result = launchGoogleSignIn(context)
|
||||
result.fold(
|
||||
onSuccess = { viewModel.onEvent(Event.GoogleTokenReceived(it)) },
|
||||
onFailure = { error ->
|
||||
viewModel.onEvent(Event.GoogleSignInFailed(error.message))
|
||||
}
|
||||
)
|
||||
}
|
||||
Action.ShowContinueAnonymousDialog -> showContinueAnonymousDialog = true
|
||||
Action.OpenPrivacyPolicy -> {
|
||||
Toast.makeText(context, context.getString(R.string.coming_soon), Toast.LENGTH_SHORT).show()
|
||||
@@ -362,6 +388,59 @@ private fun PrivacyAndTermsFooter(onEvent: (Event) -> Unit) {
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun launchGoogleSignIn(context: Context): Result<String> = runCatching {
|
||||
val activity = context.findActivity()
|
||||
val request = GetCredentialRequest.Builder()
|
||||
.addCredentialOption(
|
||||
GetSignInWithGoogleOption.Builder(context.getString(R.string.default_web_client_id))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
val response = CredentialManager.create(context).getCredential(
|
||||
context = activity,
|
||||
request = request
|
||||
)
|
||||
val credential = response.credential
|
||||
if (credential is CustomCredential &&
|
||||
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
|
||||
) {
|
||||
GoogleIdTokenCredential.createFrom(credential.data).idToken
|
||||
} else {
|
||||
error("Unsupported credential type for Google sign-in.")
|
||||
}
|
||||
}.recoverCatching {
|
||||
when (it) {
|
||||
is GetCredentialException -> {
|
||||
logGoogleSignInError(it)
|
||||
throw IllegalStateException(context.getString(R.string.toast_oauth_failed), it)
|
||||
}
|
||||
is GoogleIdTokenParsingException -> {
|
||||
Log.e(LOGIN_TAG, "Google ID token parsing failed", it)
|
||||
throw IllegalStateException(context.getString(R.string.toast_oauth_failed), it)
|
||||
}
|
||||
else -> throw it
|
||||
}
|
||||
}
|
||||
|
||||
private fun logGoogleSignInError(exception: GetCredentialException) {
|
||||
val message = when (exception) {
|
||||
is GetCredentialCancellationException -> "Google sign-in cancelled by user"
|
||||
is NoCredentialException -> "No Google credential available on device"
|
||||
is GetCredentialProviderConfigurationException -> "Credential provider is not configured correctly"
|
||||
is GetCredentialInterruptedException -> "Credential flow interrupted; try again"
|
||||
else -> "CredentialManager returned an unknown sign-in error"
|
||||
}
|
||||
Log.e(LOGIN_TAG, message, exception)
|
||||
}
|
||||
|
||||
private tailrec fun Context.findActivity(): Activity {
|
||||
return when (this) {
|
||||
is Activity -> this
|
||||
is ContextWrapper -> baseContext.findActivity()
|
||||
else -> error("Unable to find Activity context for Google sign-in.")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LoginScreenPreview() {
|
||||
|
||||
@@ -8,6 +8,7 @@ object LoginScreenContract {
|
||||
|
||||
sealed interface Action {
|
||||
data object OpenManage : Action
|
||||
data object LaunchGoogleSignIn : Action
|
||||
data object ShowContinueAnonymousDialog : Action
|
||||
data object OpenPrivacyPolicy : Action
|
||||
data object OpenTermsAndConditions : Action
|
||||
@@ -16,6 +17,8 @@ object LoginScreenContract {
|
||||
|
||||
sealed interface Event {
|
||||
data object GoogleClicked : Event
|
||||
data class GoogleTokenReceived(val idToken: String) : Event
|
||||
data class GoogleSignInFailed(val message: String? = null) : Event
|
||||
data object FacebookClicked : Event
|
||||
data object ContinueAnonymousClicked : Event
|
||||
data object ContinueAnonymousConfirmed : Event
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.db3.airmq.features.login
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
@@ -15,10 +17,13 @@ import org.db3.airmq.R
|
||||
import org.db3.airmq.features.login.LoginScreenContract.Action
|
||||
import org.db3.airmq.features.login.LoginScreenContract.Event
|
||||
import org.db3.airmq.features.login.LoginScreenContract.State
|
||||
import org.db3.airmq.sdk.auth.AuthService
|
||||
import org.db3.airmq.sdk.auth.model.AuthProvider
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(
|
||||
@ApplicationContext private val appContext: Context
|
||||
@ApplicationContext private val appContext: Context,
|
||||
private val authService: AuthService
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(State())
|
||||
@@ -30,7 +35,19 @@ class LoginViewModel @Inject constructor(
|
||||
fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
Event.GoogleClicked -> {
|
||||
_actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
||||
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||
_actions.tryEmit(Action.LaunchGoogleSignIn)
|
||||
}
|
||||
is Event.GoogleTokenReceived -> {
|
||||
signInWithGoogle(event.idToken)
|
||||
}
|
||||
is Event.GoogleSignInFailed -> {
|
||||
_uiState.value = _uiState.value.copy(isLoading = false)
|
||||
_actions.tryEmit(
|
||||
Action.ShowMessage(
|
||||
event.message ?: appContext.getString(R.string.toast_oauth_failed)
|
||||
)
|
||||
)
|
||||
}
|
||||
Event.FacebookClicked -> {
|
||||
_actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
||||
@@ -50,4 +67,16 @@ class LoginViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun signInWithGoogle(idToken: String) {
|
||||
viewModelScope.launch {
|
||||
val signInResult = authService.signIn(provider = AuthProvider.GOOGLE, token = idToken)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false)
|
||||
if (signInResult.isSuccess) {
|
||||
_actions.tryEmit(Action.OpenManage)
|
||||
} else {
|
||||
_actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.toast_oauth_failed)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.db3.airmq.features.manage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
@@ -17,21 +19,25 @@ import org.db3.airmq.features.manage.ManageScreenContract.DeviceItem
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.Event
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.State
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.UserMode
|
||||
import org.db3.airmq.sdk.auth.AuthService
|
||||
import org.db3.airmq.sdk.auth.model.User
|
||||
|
||||
@HiltViewModel
|
||||
class ManageViewModel @Inject constructor(
|
||||
@ApplicationContext private val appContext: Context
|
||||
@ApplicationContext private val appContext: Context,
|
||||
private val authService: AuthService
|
||||
) : ViewModel() {
|
||||
|
||||
// Temporary migration stub: keep screen in anonymous mode.
|
||||
private val forceAnonymous = true
|
||||
|
||||
private val _uiState = MutableStateFlow(initialState())
|
||||
val uiState: StateFlow<State> = _uiState.asStateFlow()
|
||||
|
||||
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||
|
||||
init {
|
||||
refreshAuthState()
|
||||
}
|
||||
|
||||
fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
Event.SettingsClicked -> _actions.tryEmit(Action.OpenSettings)
|
||||
@@ -42,23 +48,30 @@ class ManageViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialState(): State {
|
||||
val mode = if (forceAnonymous) {
|
||||
UserMode.ANONYMOUS
|
||||
private fun initialState(): State = anonymousState()
|
||||
|
||||
private fun refreshAuthState() {
|
||||
viewModelScope.launch {
|
||||
val session = authService.getCurrentSession()
|
||||
_uiState.value = if (session?.isAuthenticated == true) {
|
||||
authorizedState(session)
|
||||
} else {
|
||||
UserMode.AUTHORIZED
|
||||
anonymousState()
|
||||
}
|
||||
return when (mode) {
|
||||
UserMode.ANONYMOUS -> State(
|
||||
}
|
||||
}
|
||||
|
||||
private fun anonymousState(): State = State(
|
||||
userMode = UserMode.ANONYMOUS,
|
||||
userName = appContext.getString(R.string.text_anonymous_user),
|
||||
userEmail = appContext.getString(R.string.text_please_sign_in),
|
||||
devicesLabel = appContext.getString(R.string.text_sign_in_small)
|
||||
)
|
||||
UserMode.AUTHORIZED -> State(
|
||||
|
||||
private fun authorizedState(user: User): State = State(
|
||||
userMode = UserMode.AUTHORIZED,
|
||||
userName = appContext.getString(R.string.mock_user_name),
|
||||
userEmail = appContext.getString(R.string.mock_user_email),
|
||||
userName = user.displayName ?: appContext.getString(R.string.text_anonymous_user),
|
||||
userEmail = user.email ?: "",
|
||||
devicesLabel = appContext.getString(R.string.text_your_devices),
|
||||
devices = listOf(
|
||||
DeviceItem(
|
||||
@@ -76,5 +89,3 @@ class ManageViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@
|
||||
<string name="toast_copied">Copied</string>
|
||||
<string name="snackbar_not_registered">Device requires registration</string>
|
||||
|
||||
<string name="oauth_token" translatable="false" tools:ignore="Typos">21688843933-n04t4s1h7rjad12tdj1pc9j6kajgh2ka.apps.googleusercontent.com</string>
|
||||
<string name="facebook_app_id" translatable="false">356744979027664</string>
|
||||
<string name="fb_login_protocol_scheme" translatable="false">fb356744979027664</string>
|
||||
<string name="text_air_quality">Air quality</string>
|
||||
|
||||
@@ -8,4 +8,5 @@ plugins {
|
||||
alias(libs.plugins.apollo) apply false
|
||||
alias(libs.plugins.hilt.android) apply false
|
||||
alias(libs.plugins.google.services) apply false
|
||||
alias(libs.plugins.firebase.crashlytics) apply false
|
||||
}
|
||||
@@ -11,6 +11,7 @@ kotlin = "2.0.21"
|
||||
composeBom = "2024.09.00"
|
||||
navigationCompose = "2.9.3"
|
||||
firebaseBom = "34.4.0"
|
||||
firebaseCrashlyticsPlugin = "3.0.6"
|
||||
mapsCompose = "6.12.1"
|
||||
osmdroid = "6.1.20"
|
||||
apollo = "5.0.0-alpha.4"
|
||||
@@ -19,6 +20,8 @@ hiltNavigationCompose = "1.2.0"
|
||||
okhttpLogging = "4.12.0"
|
||||
ksp = "2.0.21-1.0.27"
|
||||
google-services = "4.4.4"
|
||||
androidxCredentials = "1.5.0"
|
||||
googleid = "1.1.1"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -43,11 +46,15 @@ firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.r
|
||||
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
|
||||
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" }
|
||||
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }
|
||||
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
|
||||
osmdroid-android = { group = "org.osmdroid", name = "osmdroid-android", version.ref = "osmdroid" }
|
||||
apollo-runtime = { group = "com.apollographql.apollo", name = "apollo-runtime", version.ref = "apollo" }
|
||||
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" }
|
||||
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
|
||||
androidx-credentials = { group = "androidx.credentials", name = "credentials", version.ref = "androidxCredentials" }
|
||||
androidx-credentials-play-services-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" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
@@ -58,4 +65,5 @@ apollo = { id = "com.apollographql.apollo", version.ref = "apollo" }
|
||||
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
|
||||
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsPlugin"}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.apollo)
|
||||
alias(libs.plugins.google.services)
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -50,6 +49,7 @@ dependencies {
|
||||
// Firebase
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.analytics)
|
||||
implementation(libs.firebase.auth)
|
||||
implementation(libs.firebase.crashlytics)
|
||||
implementation(libs.firebase.messaging)
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package org.db3.airmq.sdk.auth
|
||||
|
||||
import org.db3.airmq.sdk.auth.model.Auth
|
||||
import org.db3.airmq.sdk.auth.model.AuthProvider
|
||||
import org.db3.airmq.sdk.auth.model.User
|
||||
|
||||
interface AuthService {
|
||||
suspend fun getCurrentSession(): Auth?
|
||||
suspend fun getCurrentSession(): User?
|
||||
suspend fun isAuthenticated(): Boolean
|
||||
suspend fun signIn(provider: String, token: String? = null): Result<Auth>
|
||||
suspend fun signIn(provider: AuthProvider, token: String): Result<User>
|
||||
suspend fun signOut(): Result<Unit>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.db3.airmq.sdk.auth
|
||||
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import com.google.firebase.auth.FirebaseUser
|
||||
import com.google.firebase.auth.GoogleAuthProvider
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.db3.airmq.sdk.auth.model.AuthProvider
|
||||
import org.db3.airmq.sdk.auth.model.User
|
||||
|
||||
@Singleton
|
||||
class FirebaseAuthService @Inject constructor(
|
||||
private val firebaseAuth: FirebaseAuth
|
||||
) : AuthService {
|
||||
|
||||
override suspend fun getCurrentSession(): User? = firebaseAuth.currentUser?.toUser()
|
||||
|
||||
override suspend fun isAuthenticated(): Boolean = firebaseAuth.currentUser != null
|
||||
|
||||
override suspend fun signIn(provider: AuthProvider, token: String): Result<User> = runCatching {
|
||||
require(token.isNotBlank()) { "ID token is required for Google sign-in." }
|
||||
when (provider) {
|
||||
AuthProvider.GOOGLE -> signInWithGoogle(token)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun signOut(): Result<Unit> = runCatching {
|
||||
firebaseAuth.signOut()
|
||||
}
|
||||
|
||||
private suspend fun signInWithGoogle(token: String): User {
|
||||
val credential = GoogleAuthProvider.getCredential(token, null)
|
||||
val authResult = firebaseAuth.signInWithCredential(credential).awaitResult()
|
||||
val firebaseUser = authResult.user ?: error("Google sign-in completed without a Firebase user.")
|
||||
return firebaseUser.toUser()
|
||||
}
|
||||
|
||||
private fun FirebaseUser.toUser(): User = User(
|
||||
userId = uid,
|
||||
email = email,
|
||||
displayName = displayName,
|
||||
isAuthenticated = true
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun <T> Task<T>.awaitResult(): T = suspendCancellableCoroutine { continuation ->
|
||||
addOnSuccessListener { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
addOnFailureListener { exception ->
|
||||
continuation.resumeWithException(exception)
|
||||
}
|
||||
addOnCanceledListener {
|
||||
continuation.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.db3.airmq.sdk.auth.model
|
||||
|
||||
enum class AuthProvider {
|
||||
GOOGLE
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.db3.airmq.sdk.auth.model
|
||||
|
||||
data class Auth(
|
||||
data class User(
|
||||
val userId: String,
|
||||
val email: String?,
|
||||
val displayName: String?,
|
||||
@@ -1,12 +1,15 @@
|
||||
package org.db3.airmq.sdk.di
|
||||
|
||||
import com.apollographql.apollo.ApolloClient
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import org.db3.airmq.sdk.auth.AuthService
|
||||
import org.db3.airmq.sdk.auth.FirebaseAuthService
|
||||
import org.db3.airmq.sdk.map.MapServiceImpl
|
||||
import org.db3.airmq.sdk.map.MapService
|
||||
import org.db3.airmq.sdk.settings.SettingsService
|
||||
@@ -26,11 +29,19 @@ object SDKModule {
|
||||
.addInterceptor(ApolloLoggingInterceptor())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance()
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class SDKBindModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindAuthService(impl: FirebaseAuthService): AuthService
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMapService(impl: MapServiceImpl): MapService
|
||||
|
||||
Reference in New Issue
Block a user