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
This commit is contained in:
2026-03-01 21:40:58 +01:00
parent 91a9521f3e
commit 28ad63fb4a
10 changed files with 79 additions and 71 deletions

View File

@@ -392,7 +392,9 @@ private suspend fun launchGoogleSignIn(context: Context): Result<String> = 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()

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<State> = _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)
)
)
}
}
}
}

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333"
android:alpha="0.6">
<path
android:fillColor="@android:color/white"
android:pathData="M10.79,16.29c0.39,0.39 1.02,0.39 1.41,0l3.59,-3.59c0.39,-0.39 0.39,-1.02 0,-1.41L12.2,7.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L12.67,11H4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h8.67l-1.88,1.88c-0.39,0.39 -0.38,1.03 0,1.41zM19,3H5c-1.11,0 -2,0.9 -2,2v3c0,0.55 0.45,1 1,1s1,-0.45 1,-1V6c0,-0.55 0.45,-1 1,-1h12c0.55,0 1,0.45 1,1v12c0,0.55 -0.45,1 -1,1H6c-0.55,0 -1,-0.45 -1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v3c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>