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 58c2ea01..00000000 Binary files a/app/src/main/res/drawable/ic_main_logo.png and /dev/null differ 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 @@ + + + +