From 28ad63fb4a10e2c668773e271f21e05f35c715fb Mon Sep 17 00:00:00 2001 From: beetzung Date: Sun, 1 Mar 2026 21:40:58 +0100 Subject: [PATCH] Wire Firebase auth state into settings/logout and refresh Google sign-in config. This updates the settings account section to show/logout based on authenticated user state, refines auth service naming, and aligns app signing/Firebase config changes needed for successful Google authentication. Made-with: Cursor --- app/build.gradle.kts | 1 + app/google-services.json | 43 ++-------------- .../db3/airmq/features/login/LoginScreen.kt | 4 +- .../airmq/features/manage/ManageViewModel.kt | 2 +- .../airmq/features/settings/SettingsScreen.kt | 49 ++++++++++++++----- .../settings/SettingsScreenContract.kt | 8 +-- .../features/settings/SettingsViewModel.kt | 28 ++++++++--- app/src/main/res/drawable/ic_pref_logout.xml | 11 +++++ .../org/db3/airmq/sdk/auth/AuthService.kt | 2 +- .../org/db3/airmq/sdk/auth/AuthServiceImpl.kt | 2 +- 10 files changed, 79 insertions(+), 71 deletions(-) create mode 100644 app/src/main/res/drawable/ic_pref_logout.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 91a00f6..afeb048 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,5 @@ import com.android.build.api.dsl.ApplicationExtension +import java.util.Properties plugins { alias(libs.plugins.android.application) diff --git a/app/google-services.json b/app/google-services.json index 831b79b..ac0f8fd 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -14,48 +14,11 @@ }, "oauth_client": [ { - "client_id": "223884730019-0ddlndpmfesrt1vk5vdsqgeudkmoanls.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCcgdvdp8xec2dKUqlQHl9peQAyOWRVga4" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "223884730019-0ddlndpmfesrt1vk5vdsqgeudkmoanls.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:223884730019:android:1345a64a13459de7562916", - "android_client_info": { - "package_name": "org.db3.airmq.debug" - } - }, - "oauth_client": [ - { - "client_id": "223884730019-0cobqn6haoga9n5o60lqcke9uglqihea.apps.googleusercontent.com", + "client_id": "223884730019-nrh7lbjumja79jlci98u54jjc9utc988.apps.googleusercontent.com", "client_type": 1, "android_info": { - "package_name": "org.db3.airmq.debug", - "certificate_hash": "38547bbc27af278e820c78fc012227a40f64505f" - } - }, - { - "client_id": "223884730019-p44o1v2t3g6v0kmopsa65f0umf60ksif.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "org.db3.airmq.debug", - "certificate_hash": "195356c67cabf18c9ab0a8ccfcec45c3346b6b6c" + "package_name": "org.db3.airmq", + "certificate_hash": "d9df75253752259b83100ffb1c809615b9216be2" } }, { 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 17cdecd..40eea63 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 @@ -392,7 +392,9 @@ private suspend fun launchGoogleSignIn(context: Context): Result = runCa val activity = context.findActivity() val request = GetCredentialRequest.Builder() .addCredentialOption( - GetSignInWithGoogleOption.Builder(context.getString(R.string.default_web_client_id)) + GetGoogleIdOption.Builder() + .setServerClientId(context.getString(R.string.default_web_client_id)) + .setFilterByAuthorizedAccounts(false) .build() ) .build() 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 377a04c..2e76532 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 @@ -52,7 +52,7 @@ class ManageViewModel @Inject constructor( private fun refreshAuthState() { viewModelScope.launch { - val session = authService.getCurrentSession() + val session = authService.getUser() _uiState.value = if (session?.isAuthenticated == true) { authorizedState(session) } else { diff --git a/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreen.kt b/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreen.kt index 8835cf6..2a0d514 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreen.kt @@ -40,7 +40,6 @@ import org.db3.airmq.R import org.db3.airmq.features.settings.SettingsScreenContract.Action import org.db3.airmq.features.settings.SettingsScreenContract.Event import org.db3.airmq.features.settings.SettingsScreenContract.State -import org.db3.airmq.features.settings.SettingsScreenContract.UserMode import org.db3.airmq.ui.theme.AirMQTheme @Composable @@ -100,6 +99,22 @@ private fun SettingsScreenContent( .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(2.dp) ) { + if (uiState.isAuthorized) { + Text( + text = stringResource(id = R.string.pref_account_header), + style = MaterialTheme.typography.labelLarge, + color = Color(0xFF607D8B), + modifier = Modifier.padding(start = headerTextStart, top = 4.dp, bottom = 4.dp) + ) + PreferenceRow( + iconName = "ic_pref_logout", + iconFallbackRes = android.R.drawable.ic_lock_power_off, + title = stringResource(id = R.string.pref_logout_title), + iconSize = iconSize, + iconTextGap = iconTextGap, + onClick = { onEvent(Event.LogOutClicked) } + ) + } Text( text = stringResource(id = R.string.pref_application_header), style = MaterialTheme.typography.labelLarge, @@ -141,16 +156,6 @@ private fun SettingsScreenContent( iconTextGap = iconTextGap, onClick = { onEvent(Event.AboutClicked) } ) - if (uiState.userMode == UserMode.AUTHORIZED) { - PreferenceRow( - iconName = "ic_pref_debug", - iconFallbackRes = android.R.drawable.ic_lock_power_off, - title = stringResource(id = R.string.pref_logout_title), - iconSize = iconSize, - iconTextGap = iconTextGap, - onClick = { onEvent(Event.LogOutClicked) } - ) - } } } } @@ -252,7 +257,7 @@ private fun SettingsScreenAnonymousPreview() { AirMQTheme { SettingsScreenContent( uiState = State( - userMode = UserMode.ANONYMOUS, + isAuthorized = false, city = "Minsk", deviceStatusNotificationsEnabled = true, offlineDevicesVisible = false, @@ -270,7 +275,25 @@ private fun SettingsScreenAdvancedPreview() { AirMQTheme { SettingsScreenContent( uiState = State( - userMode = UserMode.ANONYMOUS, + isAuthorized = false, + city = "Minsk", + deviceStatusNotificationsEnabled = true, + offlineDevicesVisible = true, + advancedEnabled = true, + isLoading = false + ), + onEvent = {} + ) + } +} + +@Preview(name = "Settings Advanced", showBackground = true, showSystemUi = true) +@Composable +private fun SettingsScreenAuthorizedPreview() { + AirMQTheme { + SettingsScreenContent( + uiState = State( + isAuthorized = true, city = "Minsk", deviceStatusNotificationsEnabled = true, offlineDevicesVisible = true, diff --git a/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreenContract.kt b/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreenContract.kt index 8aa4957..3db2014 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreenContract.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsScreenContract.kt @@ -1,14 +1,8 @@ package org.db3.airmq.features.settings object SettingsScreenContract { - - enum class UserMode { - ANONYMOUS, - AUTHORIZED - } - data class State( - val userMode: UserMode = UserMode.ANONYMOUS, + val isAuthorized: Boolean = false, val city: String = "", val deviceStatusNotificationsEnabled: Boolean = true, val offlineDevicesVisible: Boolean = false, diff --git a/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsViewModel.kt b/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsViewModel.kt index 86100d0..886f1d9 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsViewModel.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/settings/SettingsViewModel.kt @@ -18,18 +18,16 @@ import org.db3.airmq.R import org.db3.airmq.features.settings.SettingsScreenContract.Action import org.db3.airmq.features.settings.SettingsScreenContract.Event import org.db3.airmq.features.settings.SettingsScreenContract.State -import org.db3.airmq.features.settings.SettingsScreenContract.UserMode +import org.db3.airmq.sdk.auth.AuthService import org.db3.airmq.sdk.settings.SettingsService @HiltViewModel class SettingsViewModel @Inject constructor( @ApplicationContext private val appContext: Context, - private val settingsService: SettingsService + private val settingsService: SettingsService, + private val authService: AuthService ) : ViewModel() { - // Temporary migration stub: keep screen in anonymous mode. - private val forceAnonymous = true - private val _uiState = MutableStateFlow(State()) val uiState: StateFlow = _uiState.asStateFlow() @@ -45,7 +43,7 @@ class SettingsViewModel @Inject constructor( Event.CityClicked -> _actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon))) Event.AboutClicked -> _actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon))) Event.DebugClicked -> _actions.tryEmit(Action.OpenDebug) - Event.LogOutClicked -> _actions.tryEmit(Action.LogOutToManage) + Event.LogOutClicked -> logOut() is Event.DeviceStatusNotificationsChanged -> { updateToggle( toggle = { settingsService.setDeviceStatusNotificationsEnabled(event.enabled) }, @@ -69,12 +67,13 @@ class SettingsViewModel @Inject constructor( private fun loadSettings() { viewModelScope.launch(Dispatchers.IO) { + val session = authService.getUser() val city = settingsService.getCity() ?: appContext.getString(R.string.city_minsk) val deviceStatus = settingsService.getDeviceStatusNotificationsEnabled() val offlineVisible = settingsService.getOfflineDevicesVisible() val advanced = settingsService.getAdvancedEnabled() _uiState.value = _uiState.value.copy( - userMode = if (forceAnonymous) UserMode.ANONYMOUS else UserMode.AUTHORIZED, + isAuthorized = session?.isAuthenticated == true, city = city, deviceStatusNotificationsEnabled = deviceStatus, offlineDevicesVisible = offlineVisible, @@ -103,4 +102,19 @@ class SettingsViewModel @Inject constructor( } } } + + private fun logOut() { + viewModelScope.launch(Dispatchers.IO) { + val result = authService.signOut() + if (result.isSuccess) { + _actions.tryEmit(Action.LogOutToManage) + } else { + _actions.tryEmit( + Action.ShowMessage( + result.exceptionOrNull()?.message ?: appContext.getString(R.string.toast_error) + ) + ) + } + } + } } diff --git a/app/src/main/res/drawable/ic_pref_logout.xml b/app/src/main/res/drawable/ic_pref_logout.xml new file mode 100644 index 0000000..323801c --- /dev/null +++ b/app/src/main/res/drawable/ic_pref_logout.xml @@ -0,0 +1,11 @@ + + + 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 b1e5941..df1368f 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 @@ -4,7 +4,7 @@ import org.db3.airmq.sdk.auth.model.AuthProvider import org.db3.airmq.sdk.auth.model.User interface AuthService { - suspend fun getCurrentSession(): User? + suspend fun getUser(): User? suspend fun isAuthenticated(): Boolean 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 index 37b268d..c5c303f 100644 --- a/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthServiceImpl.kt +++ b/sdk/src/main/kotlin/org/db3/airmq/sdk/auth/AuthServiceImpl.kt @@ -17,7 +17,7 @@ class FirebaseAuthService @Inject constructor( private val firebaseAuth: FirebaseAuth ) : AuthService { - override suspend fun getCurrentSession(): User? = firebaseAuth.currentUser?.toUser() + override suspend fun getUser(): User? = firebaseAuth.currentUser?.toUser() override suspend fun isAuthenticated(): Boolean = firebaseAuth.currentUser != null