From 91a9521f3ef382d1b4328831b8ce87d26b72f221 Mon Sep 17 00:00:00 2001 From: beetzung Date: Sun, 1 Mar 2026 21:15:09 +0100 Subject: [PATCH] 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 --- app/build.gradle.kts | 5 ++ {sdk => app}/google-services.json | 15 +++- .../db3/airmq/features/login/LoginScreen.kt | 79 +++++++++++++++++ .../features/login/LoginScreenContract.kt | 3 + .../airmq/features/login/LoginViewModel.kt | 33 ++++++- .../airmq/features/manage/ManageViewModel.kt | 85 +++++++++++-------- app/src/main/res/values/strings.xml | 1 - build.gradle.kts | 1 + gradle/libs.versions.toml | 8 ++ sdk/build.gradle.kts | 2 +- .../org/db3/airmq/sdk/auth/AuthService.kt | 7 +- .../org/db3/airmq/sdk/auth/AuthServiceImpl.kt | 60 +++++++++++++ .../db3/airmq/sdk/auth/model/AuthProvider.kt | 5 ++ .../airmq/sdk/auth/model/{Auth.kt => User.kt} | 2 +- .../kotlin/org/db3/airmq/sdk/di/SDKModule.kt | 11 +++ 15 files changed, 269 insertions(+), 48 deletions(-) rename {sdk => app}/google-services.json (78%) create mode 100644 sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthServiceImpl.kt create mode 100644 sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/AuthProvider.kt rename sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/{Auth.kt => User.kt} (89%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 448120b..91a00f6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 diff --git a/sdk/google-services.json b/app/google-services.json similarity index 78% rename from sdk/google-services.json rename to app/google-services.json index 49e2197..831b79b 100644 --- a/sdk/google-services.json +++ b/app/google-services.json @@ -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 } ] diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt index ccbbc81..17cdecd 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt @@ -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 = 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() { diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt index cb25111..038181a 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt @@ -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 diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt b/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt index 3703347..fd5bd85 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt @@ -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))) + } + } + } } diff --git a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageViewModel.kt b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageViewModel.kt index 241f8ce..377a04c 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageViewModel.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageViewModel.kt @@ -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 = _uiState.asStateFlow() private val _actions = MutableSharedFlow(extraBufferCapacity = 1) val actions: SharedFlow = _actions.asSharedFlow() + init { + refreshAuthState() + } + fun onEvent(event: Event) { when (event) { Event.SettingsClicked -> _actions.tryEmit(Action.OpenSettings) @@ -42,39 +48,44 @@ class ManageViewModel @Inject constructor( } } - private fun initialState(): State { - val mode = if (forceAnonymous) { - UserMode.ANONYMOUS - } else { - UserMode.AUTHORIZED - } - return when (mode) { - UserMode.ANONYMOUS -> 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( - userMode = UserMode.AUTHORIZED, - userName = appContext.getString(R.string.mock_user_name), - userEmail = appContext.getString(R.string.mock_user_email), - devicesLabel = appContext.getString(R.string.text_your_devices), - devices = listOf( - DeviceItem( - id = "device-1", - name = appContext.getString(R.string.mock_device_name_42), - status = appContext.getString(R.string.map_status_online), - hasLocation = true - ), - DeviceItem( - id = "device-2", - name = appContext.getString(R.string.mock_device_name_17), - status = appContext.getString(R.string.map_status_offline), - hasLocation = false - ) - ) - ) + private fun initialState(): State = anonymousState() + + private fun refreshAuthState() { + viewModelScope.launch { + val session = authService.getCurrentSession() + _uiState.value = if (session?.isAuthenticated == true) { + authorizedState(session) + } else { + anonymousState() + } } } + + 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) + ) + + private fun authorizedState(user: User): State = State( + userMode = UserMode.AUTHORIZED, + userName = user.displayName ?: appContext.getString(R.string.text_anonymous_user), + userEmail = user.email ?: "", + devicesLabel = appContext.getString(R.string.text_your_devices), + devices = listOf( + DeviceItem( + id = "device-1", + name = appContext.getString(R.string.mock_device_name_42), + status = appContext.getString(R.string.map_status_online), + hasLocation = true + ), + DeviceItem( + id = "device-2", + name = appContext.getString(R.string.mock_device_name_17), + status = appContext.getString(R.string.map_status_offline), + hasLocation = false + ) + ) + ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57cebe1..96f9506 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -142,7 +142,6 @@ Copied Device requires registration - 21688843933-n04t4s1h7rjad12tdj1pc9j6kajgh2ka.apps.googleusercontent.com 356744979027664 fb356744979027664 Air quality diff --git a/build.gradle.kts b/build.gradle.kts index 59b3276..0572ded 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b94fec8..ac55d23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"} diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index 700d970..3fc883e 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -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) diff --git a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthService.kt b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthService.kt index 4723178..b1e5941 100644 --- a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthService.kt +++ b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthService.kt @@ -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 + suspend fun signIn(provider: AuthProvider, token: String): Result suspend fun signOut(): Result } diff --git a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthServiceImpl.kt b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthServiceImpl.kt new file mode 100644 index 0000000..37b268d --- /dev/null +++ b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthServiceImpl.kt @@ -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 = runCatching { + require(token.isNotBlank()) { "ID token is required for Google sign-in." } + when (provider) { + AuthProvider.GOOGLE -> signInWithGoogle(token) + } + } + + override suspend fun signOut(): Result = 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 Task.awaitResult(): T = suspendCancellableCoroutine { continuation -> + addOnSuccessListener { result -> + continuation.resume(result) + } + addOnFailureListener { exception -> + continuation.resumeWithException(exception) + } + addOnCanceledListener { + continuation.cancel() + } +} diff --git a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/AuthProvider.kt b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/AuthProvider.kt new file mode 100644 index 0000000..0598d63 --- /dev/null +++ b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/AuthProvider.kt @@ -0,0 +1,5 @@ +package org.db3.airmq.sdk.auth.model + +enum class AuthProvider { + GOOGLE +} diff --git a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/Auth.kt b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/User.kt similarity index 89% rename from sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/Auth.kt rename to sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/User.kt index 3d1ff5c..0c62a75 100644 --- a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/Auth.kt +++ b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/model/User.kt @@ -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?, diff --git a/sdk/src/main/kotlin/org/db3/airmq/sdk/di/SDKModule.kt b/sdk/src/main/kotlin/org/db3/airmq/sdk/di/SDKModule.kt index 4b265d5..0dae910 100644 --- a/sdk/src/main/kotlin/org/db3/airmq/sdk/di/SDKModule.kt +++ b/sdk/src/main/kotlin/org/db3/airmq/sdk/di/SDKModule.kt @@ -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