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:
@@ -1,4 +1,5 @@
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
app/src/main/res/drawable/ic_pref_logout.xml
Normal file
11
app/src/main/res/drawable/ic_pref_logout.xml
Normal 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>
|
||||
@@ -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<User>
|
||||
suspend fun signOut(): Result<Unit>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user