Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ dependencies {
// Coil Image Loading
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
// Splash Screen API
implementation(libs.androidx.splashscreen)
}

// Allow references to generated code
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/com/cornellappdev/hustle/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,31 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.cornellappdev.hustle.ui.HustleApp
import com.cornellappdev.hustle.ui.theme.HustleTheme
import com.cornellappdev.hustle.ui.viewmodels.RootViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val rootViewModel: RootViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)

splashScreen.setKeepOnScreenCondition {
rootViewModel.uiStateFlow.value.isLoading
}

enableEdgeToEdge()
setContent {
HustleTheme {
HustleApp()
val rootUiState = rootViewModel.collectUiStateValue()
if (!rootUiState.isLoading) {
HustleApp(isSignedIn = rootUiState.isSignedIn)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class AuthRepositoryImpl @Inject constructor(
override val currentUserFlow: StateFlow<User?> = _currentUserFlow.asStateFlow()

init {
_currentUserFlow.value = firebaseAuth.currentUser?.toUser()
firebaseAuth.addAuthStateListener {
_currentUserFlow.value = it.currentUser?.toUser()
}
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/com/cornellappdev/hustle/ui/HustleApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable
import com.cornellappdev.hustle.ui.navigation.HustleNavigation

@Composable
fun HustleApp() {
HustleNavigation()
fun HustleApp(
isSignedIn: Boolean
) {
HustleNavigation(isSignedIn)
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,46 @@
package com.cornellappdev.hustle.ui.navigation

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Person
import androidx.annotation.DrawableRes
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import com.cornellappdev.hustle.R

data class BottomNavigationItem<T : AppDestination>(
val route: T,
val title: String,
val icon: ImageVector,
val selectedIcon: ImageVector = icon,
@DrawableRes val icon: Int,
@DrawableRes val selectedIcon: Int = icon,
)

val bottomNavigationItems = listOf(
BottomNavigationItem(
route = HomeTab,
title = "Home",
icon = Icons.Outlined.Home,
selectedIcon = Icons.Filled.Home,
icon = R.drawable.ic_home,
selectedIcon = R.drawable.ic_home,
),
BottomNavigationItem(
route = FavoritesTab,
title = "Favorites",
icon = Icons.Outlined.FavoriteBorder,
selectedIcon = Icons.Filled.Favorite,
route = MessagesTab,
title = "Messages",
icon = R.drawable.ic_messages,
selectedIcon = R.drawable.ic_messages,
),
BottomNavigationItem(
route = ProfileTab,
title = "Profile",
icon = Icons.Outlined.Person,
selectedIcon = Icons.Filled.Person,
icon = R.drawable.ic_profile,
selectedIcon = R.drawable.ic_profile,
),
)

Expand All @@ -67,8 +64,9 @@ fun BottomNavigationBar(navController: NavHostController) {

NavigationBarItem(icon = {
Icon(
if (selected) item.selectedIcon else item.icon,
contentDescription = item.title
painter = painterResource(id = if (selected) item.selectedIcon else item.icon),
contentDescription = item.title,
tint = Color.Unspecified
)
}, selected = selected, onClick = {
navController.navigate(item.route) {
Expand All @@ -78,6 +76,8 @@ fun BottomNavigationBar(navController: NavHostController) {
launchSingleTop = true
restoreState = true
}
}, label = {
Text(item.title)
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,29 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.cornellappdev.hustle.ui.navigation.navgraphs.homeNavGraph
import com.cornellappdev.hustle.ui.navigation.navgraphs.messagesNavGraph
import com.cornellappdev.hustle.ui.navigation.navgraphs.onboardingNavGraph
import com.cornellappdev.hustle.ui.navigation.navgraphs.profileNavGraph

@Composable
fun HustleNavigation() {
fun HustleNavigation(
isSignedIn: Boolean = false
) {
val navController = rememberNavController()
val startDestination = if (isSignedIn) HomeTab else Onboarding

Scaffold(
bottomBar = { BottomNavigationBar(navController = navController) }
bottomBar = {
if (isSignedIn) {
BottomNavigationBar(navController = navController)
}
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = HomeTab,
startDestination = startDestination,
modifier = Modifier.padding(innerPadding)
) {
onboardingNavGraph(navController = navController)
homeNavGraph(navController = navController)
messagesNavGraph(navController = navController)
profileNavGraph(navController = navController)
Expand Down
14 changes: 11 additions & 3 deletions app/src/main/java/com/cornellappdev/hustle/ui/navigation/Routes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import kotlinx.serialization.Serializable

sealed interface AppDestination

@Serializable
data object Onboarding: AppDestination

@Serializable
data object HomeTab : AppDestination

@Serializable
data object FavoritesTab : AppDestination
data object MessagesTab : AppDestination

@Serializable
data object ProfileTab : AppDestination
Expand All @@ -21,9 +24,9 @@ sealed interface HomeDestination : AppDestination {
data class ServiceDetail(val serviceId: String) : HomeDestination
}

sealed interface FavoritesDestination : AppDestination {
sealed interface MessagesDestination : AppDestination {
@Serializable
data object Favorites : FavoritesDestination
data object Messages : MessagesDestination
}

sealed interface ProfileDestination : AppDestination {
Expand All @@ -32,4 +35,9 @@ sealed interface ProfileDestination : AppDestination {

@Serializable
data object EditProfile : ProfileDestination
}

sealed interface OnboardingDestination: AppDestination {
@Serializable
data object SignIn : OnboardingDestination
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.cornellappdev.hustle.ui.navigation.navgraphs

import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import com.cornellappdev.hustle.ui.navigation.HomeTab
import com.cornellappdev.hustle.ui.navigation.Onboarding
import com.cornellappdev.hustle.ui.navigation.OnboardingDestination
import com.cornellappdev.hustle.ui.screens.onboarding.SignInScreen

fun NavGraphBuilder.onboardingNavGraph(navController: NavHostController) {
navigation<Onboarding>(startDestination = OnboardingDestination.SignIn) {
composable<OnboardingDestination.SignIn> {
SignInScreen(navigateToHome = {
navController.navigate(HomeTab) {
popUpTo(0) { inclusive = true }
}
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import com.cornellappdev.hustle.ui.navigation.Onboarding
import com.cornellappdev.hustle.ui.navigation.ProfileDestination
import com.cornellappdev.hustle.ui.navigation.ProfileTab
import com.cornellappdev.hustle.ui.screens.profile.ProfileScreen

fun NavGraphBuilder.profileNavGraph(navController: NavHostController) {
navigation<ProfileTab>(startDestination = ProfileDestination.Profile) {
composable<ProfileDestination.Profile> {

ProfileScreen(navigateToSignIn = {
navController.navigate(Onboarding) {
popUpTo(0) { inclusive = true }
}
})
}

composable<ProfileDestination.EditProfile> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.cornellappdev.hustle.ui.screens.home
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.cornellappdev.hustle.ui.viewmodels.home.HomeScreenViewModel

@Composable
fun HomeScreen(viewModel: HomeScreenViewModel = hiltViewModel()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.cornellappdev.hustle.ui.screens.onboarding

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.cornellappdev.hustle.ui.components.general.ErrorMessage
import com.cornellappdev.hustle.ui.components.onboarding.GoogleSignInButton
import com.cornellappdev.hustle.ui.viewmodels.ActionState
import com.cornellappdev.hustle.ui.viewmodels.onboarding.SignInScreenViewModel

@Composable
fun SignInScreen(
navigateToHome: () -> Unit,
modifier: Modifier = Modifier,
signInScreenViewModel: SignInScreenViewModel = hiltViewModel()
) {
val signInUiState = signInScreenViewModel.collectUiStateValue()

LaunchedEffect(signInUiState.isSignedIn) {
if (signInUiState.isSignedIn) navigateToHome()
}

SignInScreenContent(
onGoogleSignInButtonClick = signInScreenViewModel::signInWithGoogle,
isSignInLoading = signInUiState.actionState is ActionState.Loading,
errorMessage = when (signInUiState.actionState) {
is ActionState.Error -> signInUiState.actionState.message
else -> null
},
onDismissError = signInScreenViewModel::clearActionState,
modifier = modifier
)
}

@Composable
private fun SignInScreenContent(
onGoogleSignInButtonClick: () -> Unit,
isSignInLoading: Boolean,
errorMessage: String?,
onDismissError: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
GoogleSignInButton(
onClick = onGoogleSignInButtonClick, isLoading = isSignInLoading
)
}
errorMessage?.let { error ->
ErrorMessage(
message = error,
onDismiss = onDismissError,
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
}

class SignInErrorMessageProvider : PreviewParameterProvider<String?> {
override val values = sequenceOf(
null,
"Sign in failed. Please sign in with your Cornell email"
)
}

@Preview(showBackground = true)
@Composable
private fun SignInScreenPreview(
@PreviewParameter(SignInErrorMessageProvider::class) errorMessage: String?
) {
SignInScreenContent(
onGoogleSignInButtonClick = {},
isSignInLoading = false,
errorMessage = errorMessage,
onDismissError = {})
}
Loading