From 8bf076697e77c24e473c9983d6b797cd5fc41966 Mon Sep 17 00:00:00 2001 From: beetzung Date: Mon, 2 Mar 2026 03:16:46 +0100 Subject: [PATCH] Refine social auth UI and simplify manage auth state. Extract Google sign-in into a dedicated manager with explicit cancel handling, update shared social button variants, and switch manage UI state from enum modes to a direct authorization flag. Made-with: Cursor --- .../db3/airmq/features/common/AirMQButtons.kt | 146 ++++++++++++++- .../features/dashboard/DashboardScreen.kt | 4 - .../features/login/GoogleSignInManager.kt | 82 +++++++++ .../db3/airmq/features/login/LoginScreen.kt | 172 +++--------------- .../features/login/LoginScreenContract.kt | 1 + .../airmq/features/login/LoginViewModel.kt | 3 + .../db3/airmq/features/manage/ManageScreen.kt | 18 +- .../features/manage/ManageScreenContract.kt | 8 +- .../airmq/features/manage/ManageViewModel.kt | 5 +- 9 files changed, 257 insertions(+), 182 deletions(-) create mode 100644 app/src/main/kotlin/org/db3/airmq/features/login/GoogleSignInManager.kt diff --git a/app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt b/app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt index 90d5064..47c43dc 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt @@ -9,9 +9,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -54,7 +53,7 @@ enum class AirMQButtonStyle { } private val LegacyButtonShape = RoundedCornerShape(18.dp) -private val LegacyButtonHeight = 48.dp +private val LegacyButtonHeight = 36.dp private val LegacyDisabledContainer = Color(0xFFE0E0E0) private val LegacyDisabledContent = Color(0x61000000) @@ -99,6 +98,69 @@ fun AirMQButton( } } +@Composable +fun AirMQSocialButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + leadingIconRes: Int? = null, + iconTint: Color = Color.Unspecified, + containerColor: Color = Color.White, + contentColor: Color = Color(0xFF202124) +) { + Button( + onClick = onClick, + enabled = enabled, + shape = LegacyButtonShape, + modifier = modifier.height(LegacyButtonHeight), + colors = ButtonDefaults.buttonColors( + containerColor = containerColor, + contentColor = contentColor, + disabledContainerColor = LegacyDisabledContainer, + disabledContentColor = LegacyDisabledContent + ) + ) { + AirMQButtonLabel( + text = text, + leadingIconRes = leadingIconRes, + iconTint = if (enabled) iconTint else LegacyDisabledContent, + textColor = if (enabled) contentColor else LegacyDisabledContent + ) + } +} + +@Composable +fun AirMQOutlinedLightButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + leadingIconRes: Int? = null +) { + OutlinedButton( + onClick = onClick, + enabled = enabled, + shape = LegacyButtonShape, + border = BorderStroke( + width = 1.dp, + color = if (enabled) Color.White.copy(alpha = 0.55f) else LegacyDisabledContent + ), + modifier = modifier.height(LegacyButtonHeight), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = Color.White, + disabledContentColor = LegacyDisabledContent + ) + ) { + AirMQButtonLabel( + text = text, + leadingIconRes = leadingIconRes, + iconTint = if (enabled) Color.White else LegacyDisabledContent, + textColor = if (enabled) Color.White else LegacyDisabledContent + ) + } +} + @Composable fun AirMQContainedButton( text: String, @@ -111,7 +173,7 @@ fun AirMQContainedButton( onClick = onClick, enabled = enabled, shape = LegacyButtonShape, - modifier = modifier.heightIn(min = LegacyButtonHeight), + modifier = modifier.height(LegacyButtonHeight), colors = ButtonDefaults.buttonColors( containerColor = LegacyButtonContained, contentColor = LegacyButtonOnContained, @@ -143,7 +205,7 @@ fun AirMQOutlinedButton( width = 1.dp, color = if (enabled) LegacyOutlineLight else LegacyDisabledContent ), - modifier = modifier.heightIn(min = LegacyButtonHeight), + modifier = modifier.height(LegacyButtonHeight), colors = ButtonDefaults.outlinedButtonColors( contentColor = LegacyButtonOnOutlined, disabledContentColor = LegacyDisabledContent @@ -169,7 +231,7 @@ fun AirMQTextButton( onClick = onClick, enabled = enabled, shape = LegacyButtonShape, - modifier = modifier.heightIn(min = LegacyButtonHeight), + modifier = modifier.height(LegacyButtonHeight), colors = ButtonDefaults.textButtonColors( contentColor = LegacyButtonOnText, disabledContentColor = LegacyDisabledContent @@ -205,7 +267,7 @@ fun AirMQGradientButton( shape = LegacyButtonShape, interactionSource = interactionSource, modifier = modifier - .heightIn(min = LegacyButtonHeight) + .height(LegacyButtonHeight) .clip(LegacyButtonShape), contentPadding = PaddingValues(0.dp), colors = ButtonDefaults.buttonColors( @@ -218,7 +280,7 @@ fun AirMQGradientButton( Box( modifier = Modifier .fillMaxWidth() - .heightIn(min = LegacyButtonHeight) + .height(LegacyButtonHeight) .clip(LegacyButtonShape) .background( brush = Brush.horizontalGradient( @@ -330,3 +392,71 @@ private fun AirMQButtonsPreviewGradientWithIcon() { } } } + +@Preview(name = "Buttons - Social", showBackground = true) +@Composable +private fun AirMQButtonsPreviewSocial() { + AirMQTheme { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + AirMQSocialButton( + text = "Sign in with Google", + leadingIconRes = R.drawable.ic_google, + iconTint = Color.Unspecified, + containerColor = Color.White, + contentColor = Color(0xFF202124), + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + AirMQSocialButton( + text = "Sign in with Facebook", + leadingIconRes = R.drawable.ic_facebook, + iconTint = Color.White, + containerColor = Color(0xFF3B5998), + contentColor = Color.White, + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + AirMQOutlinedLightButton( + text = "Continue anonymously", + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Preview(name = "Buttons - Social Icon Comparison", showBackground = true) +@Composable +private fun AirMQButtonsPreviewSocialIconComparison() { + AirMQTheme { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + AirMQSocialButton( + text = "Sign in with Google", + leadingIconRes = R.drawable.ic_google, + iconTint = Color.Unspecified, + containerColor = Color.White, + contentColor = Color(0xFF202124), + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + AirMQSocialButton( + text = "Sign in with Google", + leadingIconRes = null, + containerColor = Color.White, + contentColor = Color(0xFF202124), + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + } + } +} diff --git a/app/src/main/kotlin/org/db3/airmq/features/dashboard/DashboardScreen.kt b/app/src/main/kotlin/org/db3/airmq/features/dashboard/DashboardScreen.kt index 7425fb2..f0c4414 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/dashboard/DashboardScreen.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/dashboard/DashboardScreen.kt @@ -19,10 +19,6 @@ fun DashboardScreen( title = stringResource(id = R.string.title_dashboard), subtitle = stringResource(id = R.string.dashboard_subtitle), actions = listOf( - ScreenAction(stringResource(id = R.string.dashboard_open_map), onOpenMap), - ScreenAction(stringResource(id = R.string.dashboard_open_manage), onOpenManage), - ScreenAction(stringResource(id = R.string.dashboard_open_city), onOpenCity), - ScreenAction(stringResource(id = R.string.dashboard_open_device), onOpenDevice), ScreenAction(stringResource(id = R.string.dashboard_open_news), onOpenNews), ScreenAction(stringResource(id = R.string.manage_open_widget_constructor), onOpenWidgetConstructor) ) diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/GoogleSignInManager.kt b/app/src/main/kotlin/org/db3/airmq/features/login/GoogleSignInManager.kt new file mode 100644 index 0000000..d16b1ed --- /dev/null +++ b/app/src/main/kotlin/org/db3/airmq/features/login/GoogleSignInManager.kt @@ -0,0 +1,82 @@ +package org.db3.airmq.features.login + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.util.Log +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 com.google.android.libraries.identity.googleid.GetGoogleIdOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException +import org.db3.airmq.R + +sealed interface GoogleSignInResult { + data class Success(val idToken: String) : GoogleSignInResult + data object Cancelled : GoogleSignInResult + data class Error(val message: String) : GoogleSignInResult +} + +private const val GOOGLE_SIGN_IN_TAG = "GoogleSignIn" + +suspend fun launchGoogleSignIn(context: Context): GoogleSignInResult { + return try { + val activity = context.findActivity() + val request = GetCredentialRequest.Builder() + .addCredentialOption( + GetGoogleIdOption.Builder() + .setServerClientId(context.getString(R.string.default_web_client_id)) + .setFilterByAuthorizedAccounts(false) + .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 + ) { + GoogleSignInResult.Success(GoogleIdTokenCredential.createFrom(credential.data).idToken) + } else { + GoogleSignInResult.Error("Unsupported credential type for Google sign-in.") + } + } catch (error: GetCredentialCancellationException) { + logGoogleSignInError(error) + GoogleSignInResult.Cancelled + } catch (error: GetCredentialException) { + logGoogleSignInError(error) + GoogleSignInResult.Error(context.getString(R.string.toast_oauth_failed)) + } catch (error: GoogleIdTokenParsingException) { + Log.e(GOOGLE_SIGN_IN_TAG, "Google ID token parsing failed", error) + GoogleSignInResult.Error(context.getString(R.string.toast_oauth_failed)) + } catch (error: Throwable) { + GoogleSignInResult.Error(error.message ?: context.getString(R.string.toast_oauth_failed)) + } +} + +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(GOOGLE_SIGN_IN_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.") + } +} 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 40eea63..5a23ccc 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,21 +1,8 @@ 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 import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -25,13 +12,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -57,20 +40,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.text.ClickableText -import androidx.compose.material3.Icon 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.common.AirMQOutlinedLightButton +import org.db3.airmq.features.common.AirMQSocialButton import org.db3.airmq.features.login.LoginScreenContract.Action import org.db3.airmq.features.login.LoginScreenContract.Event import org.db3.airmq.features.login.LoginScreenContract.State @@ -79,7 +57,6 @@ 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( @@ -95,13 +72,11 @@ fun LoginScreen( 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)) - } - ) + when (val result = launchGoogleSignIn(context)) { + is GoogleSignInResult.Success -> viewModel.onEvent(Event.GoogleTokenReceived(result.idToken)) + GoogleSignInResult.Cancelled -> viewModel.onEvent(Event.GoogleSignInCancelled) + is GoogleSignInResult.Error -> viewModel.onEvent(Event.GoogleSignInFailed(result.message)) + } } Action.ShowContinueAnonymousDialog -> showContinueAnonymousDialog = true Action.OpenPrivacyPolicy -> { @@ -230,40 +205,31 @@ private fun LoginScreenContent( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - SocialSignInButton( + AirMQSocialButton( text = stringResource(id = R.string.button_sign_in_google), - iconRes = R.drawable.ic_google, + leadingIconRes = R.drawable.ic_google, iconTint = Color.Unspecified, - backgroundColor = Color.White, - textColor = Color(0xFF202124), + containerColor = Color.White, + contentColor = Color(0xFF202124), + modifier = Modifier.fillMaxWidth(), onClick = { onEvent(Event.GoogleClicked) } ) - SocialSignInButton( + AirMQSocialButton( text = stringResource(id = R.string.button_sign_in_facebook), - iconRes = R.drawable.ic_facebook, + leadingIconRes = R.drawable.ic_facebook, iconTint = Color.White, - backgroundColor = LegacyFacebookBlue, - textColor = Color.White, + containerColor = LegacyFacebookBlue, + contentColor = Color.White, + modifier = Modifier.fillMaxWidth(), onClick = { onEvent(Event.FacebookClicked) } ) - OutlinedButton( - onClick = { onEvent(Event.ContinueAnonymousClicked) }, - shape = RoundedCornerShape(18.dp), - colors = ButtonDefaults.outlinedButtonColors( - contentColor = Color.White - ), - border = BorderStroke(1.dp, Color.White.copy(alpha = 0.55f)), - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - ) { - Text( - text = stringResource(id = R.string.button_continue_anonym).uppercase(), - style = MaterialTheme.typography.labelLarge - ) - } + AirMQOutlinedLightButton( + text = stringResource(id = R.string.button_continue_anonym), + modifier = Modifier.fillMaxWidth(), + onClick = { onEvent(Event.ContinueAnonymousClicked) } + ) } Spacer(modifier = Modifier.height(24.dp)) @@ -275,45 +241,6 @@ private fun LoginScreenContent( } } -@Composable -private fun SocialSignInButton( - text: String, - iconRes: Int, - iconTint: Color, - backgroundColor: Color, - textColor: Color, - onClick: () -> Unit -) { - Button( - onClick = onClick, - shape = RoundedCornerShape(18.dp), - colors = ButtonDefaults.buttonColors( - containerColor = backgroundColor, - contentColor = textColor - ), - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxHeight() - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = null, - tint = iconTint, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.size(8.dp)) - Text( - text = text.uppercase(), - style = MaterialTheme.typography.labelLarge - ) - } - } -} - @Composable private fun PrivacyAndTermsFooter(onEvent: (Event) -> Unit) { val privacyPolicy = "Privacy Policy" @@ -388,61 +315,6 @@ private fun PrivacyAndTermsFooter(onEvent: (Event) -> Unit) { ) } -private suspend fun launchGoogleSignIn(context: Context): Result = runCatching { - val activity = context.findActivity() - val request = GetCredentialRequest.Builder() - .addCredentialOption( - GetGoogleIdOption.Builder() - .setServerClientId(context.getString(R.string.default_web_client_id)) - .setFilterByAuthorizedAccounts(false) - .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 038181a..7cb4c29 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 @@ -18,6 +18,7 @@ object LoginScreenContract { sealed interface Event { data object GoogleClicked : Event data class GoogleTokenReceived(val idToken: String) : Event + data object GoogleSignInCancelled : Event data class GoogleSignInFailed(val message: String? = null) : Event data object FacebookClicked : Event data object ContinueAnonymousClicked : 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 fd5bd85..69c6adf 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 @@ -49,6 +49,9 @@ class LoginViewModel @Inject constructor( ) ) } + Event.GoogleSignInCancelled -> { + _uiState.value = _uiState.value.copy(isLoading = false) + } Event.FacebookClicked -> { _actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon))) } diff --git a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt index 7b4130e..0ae8b58 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt @@ -47,7 +47,6 @@ import org.db3.airmq.features.manage.ManageScreenContract.Action 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.ui.theme.AirMQTheme import org.db3.airmq.ui.theme.LegacyNavGradientEnd import org.db3.airmq.ui.theme.LegacyNavGradientStart @@ -99,16 +98,16 @@ private fun ManageScreenContent( ProfileHeader( name = uiState.userName, email = uiState.userEmail, - isAnonymous = uiState.userMode == UserMode.ANONYMOUS, + isAnonymous = !uiState.isAuthorized, onSettingsClick = { onEvent(Event.SettingsClicked) } ) - when (uiState.userMode) { - UserMode.ANONYMOUS -> AnonymousContent( + when (uiState.isAuthorized) { + false -> AnonymousContent( modifier = Modifier.weight(1f), devicesLabel = uiState.devicesLabel, onSignIn = { onEvent(Event.SignInClicked) } ) - UserMode.AUTHORIZED -> AuthorizedContent( + true -> AuthorizedContent( devicesLabel = uiState.devicesLabel, devices = uiState.devices, onOpenSetup = { onEvent(Event.SetupClicked) }, @@ -116,7 +115,7 @@ private fun ManageScreenContent( onOpenLocation = { onEvent(Event.DeviceLocationClicked(it)) } ) } - if (uiState.userMode == UserMode.AUTHORIZED) { + if (uiState.isAuthorized) { Spacer(modifier = Modifier.height(8.dp)) AirMQButton( text = stringResource(id = R.string.manage_open_widget_constructor), @@ -147,7 +146,7 @@ private fun ProfileHeader( ) { Column( modifier = Modifier - .fillMaxWidth() + .fillMaxSize() .background( brush = Brush.verticalGradient( colors = listOf(LegacyNavGradientEnd, LegacyNavGradientStart) @@ -245,7 +244,6 @@ private fun AnonymousContent( modifier = Modifier .align(Alignment.BottomCenter) .fillMaxWidth(0.46f) - .height(48.dp) .padding(bottom = 16.dp) ) } @@ -336,7 +334,7 @@ private fun ManageScreenAnonymousPreview() { AirMQTheme { ManageScreenContent( uiState = State( - userMode = UserMode.ANONYMOUS, + isAuthorized = false, userName = "Anonymous user", userEmail = "Your preferences are not being synced, please sign in", devicesLabel = "Sign in to add devices" @@ -354,7 +352,7 @@ private fun ManageScreenAuthorizedPreview() { AirMQTheme { ManageScreenContent( uiState = State( - userMode = UserMode.AUTHORIZED, + isAuthorized = true, userName = "Anton Betsun", userEmail = "messbees@gmail.com", devicesLabel = "My devices", diff --git a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreenContract.kt b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreenContract.kt index ec4b885..9cd5898 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreenContract.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreenContract.kt @@ -1,12 +1,6 @@ package org.db3.airmq.features.manage object ManageScreenContract { - - enum class UserMode { - ANONYMOUS, - AUTHORIZED - } - data class DeviceItem( val id: String, val name: String, @@ -15,7 +9,7 @@ object ManageScreenContract { ) data class State( - val userMode: UserMode = UserMode.ANONYMOUS, + val isAuthorized: Boolean = false, val userName: String = "", val userEmail: String = "", val devicesLabel: String = "", 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 2e76532..b9fa094 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 @@ -18,7 +18,6 @@ import org.db3.airmq.features.manage.ManageScreenContract.Action 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 @@ -62,14 +61,14 @@ class ManageViewModel @Inject constructor( } private fun anonymousState(): State = State( - userMode = UserMode.ANONYMOUS, + isAuthorized = false, 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, + isAuthorized = true, userName = user.displayName ?: appContext.getString(R.string.text_anonymous_user), userEmail = user.email ?: "", devicesLabel = appContext.getString(R.string.text_your_devices),