From 0efb0648d4a6a9e00e1bbfb88961208c6911c980 Mon Sep 17 00:00:00 2001 From: conniecliu Date: Tue, 15 Sep 2026 22:28:44 -0400 Subject: [PATCH] Add guest profile sign-in flow This implements the skip sign-in flow for when a user skips the sign-in prompt. Changes include: - New guest profile screen that appears when a user is not logged in. - Replaced main logo png with xml for visual clarity. - Modified buttons and navigation for new flow and UI. --- .../uplift/ui/MainNavigationWrapper.kt | 17 ++- .../ui/components/general/UpliftButton.kt | 4 +- .../components/onboarding/auth/LogInButton.kt | 11 +- .../ui/screens/profile/GuestProfileScreen.kt | 144 ++++++++++++++++++ .../viewmodels/nav/RootNavigationViewModel.kt | 28 ++-- .../main/res/drawable/guest_profile_goal.xml | 21 +++ app/src/main/res/drawable/ic_main_logo.png | Bin 3109 -> 0 bytes app/src/main/res/drawable/ic_main_logo.xml | 14 ++ 8 files changed, 217 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/GuestProfileScreen.kt create mode 100644 app/src/main/res/drawable/guest_profile_goal.xml delete mode 100644 app/src/main/res/drawable/ic_main_logo.png create mode 100644 app/src/main/res/drawable/ic_main_logo.xml diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt b/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt index a0d652c4..a86f5e78 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt @@ -45,6 +45,7 @@ import com.cornellappdev.uplift.ui.screens.gyms.GymDetailScreen import com.cornellappdev.uplift.ui.screens.gyms.HomeScreen import com.cornellappdev.uplift.ui.screens.onboarding.ProfileCreationScreen import com.cornellappdev.uplift.ui.screens.onboarding.SignInPromptScreen +import com.cornellappdev.uplift.ui.screens.profile.GuestProfileScreen import com.cornellappdev.uplift.ui.screens.profile.ProfileScreen import com.cornellappdev.uplift.ui.screens.profile.SettingsScreen import com.cornellappdev.uplift.ui.screens.profile.WorkoutHistoryScreen @@ -118,8 +119,14 @@ fun MainNavigationWrapper( //TODO: Try to consolidate launched effects into one with consumeIn function that takes in coroutine scope LaunchedEffect(rootNavigationUiState.navEvent) { - rootNavigationUiState.navEvent?.consumeSuspend { - navController.navigate(it) + rootNavigationUiState.navEvent?.consumeSuspend { route -> + navController.navigate(route) { + if (route == UpliftRootRoute.Home) { + // Finish skip/login/onboarding without leaving those screens on Back. + popUpTo(0) + launchSingleTop = true + } + } } } LaunchedEffect(rootNavigationUiState.popBackStack) { @@ -258,7 +265,11 @@ fun MainNavigationWrapper( CapacityReminderScreen() } composable { - ProfileScreen() + if (isLoggedIn) { + ProfileScreen() + } else { + GuestProfileScreen() + } } composable { MainReminderScreen() diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/general/UpliftButton.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/general/UpliftButton.kt index 5663750e..ae93f2c7 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/general/UpliftButton.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/general/UpliftButton.kt @@ -90,7 +90,7 @@ fun UpliftButton( fontSize = fontSize.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, - modifier = modifier.wrapContentSize() + modifier = Modifier.wrapContentSize() ) } } @@ -100,4 +100,4 @@ fun UpliftButton( @Composable fun UpliftButtonPreview() { UpliftButton(onClick = { /*TODO*/ }) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/onboarding/auth/LogInButton.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/onboarding/auth/LogInButton.kt index a2cf5b5c..5f5bc224 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/onboarding/auth/LogInButton.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/onboarding/auth/LogInButton.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.credentials.Credential @@ -16,7 +17,10 @@ import com.cornellappdev.uplift.ui.components.general.UpliftButton import kotlinx.coroutines.launch @Composable -fun LogInButton(onRequestResult: (Credential) -> Unit) { +fun LogInButton( + onRequestResult: (Credential) -> Unit, + modifier: Modifier = Modifier +) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() UpliftButton( @@ -32,7 +36,8 @@ fun LogInButton(onRequestResult: (Credential) -> Unit) { width = 144.dp, height = 44.dp, fontSize = 16f, - elevation = 2.dp + elevation = 2.dp, + modifier = modifier ) } @@ -60,4 +65,4 @@ private suspend fun launchCredentialManagerButtonUI( Log.e("CredentialManager", e.message.orEmpty(), e) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/GuestProfileScreen.kt b/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/GuestProfileScreen.kt new file mode 100644 index 00000000..be96c93d --- /dev/null +++ b/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/GuestProfileScreen.kt @@ -0,0 +1,144 @@ +package com.cornellappdev.uplift.ui.screens.profile + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.credentials.Credential +import androidx.hilt.navigation.compose.hiltViewModel +import com.cornellappdev.uplift.R +import com.cornellappdev.uplift.ui.components.onboarding.auth.LogInButton +import com.cornellappdev.uplift.ui.viewmodels.onboarding.LoginViewModel +import com.cornellappdev.uplift.util.LIGHT_YELLOW +import com.cornellappdev.uplift.util.PRIMARY_BLACK +import com.cornellappdev.uplift.util.montserratFamily + +@Composable +fun GuestProfileScreen( + loginViewModel: LoginViewModel = hiltViewModel(), +) { + GuestProfileScreenContent(loginViewModel::onSignInWithGoogle) +} + +@Composable +private fun GuestProfileScreenContent(onSignIn: (Credential) -> Unit) { + BoxWithConstraints( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .clipToBounds() + ) { + val headerScale = (maxHeight.value / 769f).coerceIn(0.65f, 1.2f) + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(375.dp * headerScale) + .drawBehind { + drawCircle( + color = LIGHT_YELLOW, + radius = 353.5.dp.toPx() * headerScale, + center = Offset(size.width * 101.5f / 393f, 4.5.dp.toPx() * headerScale) + ) + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(136.dp * headerScale)) + Image( + painter = painterResource(R.drawable.ic_main_logo), + contentDescription = "Uplift logo", + modifier = Modifier + .width(207.dp * headerScale) + .height(183.dp * headerScale) + ) + } + Spacer(Modifier.height(24.dp)) + Text( + text = "Create your Uplift profile.", + modifier = Modifier.padding(horizontal = 16.dp), + fontFamily = montserratFamily, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + lineHeight = 30.sp, + color = PRIMARY_BLACK, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(24.dp)) + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + GuestProfileBenefit(R.drawable.guest_profile_goal, "Create fitness goals") + GuestProfileBenefit(R.drawable.gym_simple, "Track fitness progress") + GuestProfileBenefit(R.drawable.history, "View workout history") + } + Spacer(Modifier.height(48.dp)) + LogInButton( + onRequestResult = onSignIn, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + Spacer(Modifier.height(32.dp)) + } + } +} + +@Composable +private fun GuestProfileBenefit(@DrawableRes icon: Int, text: String) { + Row( + modifier = Modifier + .width(240.dp) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Image( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + Text( + text = text, + fontFamily = montserratFamily, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 16.sp, + color = PRIMARY_BLACK.copy(alpha = 0.9f) + ) + } +} + +@Preview(showBackground = true, widthDp = 393, heightDp = 769) +@Composable +private fun GuestProfilePreview() { + GuestProfileScreenContent {} +} diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/nav/RootNavigationViewModel.kt b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/nav/RootNavigationViewModel.kt index aca57c5e..5b58c774 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/nav/RootNavigationViewModel.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/nav/RootNavigationViewModel.kt @@ -36,7 +36,16 @@ class RootNavigationViewModel @Inject constructor( val popBackStack: UIEvent? = null, val navigateUp: UIEvent? = null, val startDestination: UpliftRootRoute = if (ONBOARDING_FLAG) UpliftRootRoute.Onboarding else UpliftRootRoute.Home - ) + ) { + internal fun withSession(loggedIn: Boolean, destination: UpliftRootRoute): RootNavigationUiState { + val shouldNavigate = destination != startDestination || loggedIn != isLoggedIn + return copy( + isLoggedIn = loggedIn, + startDestination = destination, + navEvent = if (shouldNavigate) UIEvent(destination) else navEvent + ) + } + } init { @@ -60,25 +69,16 @@ class RootNavigationViewModel @Inject constructor( viewModelScope.launch { sessionManager.isLoggedIn.collect { loggedIn -> - applyMutation { - copy(isLoggedIn = loggedIn) - } - val hasSkipped = userInfoRepository.getSkipFromDataStore() val shouldShowHome = loggedIn || hasSkipped || !ONBOARDING_FLAG val newRoute = if (shouldShowHome) UpliftRootRoute.Home else UpliftRootRoute.Onboarding applyMutation { - // Only attach a navEvent if we are actually changing the destination compared to what was set during initialization. - val shouldNav = newRoute != startDestination || loggedIn != isLoggedIn - - copy( - isLoggedIn = loggedIn, - startDestination = newRoute, - navEvent = if (shouldNav) UIEvent(newRoute) else navEvent - ) + // Compare against the previous session before updating it. Guest login + // must finish onboarding even when Home is already the start destination. + withSession(loggedIn, newRoute) } } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/guest_profile_goal.xml b/app/src/main/res/drawable/guest_profile_goal.xml new file mode 100644 index 00000000..39a85b58 --- /dev/null +++ b/app/src/main/res/drawable/guest_profile_goal.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_main_logo.png b/app/src/main/res/drawable/ic_main_logo.png deleted file mode 100644 index 58c2ea016962852241617e54a93301b874c9fb70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3109 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 diff --git a/app/src/main/res/drawable/ic_main_logo.xml b/app/src/main/res/drawable/ic_main_logo.xml new file mode 100644 index 00000000..29112e20 --- /dev/null +++ b/app/src/main/res/drawable/ic_main_logo.xml @@ -0,0 +1,14 @@ + + + +