diff --git a/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt b/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt index 9625712..5c68936 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt @@ -7,7 +7,10 @@ import androidx.lifecycle.viewModelScope import com.example.livingai_lg.api.AuthApiClient import com.example.livingai_lg.api.TokenManager import com.example.livingai_lg.api.UserDetails +import com.example.livingai_lg.ui.navigation.NavEvent +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -343,4 +346,14 @@ class MainViewModel(context: Context) : ViewModel() { } } } + + // Used in navigation logic. + private val _navEvents = MutableSharedFlow() + val navEvents = _navEvents.asSharedFlow() + + fun emitNavEvent(event: NavEvent) { + viewModelScope.launch { + _navEvents.emit(event) + } + } } diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt b/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt index 35f4fd5..67f9db9 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt @@ -44,7 +44,8 @@ fun BuyAnimalCard( onSavedChange: (Boolean) -> Unit, onProductClick: () -> Unit, onSellerClick:(sellerId: String)-> Unit, - onBookmarkClick: () -> Unit + onBookmarkClick: () -> Unit, + onInfoClick: () -> Unit, ) { Box( modifier = Modifier @@ -164,7 +165,7 @@ fun BuyAnimalCard( Icon( imageVector = Icons.Outlined.Info, contentDescription = null, - Modifier.padding(horizontal = 4.dp).size(16.dp).align(Alignment.CenterVertically), + Modifier.padding(horizontal = 4.dp).size(16.dp).align(Alignment.CenterVertically).clickable{onInfoClick()}, ) diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/InfoOverlay.kt b/app/src/main/java/com/example/livingai_lg/ui/components/InfoOverlay.kt new file mode 100644 index 0000000..db9e18f --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/components/InfoOverlay.kt @@ -0,0 +1,142 @@ +package com.example.livingai_lg.ui.components + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.material.icons.Icons +import androidx.compose.material3.Icon +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.zIndex +import com.example.livingai_lg.ui.screens.align +import com.example.livingai_lg.ui.theme.AppTypography + +@Composable +fun InfoOverlay( + visible: Boolean, + title: String, + text: String, + onDismiss: () -> Unit +) { + BackHandler(enabled = visible) { onDismiss() } + + Box( + modifier = Modifier.fillMaxSize() + ) { + // Dimmed background + AnimatedVisibility( + visible = visible, + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.35f)) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { onDismiss() } + ) + } + + // Sliding content + AnimatedVisibility( + visible = visible, + enter = slideInVertically { it }, // from bottom + exit = slideOutVertically { it }, + modifier = Modifier.align(Alignment.BottomCenter) + ) { + InfoOverlayContent( + title = title, + text = text, + onDismiss = onDismiss + ) + } + } +} + + +@Composable +private fun InfoOverlayContent( + title: String, + text: String, + onDismiss: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + Color.White, + RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) + .padding(20.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = title, + fontSize = AppTypography.Title, + fontWeight = FontWeight.Medium, + color = Color.Black + ) + + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + modifier = Modifier + .size(24.dp) + .clickable { onDismiss() } + ) + } + + Text( + text = text, + fontSize = AppTypography.Body, + color = Color(0xFF4A5565), + lineHeight = 20.sp + ) + + Spacer(modifier = Modifier.height(18.dp)) + + } + } +} + diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt b/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt index 657dc4a..7437977 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt @@ -29,7 +29,7 @@ val sampleAnimals = listOf( name = "Rita", age = 45, breed = "Gir", - breedInfo = "The 2nd best in India", + breedInfo = "The best in India", location = "Punjab", distance = 12000, imageUrl = listOf("https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2F4.bp.blogspot.com%2F_tecSnxaePMo%2FTLLVknW8dOI%2FAAAAAAAAACo%2F_kd1ZNBXU1o%2Fs1600%2FGIR%2CGujrat.jpg&f=1&nofb=1&ipt=da6ba1d040c396b64d3f08cc99998f66200dcd6c001e4a56def143ab3d1a87ea","https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcpimg.tistatic.com%2F4478702%2Fb%2F4%2Fgir-cow.jpg&f=1&nofb=1&ipt=19bf391461480585c786d01433d863a383c60048ac2ce063ce91f173e215205d"), diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt index 1bae8a9..4fb3d97 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt @@ -1,455 +1,492 @@ -package com.example.livingai_lg.ui.navigation - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.navigation.NavController -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import androidx.navigation.NavType -import androidx.navigation.navArgument -import kotlinx.coroutines.delay -import com.example.livingai_lg.ui.AuthState -import com.example.livingai_lg.ui.screens.AnimalProfileScreen -import com.example.livingai_lg.ui.screens.BuyScreen -import com.example.livingai_lg.ui.screens.ChooseServiceScreen -import com.example.livingai_lg.ui.screens.CreateProfileScreen -import com.example.livingai_lg.ui.screens.FilterScreen -import com.example.livingai_lg.ui.screens.NewListingScreen -import com.example.livingai_lg.ui.screens.PostSaleSurveyScreen -import com.example.livingai_lg.ui.screens.SaleArchiveScreen -import com.example.livingai_lg.ui.screens.SellerProfileScreen -import com.example.livingai_lg.ui.screens.SortScreen -import com.example.livingai_lg.ui.screens.auth.LandingScreen -import com.example.livingai_lg.ui.screens.auth.OtpScreen -import com.example.livingai_lg.ui.screens.auth.SignInScreen -import com.example.livingai_lg.ui.screens.auth.SignUpScreen -import com.example.livingai_lg.ui.navigation.authNavGraph -import com.example.livingai_lg.ui.navigation.mainNavGraph - -object AppScreen { - const val LANDING = "landing" - const val SIGN_IN = "sign_in" - const val SIGN_UP = "sign_up" - - const val OTP = "otp" - - const val CHOOSE_SERVICE = "choose_service" - - const val CREATE_PROFILE = "create_profile" - - const val BUY_ANIMALS = "buy_animals" - const val ANIMAL_PROFILE = "animal_profile" - - const val CREATE_ANIMAL_LISTING = "create_animal_listing" - - const val BUY_ANIMALS_FILTERS = "buy_animals_filters" - const val BUY_ANIMALS_SORT = "buy_animals_sort" - - const val SELLER_PROFILE = "seller_profile" - - const val SALE_ARCHIVE = "sale_archive" - - const val POST_SALE_SURVEY = "post_sale_survey" - - const val SAVED_LISTINGS = "saved_listings" - - const val CONTACTS = "contacts" - - const val CALLS = "calls" - - const val CHAT = "chat" - - const val CHATS = "chats" - - const val ACCOUNTS = "accounts" - - fun chats(contact: String) = - "$CHAT/$contact" - - fun otp(phone: String, name: String) = - "$OTP/$phone/$name" - - fun otpWithSignup(phone: String, name: String, state: String, district: String, village: String) = - "$OTP/$phone/$name/$state/$district/$village" - - fun createProfile(name: String) = - "$CREATE_PROFILE/$name" - - fun chooseService(profileId: String) = - "$CHOOSE_SERVICE/$profileId" - fun postSaleSurvey(animalId: String) = - "$POST_SALE_SURVEY/$animalId" - - fun animalProfile(animalId: String) = - "$ANIMAL_PROFILE/$animalId" - - fun sellerProfile(sellerId: String) = - "$SELLER_PROFILE/$sellerId" - - fun saleArchive(saleId: String) = - "$SALE_ARCHIVE/$saleId" - -} - -object Graph { - const val AUTH = "auth" - const val MAIN = "main" - - fun auth(route: String)= - "$AUTH/$route" - - fun main(route: String)= - "$MAIN/$route" -} -@Composable -fun AppNavigation( - authState: AuthState, - mainViewModel: com.example.livingai_lg.ui.MainViewModel -) { - val navController = rememberNavController() - - // Determine start destination based on initial auth state - // This prevents showing landing screen when user is already logged in - val startDestination = remember(authState) { - when (authState) { - is AuthState.Authenticated -> Graph.MAIN - is AuthState.Unauthenticated -> Graph.AUTH - is AuthState.Unknown -> Graph.AUTH // Show landing while checking - } - } - - NavHost( - navController = navController, - startDestination = startDestination - ) { - authNavGraph(navController, mainViewModel) - mainNavGraph(navController) - } - - // Handle navigation based on auth state changes - LaunchedEffect(authState) { - android.util.Log.d("AppNavigation", "LaunchedEffect triggered with authState: $authState") - when (authState) { - is AuthState.Authenticated -> { - // User is authenticated, navigate to ChooseServiceScreen - // Add a small delay to ensure NavHost graphs are fully built - delay(100) - - val currentRoute = navController.currentBackStackEntry?.destination?.route - android.util.Log.d("AppNavigation", "Authenticated - currentRoute: $currentRoute") - // Only navigate if we're not already in the MAIN graph or ChooseServiceScreen - if (currentRoute?.startsWith(Graph.MAIN) != true && - currentRoute?.startsWith(AppScreen.CHOOSE_SERVICE) != true) { - android.util.Log.d("AppNavigation", "Navigating to ChooseServiceScreen (default profileId: 1)") - try { - // Navigate directly to the start destination route of MAIN graph - // This avoids the "Sequence is empty" error when navigating to Graph.MAIN - navController.navigate(AppScreen.chooseService("1")) { - // Clear back stack to prevent going back to auth screens - popUpTo(Graph.AUTH) { inclusive = true } - // Prevent multiple navigations - launchSingleTop = true - } - } catch (e: Exception) { - android.util.Log.e("AppNavigation", "Navigation error: ${e.message}", e) - // Fallback: try navigating to Graph.MAIN if direct route fails - try { - navController.navigate(Graph.MAIN) { - popUpTo(Graph.AUTH) { inclusive = true } - launchSingleTop = true - } - } catch (e2: Exception) { - android.util.Log.e("AppNavigation", "Fallback navigation also failed: ${e2.message}", e2) - } - } - } else { - android.util.Log.d("AppNavigation", "Already in MAIN graph or ChooseServiceScreen, skipping navigation") - } - } - is AuthState.Unauthenticated -> { - // User is not authenticated, ensure we're in auth graph (landing screen) - val currentRoute = navController.currentBackStackEntry?.destination?.route - if (currentRoute?.startsWith(Graph.MAIN) == true || - currentRoute?.startsWith(Graph.AUTH) != true) { - navController.navigate(Graph.AUTH) { - // Clear back stack to prevent going back to main screens - popUpTo(0) { inclusive = true } - launchSingleTop = true - } - } - } - is AuthState.Unknown -> { - // Still checking auth status - // If we're on landing screen, stay there - // If we're on main screen and checking, don't navigate yet - // This prevents flickering during token validation - } - } - } -// MainNavGraph(navController) -// AuthNavGraph(navController) -// when (authState) { -// is AuthState.Unauthenticated -> {AuthNavGraph()} -// is AuthState.Authenticated -> {MainNavGraph()} -// is AuthState.Unknown -> { -// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { -// CircularProgressIndicator() -// } -// } -// AuthState.Loading -> SplashScreen() -// } - -// val onNavClick: (String) -> Unit = { route -> -// val currentRoute = -// navController.currentBackStackEntry?.destination?.route -// -// if (currentRoute != route) { -// navController.navigate(route) { -// launchSingleTop = true -// restoreState = true -// popUpTo(navController.graph.startDestinationId) { -// saveState = true -// } -// } -// } -// } - - - -// NavHost( -// navController = navController, -// startDestination = AppScreen.LANDING, -// -// ) { -// composable(AppScreen.LANDING) { -// LandingScreen( -// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) }, -// onSignInClick = { navController.navigate(AppScreen.SIGN_IN) }, -// onGuestClick = { navController.navigate(AppScreen.CREATE_PROFILE) } -// ) -// } -// -// composable(AppScreen.SIGN_IN) { -// SignInScreen( -// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) }, -// onSignInClick = { -// navController.navigate(AppScreen.OTP) -// } -// ) -// } -// -// composable(AppScreen.SIGN_UP) { -// SignUpScreen( -// onSignUpClick = { -// navController.navigate(AppScreen.OTP) -// }, -// onSignInClick = { -// navController.navigate(AppScreen.SIGN_IN) { -// popUpTo(AppScreen.SIGN_UP) { inclusive = true } -// } -// } -// ) -// } -// -// composable(AppScreen.OTP) { -// OtpScreen( -// onContinue = { navController.navigate(AppScreen.CREATE_PROFILE) } -// ) -// } - -// composable(AppScreen.CREATE_PROFILE) { -// CreateProfileScreen ( -// onProfileSelected = { profileId -> -// if (profileId == "buyer_seller") { -// navController.navigate(AppScreen.BUY_ANIMALS) -// } else { -// navController.navigate(AppScreen.CHOOSE_SERVICE) -// } }, -// ) -// } -// -// composable(AppScreen.CHOOSE_SERVICE) { -// ChooseServiceScreen ( -// onServiceSelected = { navController.navigate(AppScreen.LANDING) }, -// ) -// } -// -// composable(AppScreen.BUY_ANIMALS) { -// BuyScreen( -// onBackClick = { -// navController.popBackStack() -// }, -// onProductClick = { animalId -> -// navController.navigate( -// AppScreen.animalProfile(animalId) -// ) -// }, -// onNavClick = onNavClick, -// onFilterClick = {navController.navigate(AppScreen.BUY_ANIMALS_FILTERS)}, -// onSortClick = {navController.navigate(AppScreen.BUY_ANIMALS_SORT)}, -// onSellerClick = { sellerId -> -// navController.navigate( -// AppScreen.sellerProfile(sellerId) -// ) -// }, -// ) -// } -// -// composable(AppScreen.BUY_ANIMALS_FILTERS) { -// FilterScreen( -// onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, -// onBackClick = { -// navController.popBackStack() -// }, -// onSkipClick = { -// navController.popBackStack() -// }, -// onCancelClick = { -// navController.popBackStack() -// }, -// -// ) -// } -// -// composable(AppScreen.BUY_ANIMALS_SORT) { -// SortScreen( -// onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, -// onBackClick = { -// navController.popBackStack() -// }, -// onSkipClick = { -// navController.popBackStack() -// }, -// onCancelClick = { -// navController.popBackStack() -// }, -// -// ) -// } -// -// composable(AppScreen.CREATE_ANIMAL_LISTING) { -// NewListingScreen ( -// onSaveClick = {navController.navigate( -// AppScreen.postSaleSurvey("2") -// )}, -// onBackClick = { -// navController.popBackStack() -// } -// ) -// } -// -// composable( -// route = "${AppScreen.SALE_ARCHIVE}/{saleId}", -// arguments = listOf( -// navArgument("saleId") { type = NavType.StringType } -// ) -// ) { backStackEntry -> -// -// val saleId = backStackEntry -// .arguments -// ?.getString("saleId") -// ?: return@composable -// -// SaleArchiveScreen( -// saleId = saleId, -// onBackClick = { -// navController.popBackStack() -// }, -// onSaleSurveyClick = { saleId -> -// navController.navigate( -// AppScreen.sellerProfile(saleId) -// ) -// }, -// ) -// } -// -// composable( -// route = "${AppScreen.POST_SALE_SURVEY}/{animalId}", -// arguments = listOf( -// navArgument("animalId") { type = NavType.StringType } -// ) -// ) { backStackEntry -> -// -// val animalId = backStackEntry -// .arguments -// ?.getString("animalId") -// ?: return@composable -// -// PostSaleSurveyScreen ( -// animalId = animalId, -// onBackClick = { -// navController.popBackStack() -// }, -// onSubmit = {navController.navigate( -// AppScreen.saleArchive("2") -// )} -// ) -// } -// -// composable( -// route = "${AppScreen.ANIMAL_PROFILE}/{animalId}", -// arguments = listOf( -// navArgument("animalId") { type = NavType.StringType } -// ) -// ) { backStackEntry -> -// -// val animalId = backStackEntry -// .arguments -// ?.getString("animalId") -// ?: return@composable -// -// AnimalProfileScreen( -// animalId = animalId, -// onBackClick = { -// navController.popBackStack() -// }, -// onSellerClick = { sellerId -> -// navController.navigate( -// AppScreen.sellerProfile(sellerId) -// ) -// }, -// ) -// } -// -// composable( -// route = "${AppScreen.SELLER_PROFILE}/{sellerId}", -// arguments = listOf( -// navArgument("sellerId") { type = NavType.StringType } -// ) -// ) { backStackEntry -> -// -// val sellerId = backStackEntry -// .arguments -// ?.getString("sellerId") -// ?: return@composable -// -// SellerProfileScreen( -// sellerId = sellerId, -// onBackClick = { -// navController.popBackStack() -// } -// ) -// } -// composable(AppScreen.SELLER_PROFILE) { -// SellerProfileScreen ( -// onBackClick = { -// navController.popBackStack() -// } -// ) -// } - - -// } -} - - +package com.example.livingai_lg.ui.navigation + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.navigation.NavController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.example.farmmarketplace.ui.screens.CallsScreen +import com.example.farmmarketplace.ui.screens.ContactsScreen +import com.example.livingai_lg.ui.AuthState +import com.example.livingai_lg.ui.MainViewModel +import com.example.livingai_lg.ui.screens.AccountsScreen +import com.example.livingai_lg.ui.screens.AnimalProfileScreen +import com.example.livingai_lg.ui.screens.BuyScreen +import com.example.livingai_lg.ui.screens.ChooseServiceScreen +import com.example.livingai_lg.ui.screens.CreateProfileScreen +import com.example.livingai_lg.ui.screens.FilterScreen +import com.example.livingai_lg.ui.screens.NewListingScreen +import com.example.livingai_lg.ui.screens.PostSaleSurveyScreen +import com.example.livingai_lg.ui.screens.SaleArchiveScreen +import com.example.livingai_lg.ui.screens.SavedListingsScreen +import com.example.livingai_lg.ui.screens.SellerProfileScreen +import com.example.livingai_lg.ui.screens.SortScreen +import com.example.livingai_lg.ui.screens.auth.LandingScreen +import com.example.livingai_lg.ui.screens.auth.OtpScreen +import com.example.livingai_lg.ui.screens.auth.SignInScreen +import com.example.livingai_lg.ui.screens.auth.SignUpScreen +import com.example.livingai_lg.ui.screens.chat.ChatScreen +import com.example.livingai_lg.ui.screens.chat.ChatsScreen +import com.example.livingai_lg.ui.screens.testing.ApiTestScreen + +fun NavController.navigateIfAuthenticated( + authState: AuthState, + targetRoute: String, + fallbackRoute: String = AppScreen.LANDING +) { + if (authState is AuthState.Authenticated) { + navigate(targetRoute) { + launchSingleTop = true + } + } else { + navigate(fallbackRoute) { + popUpTo(0) { inclusive = true } + launchSingleTop = true + } + } +} + +@Composable +fun AppNavigation( + authState: AuthState, + mainViewModel: MainViewModel +) { + val navController = rememberNavController() + + /* --------------------------------------------------- + * Collect one-time navigation events (OTP, login, logout) + * --------------------------------------------------- */ + LaunchedEffect(Unit) { + mainViewModel.navEvents.collect { event -> + when (event) { + + is NavEvent.ToCreateProfile -> { + navController.navigate( + AppScreen.createProfile(event.name) + ) { + popUpTo(0) { inclusive = true } + launchSingleTop = true + } + } + + is NavEvent.ToChooseService -> { + navController.navigate( + AppScreen.chooseService(event.profileId) + ) { + popUpTo(0) { inclusive = true } + launchSingleTop = true + } + } + + NavEvent.ToLanding -> { + navController.navigate(AppScreen.LANDING) { + popUpTo(0) { inclusive = true } + launchSingleTop = true + } + } + } + } + } + + val onNavClick: (String) -> Unit = { route -> + val currentRoute = + navController.currentBackStackEntry?.destination?.route + Log.d("Current Route:"," $currentRoute $route") + + if (currentRoute != route) { + navController.navigate(route) { + launchSingleTop = true + //restoreState = true +// popUpTo(navController.graph.startDestinationId) { +// saveState = true +// } + } + } + } + + /* --------------------------------------------------- + * Single NavHost + * --------------------------------------------------- */ + NavHost( + navController = navController, + startDestination = AppScreen.LANDING + ) { + + /* ---------------- AUTH ---------------- */ + + composable(AppScreen.LANDING) { + LandingScreen( + onSignUpClick = { + navController.navigate(AppScreen.SIGN_UP) + }, + onSignInClick = { + navController.navigate(AppScreen.SIGN_IN) + }, + onGuestClick = { + mainViewModel.emitNavEvent( + NavEvent.ToCreateProfile("guest") + ) + } + ) + } + + composable(AppScreen.SIGN_IN) { + SignInScreen( + onSignUpClick = { + navController.navigate(AppScreen.SIGN_UP) + }, + onSignInClick = { phone, name -> + navController.navigate( + AppScreen.otp(phone, name) + ) + } + ) + } + + composable(AppScreen.SIGN_UP) { + SignUpScreen( + onSignUpClick = { phone, name, state, district, village -> + navController.navigate( + AppScreen.otpWithSignup( + phone, name, state, district, village + ) + ) + }, + onSignInClick = { + navController.navigate(AppScreen.SIGN_IN) + } + ) + } + + composable( + "${AppScreen.OTP}/{phoneNumber}/{name}", + arguments = listOf( + navArgument("phoneNumber") { type = NavType.StringType }, + navArgument("name") { type = NavType.StringType } + ) + ) { backStackEntry -> + OtpScreen( + mainViewModel = mainViewModel, + phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "", + name = backStackEntry.arguments?.getString("name") ?: "", + onCreateProfile = { name -> +// navController.navigateIfAuthenticated( +// authState, +// AppScreen.createProfile(name), +// AppScreen.LANDING +// ) + mainViewModel.emitNavEvent( + NavEvent.ToCreateProfile(name) + ) + }, + onSuccess = { +// navController.navigateIfAuthenticated( +// authState, +// AppScreen.chooseService(""), +// AppScreen.LANDING +// ) + mainViewModel.emitNavEvent( + NavEvent.ToChooseService() + ) + } + ) + } + + composable( + "${AppScreen.OTP}/{phoneNumber}/{name}/{state}/{district}/{village}", + arguments = listOf( + navArgument("phoneNumber") { type = NavType.StringType }, + navArgument("name") { type = NavType.StringType }, + navArgument("state") { type = NavType.StringType }, + navArgument("district") { type = NavType.StringType }, + navArgument("village") { type = NavType.StringType } + ) + ) { backStackEntry -> + OtpScreen( + mainViewModel = mainViewModel, + phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "", + name = backStackEntry.arguments?.getString("name") ?: "", + signupState = backStackEntry.arguments?.getString("state"), + signupDistrict = backStackEntry.arguments?.getString("district"), + signupVillage = backStackEntry.arguments?.getString("village"), + onCreateProfile = { name -> + mainViewModel.emitNavEvent( + NavEvent.ToCreateProfile(name) + ) + }, + onSuccess = { + mainViewModel.emitNavEvent( + NavEvent.ToChooseService() + ) + } + ) + } + + /* ---------------- MAIN ---------------- */ + + composable( + "${AppScreen.CREATE_PROFILE}/{name}", + arguments = listOf( + navArgument("name") { type = NavType.StringType } + ) + ) { backStackEntry -> + CreateProfileScreen( + name = backStackEntry.arguments?.getString("name") ?: "", + onProfileSelected = { profileId -> + mainViewModel.emitNavEvent( + NavEvent.ToChooseService(profileId) + ) + } + ) + } + + composable( + "${AppScreen.CHOOSE_SERVICE}/{profileId}", + arguments = listOf( + navArgument("profileId") { type = NavType.StringType } + ) + ) { backStackEntry -> + ChooseServiceScreen( + profileId = backStackEntry.arguments?.getString("profileId") ?: "", + onServiceSelected = { + navController.navigate(AppScreen.BUY_ANIMALS) +// navController.navigateIfAuthenticated( +// authState, +// AppScreen.BUY_ANIMALS +// ) + } + ) + } + + composable(AppScreen.BUY_ANIMALS) { + BuyScreen( + onBackClick = { + navController.popBackStack() + }, + onProductClick = { animalId -> + navController.navigate( + AppScreen.animalProfile(animalId) + ) + }, + onNavClick = onNavClick, + onSellerClick = { sellerId -> + navController.navigate( + AppScreen.sellerProfile(sellerId) + ) + }, + ) + } + + composable(AppScreen.BUY_ANIMALS_FILTERS) { + FilterScreen( + onBackClick = { + navController.popBackStack() + }, + onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, + onCancelClick = { + navController.popBackStack() + }, + ) + } + + composable(AppScreen.BUY_ANIMALS_SORT) { + SortScreen( + onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, + onBackClick = { + navController.popBackStack() + }, + onCancelClick = { + navController.popBackStack() + }, + + ) + } + + composable(AppScreen.SAVED_LISTINGS) { + SavedListingsScreen( + onNavClick = onNavClick, + onBackClick = { navController.popBackStack() }) + } + + composable(AppScreen.ACCOUNTS) { + AccountsScreen( + onBackClick = { navController.popBackStack() }, + onLogout = { + // Navigate to auth graph after logout + navController.navigate(AppScreen.LANDING) { + popUpTo(0) { inclusive = true } + } + }, + onApiTest = { + navController.navigate(AppScreen.API_TEST) + } + ) + } + + + composable(AppScreen.CREATE_ANIMAL_LISTING) { + NewListingScreen ( + onSaveClick = {navController.navigate( + AppScreen.postSaleSurvey("2") + )}, + onBackClick = { + navController.navigate(AppScreen.BUY_ANIMALS){ + popUpTo(AppScreen.BUY_ANIMALS){ + inclusive = true + } + } + + } + ) + } + + composable( + route = "${AppScreen.SALE_ARCHIVE}/{saleId}", + arguments = listOf( + navArgument("saleId") { type = NavType.StringType } + ) + ) { backStackEntry -> + + val saleId = backStackEntry + .arguments + ?.getString("saleId") + ?: return@composable + + SaleArchiveScreen( + saleId = saleId, + onBackClick = { + navController.popBackStack() + }, + onSaleSurveyClick = { saleId -> + navController.navigate( + AppScreen.sellerProfile(saleId) + ) + }, + ) + } + + composable( + route = "${AppScreen.POST_SALE_SURVEY}/{animalId}", + arguments = listOf( + navArgument("animalId") { type = NavType.StringType } + ) + ) { backStackEntry -> + + val animalId = backStackEntry + .arguments + ?.getString("animalId") + ?: return@composable + + PostSaleSurveyScreen ( + animalId = animalId, + onBackClick = { + navController.navigate(AppScreen.CREATE_ANIMAL_LISTING) + }, + onSubmit = {navController.navigate( + AppScreen.saleArchive("2") + )} + ) + } + + composable( + route = "${AppScreen.ANIMAL_PROFILE}/{animalId}", + arguments = listOf( + navArgument("animalId") { type = NavType.StringType } + ) + ) { backStackEntry -> + + val animalId = backStackEntry + .arguments + ?.getString("animalId") + ?: return@composable + + AnimalProfileScreen( + animalId = animalId, + onBackClick = { + navController.popBackStack() + }, + onNavClick = onNavClick, + onSellerClick = { sellerId -> + navController.navigate( + AppScreen.sellerProfile(sellerId) + ) + }, + ) + } + + composable( + route = "${AppScreen.SELLER_PROFILE}/{sellerId}", + arguments = listOf( + navArgument("sellerId") { type = NavType.StringType } + ) + ) { backStackEntry -> + + val sellerId = backStackEntry + .arguments + ?.getString("sellerId") + ?: return@composable + + SellerProfileScreen( + sellerId = sellerId, + onBackClick = { + navController.popBackStack() + } + ) + } + + composable(AppScreen.CONTACTS) { + ContactsScreen( + onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()}, + onTabClick = onNavClick, + ) + } + + composable(AppScreen.CALLS) { + CallsScreen( + onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()}, + onTabClick = onNavClick, + ) + } + + composable(AppScreen.CHATS) { + ChatsScreen( + onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()}, + onTabClick = onNavClick, + onChatItemClick = {navController.navigate(AppScreen.chats("2"))} + ) + } + + composable( + route = "${AppScreen.CHAT}/{contact}", + arguments = listOf( + navArgument("contact") { type = NavType.StringType } + ) + ) { backStackEntry -> + + val sellerId = backStackEntry + .arguments + ?.getString("contact") + ?: return@composable + + ChatScreen( + sellerId, + onBackClick = { + navController.navigate(AppScreen.CHATS) + //navController.popBackStack() + } + ) + } + + //A route for testing + composable(AppScreen.API_TEST) { + ApiTestScreen( + onBackClick = { navController.popBackStack() } + ) + } + } +} + diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppScreen.kt new file mode 100644 index 0000000..bfa2960 --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppScreen.kt @@ -0,0 +1,79 @@ +package com.example.livingai_lg.ui.navigation + +object AppScreen { + const val LANDING = "landing" + const val SIGN_IN = "sign_in" + const val SIGN_UP = "sign_up" + + const val OTP = "otp" + + fun otp(phone: String, name: String) = + "$OTP/$phone/$name" + + fun otpWithSignup(phone: String, name: String, state: String, district: String, village: String) = + "$OTP/$phone/$name/$state/$district/$village" + + const val CHOOSE_SERVICE = "choose_service" + fun chooseService(profileId: String?) = + "$CHOOSE_SERVICE/$profileId" + + const val CREATE_PROFILE = "create_profile" + + fun createProfile(name: String) = + "$CREATE_PROFILE/$name" + + const val BUY_ANIMALS = "buy_animals" + const val ANIMAL_PROFILE = "animal_profile" + fun animalProfile(animalId: String) = + "$ANIMAL_PROFILE/$animalId" + + const val CREATE_ANIMAL_LISTING = "create_animal_listing" + + const val BUY_ANIMALS_FILTERS = "buy_animals_filters" + const val BUY_ANIMALS_SORT = "buy_animals_sort" + + const val SELLER_PROFILE = "seller_profile" + fun sellerProfile(sellerId: String) = + "$SELLER_PROFILE/$sellerId" + + const val SALE_ARCHIVE = "sale_archive" + fun saleArchive(saleId: String) = + "$SALE_ARCHIVE/$saleId" + + const val POST_SALE_SURVEY = "post_sale_survey" + fun postSaleSurvey(animalId: String) = + "$POST_SALE_SURVEY/$animalId" + + const val SAVED_LISTINGS = "saved_listings" + + const val CONTACTS = "contacts" + + fun chats(contact: String) = + "$CHAT/$contact" + + const val CALLS = "calls" + + const val CHAT = "chat" + + const val CHATS = "chats" + + const val ACCOUNTS = "accounts" + + + + // Test screens:: + const val API_TEST = "api_test" + + + + + + + + + + + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/NavEvent.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/NavEvent.kt new file mode 100644 index 0000000..d9de87d --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/NavEvent.kt @@ -0,0 +1,6 @@ +package com.example.livingai_lg.ui.navigation +sealed class NavEvent { + data class ToCreateProfile(val name: String) : NavEvent() + data class ToChooseService(val profileId: String = "") : NavEvent() + object ToLanding : NavEvent() +} diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/AuthNavGraph.kt similarity index 81% rename from app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt rename to app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/AuthNavGraph.kt index 115a61e..3a51f0c 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/AuthNavGraph.kt @@ -1,27 +1,25 @@ -package com.example.livingai_lg.ui.navigation +package com.example.livingai_lg.ui.navigation.navigation_legacy -import androidx.compose.runtime.Composable +import android.util.Log import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController import androidx.navigation.NavType -import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.navigation -import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import com.example.livingai_lg.ui.MainViewModel +import com.example.livingai_lg.ui.navigation.AppScreen import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch // Note: CoroutineScope, Dispatchers, delay, launch still used in onCreateProfile callbacks -import com.example.livingai_lg.ui.screens.SaleArchiveScreen import com.example.livingai_lg.ui.screens.auth.LandingScreen import com.example.livingai_lg.ui.screens.auth.OtpScreen import com.example.livingai_lg.ui.screens.auth.SignInScreen import com.example.livingai_lg.ui.screens.auth.SignUpScreen -fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: com.example.livingai_lg.ui.MainViewModel) { +fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: MainViewModel) { navigation( route = Graph.AUTH, startDestination = AppScreen.LANDING @@ -70,7 +68,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co name = backStackEntry.arguments?.getString("name") ?: "", mainViewModel = mainViewModel, onCreateProfile = {name -> - android.util.Log.d("AuthNavGraph", "Navigating to create profile with name: $name") + Log.d("AuthNavGraph", "Navigating to create profile with name: $name") // Navigate to main graph first without popping, then navigate to specific route try { // Navigate to main graph (this will use its start destination) @@ -87,15 +85,15 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co popUpTo(Graph.AUTH) { inclusive = true } } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Secondary navigation error: ${e.message}", e) + Log.e("AuthNavGraph", "Secondary navigation error: ${e.message}", e) } } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) + Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) } }, onLanding = { - android.util.Log.d("AuthNavGraph", "Navigating to landing page for new user") + Log.d("AuthNavGraph", "Navigating to landing page for new user") // Navigate to landing page within AUTH graph try { navController.navigate(AppScreen.LANDING) { @@ -104,11 +102,11 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co launchSingleTop = true } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Navigation to landing error: ${e.message}", e) + Log.e("AuthNavGraph", "Navigation to landing error: ${e.message}", e) } }, onSuccess = { - android.util.Log.d("AuthNavGraph", "Sign-in successful - navigating to ChooseServiceScreen") + Log.d("AuthNavGraph", "Sign-in successful - navigating to ChooseServiceScreen") // Navigate to MAIN graph which starts at ChooseServiceScreen (startDestination) try { navController.navigate(Graph.MAIN) { @@ -117,7 +115,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co launchSingleTop = true } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) + Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) } } ) @@ -141,7 +139,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co signupVillage = backStackEntry.arguments?.getString("village"), mainViewModel = mainViewModel, onCreateProfile = {name -> - android.util.Log.d("AuthNavGraph", "Navigating to create profile with name: $name") + Log.d("AuthNavGraph", "Navigating to create profile with name: $name") // Navigate to main graph first without popping, then navigate to specific route try { // Navigate to main graph (this will use its start destination) @@ -158,15 +156,15 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co popUpTo(Graph.AUTH) { inclusive = true } } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Secondary navigation error: ${e.message}", e) + Log.e("AuthNavGraph", "Secondary navigation error: ${e.message}", e) } } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) + Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) } }, onLanding = { - android.util.Log.d("AuthNavGraph", "Navigating to landing page for new user") + Log.d("AuthNavGraph", "Navigating to landing page for new user") // Navigate to landing page within AUTH graph try { navController.navigate(AppScreen.LANDING) { @@ -175,11 +173,11 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co launchSingleTop = true } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Navigation to landing error: ${e.message}", e) + Log.e("AuthNavGraph", "Navigation to landing error: ${e.message}", e) } }, onSuccess = { - android.util.Log.d("AuthNavGraph", "Sign-in successful - navigating to ChooseServiceScreen") + Log.d("AuthNavGraph", "Sign-in successful - navigating to ChooseServiceScreen") // Navigate to MAIN graph which starts at ChooseServiceScreen (startDestination) try { navController.navigate(Graph.MAIN) { @@ -188,7 +186,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co launchSingleTop = true } } catch (e: Exception) { - android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) + Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) } } ) diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/MainNavGraph.kt similarity index 95% rename from app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt rename to app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/MainNavGraph.kt index fe577ef..367bc46 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/MainNavGraph.kt @@ -1,18 +1,15 @@ -package com.example.livingai_lg.ui.navigation +package com.example.livingai_lg.ui.navigation.navigation_legacy import android.util.Log import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType -import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import androidx.navigation.navArgument -import com.example.livingai_lg.ui.navigation.AppScreen -import com.example.livingai_lg.ui.navigation.Graph import com.example.farmmarketplace.ui.screens.CallsScreen import com.example.farmmarketplace.ui.screens.ContactsScreen -import com.example.livingai_lg.ui.models.profileTypes +import com.example.livingai_lg.ui.navigation.AppScreen import com.example.livingai_lg.ui.screens.AccountsScreen import com.example.livingai_lg.ui.screens.AnimalProfileScreen import com.example.livingai_lg.ui.screens.BuyScreen @@ -137,7 +134,8 @@ fun NavGraphBuilder.mainNavGraph(navController: NavController) { navController.navigate(Graph.AUTH) { popUpTo(0) { inclusive = true } } - } + }, + onApiTest = {navController.navigate(AppScreen.API_TEST)} ) } diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/OldAppNavigation.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/OldAppNavigation.kt new file mode 100644 index 0000000..1ff9556 --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/navigation_legacy/OldAppNavigation.kt @@ -0,0 +1,430 @@ +package com.example.livingai_lg.ui.navigation.navigation_legacy + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.delay +import com.example.livingai_lg.ui.AuthState +import com.example.livingai_lg.ui.MainViewModel +import com.example.livingai_lg.ui.navigation.AppScreen + +object OldAppScreen { + const val LANDING = "landing" + const val SIGN_IN = "sign_in" + const val SIGN_UP = "sign_up" + + const val OTP = "otp" + + const val CHOOSE_SERVICE = "choose_service" + + const val CREATE_PROFILE = "create_profile" + + const val BUY_ANIMALS = "buy_animals" + const val ANIMAL_PROFILE = "animal_profile" + + const val CREATE_ANIMAL_LISTING = "create_animal_listing" + + const val BUY_ANIMALS_FILTERS = "buy_animals_filters" + const val BUY_ANIMALS_SORT = "buy_animals_sort" + + const val SELLER_PROFILE = "seller_profile" + + const val SALE_ARCHIVE = "sale_archive" + + const val POST_SALE_SURVEY = "post_sale_survey" + + const val SAVED_LISTINGS = "saved_listings" + + const val CONTACTS = "contacts" + + const val CALLS = "calls" + + const val CHAT = "chat" + + const val CHATS = "chats" + + const val ACCOUNTS = "accounts" + + fun chats(contact: String) = + "$CHAT/$contact" + + fun otp(phone: String, name: String) = + "$OTP/$phone/$name" + + fun otpWithSignup(phone: String, name: String, state: String, district: String, village: String) = + "$OTP/$phone/$name/$state/$district/$village" + + fun createProfile(name: String) = + "$CREATE_PROFILE/$name" + + fun chooseService(profileId: String) = + "$CHOOSE_SERVICE/$profileId" + fun postSaleSurvey(animalId: String) = + "$POST_SALE_SURVEY/$animalId" + + fun animalProfile(animalId: String) = + "$ANIMAL_PROFILE/$animalId" + + fun sellerProfile(sellerId: String) = + "$SELLER_PROFILE/$sellerId" + + fun saleArchive(saleId: String) = + "$SALE_ARCHIVE/$saleId" + +} + +object Graph { + const val AUTH = "auth" + const val MAIN = "main" + + fun auth(route: String)= + "$AUTH/$route" + + fun main(route: String)= + "$MAIN/$route" +} +@Composable +fun OldAppNavigation( + authState: AuthState, + mainViewModel: MainViewModel +) { + val navController = rememberNavController() + + // Determine start destination based on initial auth state + // This prevents showing landing screen when user is already logged in + val startDestination = remember(authState) { + when (authState) { + is AuthState.Authenticated -> Graph.MAIN + is AuthState.Unauthenticated -> Graph.AUTH + is AuthState.Unknown -> Graph.AUTH // Show landing while checking + } + } + + NavHost( + navController = navController, + startDestination = startDestination + ) { + authNavGraph(navController, mainViewModel) + mainNavGraph(navController) + } + + // Handle navigation based on auth state changes + LaunchedEffect(authState) { + Log.d("AppNavigation", "LaunchedEffect triggered with authState: $authState") + when (authState) { + is AuthState.Authenticated -> { + // User is authenticated, navigate to ChooseServiceScreen + // Add a small delay to ensure NavHost graphs are fully built + delay(100) + + val currentRoute = navController.currentBackStackEntry?.destination?.route + Log.d("AppNavigation", "Authenticated - currentRoute: $currentRoute") + // Only navigate if we're not already in the MAIN graph or ChooseServiceScreen + if (currentRoute?.startsWith(Graph.MAIN) != true && + currentRoute?.startsWith(AppScreen.CHOOSE_SERVICE) != true) { + Log.d("AppNavigation", "Navigating to ChooseServiceScreen (default profileId: 1)") + try { + // Navigate directly to the start destination route of MAIN graph + // This avoids the "Sequence is empty" error when navigating to Graph.MAIN + navController.navigate(AppScreen.chooseService("1")) { + // Clear back stack to prevent going back to auth screens + popUpTo(Graph.AUTH) { inclusive = true } + // Prevent multiple navigations + launchSingleTop = true + } + } catch (e: Exception) { + Log.e("AppNavigation", "Navigation error: ${e.message}", e) + // Fallback: try navigating to Graph.MAIN if direct route fails + try { + navController.navigate(Graph.MAIN) { + popUpTo(Graph.AUTH) { inclusive = true } + launchSingleTop = true + } + } catch (e2: Exception) { + Log.e("AppNavigation", "Fallback navigation also failed: ${e2.message}", e2) + } + } + } else { + Log.d("AppNavigation", "Already in MAIN graph or ChooseServiceScreen, skipping navigation") + } + } + is AuthState.Unauthenticated -> { + // User is not authenticated, ensure we're in auth graph (landing screen) + val currentRoute = navController.currentBackStackEntry?.destination?.route + if (currentRoute?.startsWith(Graph.MAIN) == true || + currentRoute?.startsWith(Graph.AUTH) != true) { + navController.navigate(Graph.AUTH) { + // Clear back stack to prevent going back to main screens + popUpTo(0) { inclusive = true } + launchSingleTop = true + } + } + } + is AuthState.Unknown -> { + // Still checking auth status + // If we're on landing screen, stay there + // If we're on main screen and checking, don't navigate yet + // This prevents flickering during token validation + } + } + } +// MainNavGraph(navController) +// AuthNavGraph(navController) +// when (authState) { +// is AuthState.Unauthenticated -> {AuthNavGraph()} +// is AuthState.Authenticated -> {MainNavGraph()} +// is AuthState.Unknown -> { +// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { +// CircularProgressIndicator() +// } +// } +// AuthState.Loading -> SplashScreen() +// } + +// val onNavClick: (String) -> Unit = { route -> +// val currentRoute = +// navController.currentBackStackEntry?.destination?.route +// +// if (currentRoute != route) { +// navController.navigate(route) { +// launchSingleTop = true +// restoreState = true +// popUpTo(navController.graph.startDestinationId) { +// saveState = true +// } +// } +// } +// } + + + +// NavHost( +// navController = navController, +// startDestination = AppScreen.LANDING, +// +// ) { +// composable(AppScreen.LANDING) { +// LandingScreen( +// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) }, +// onSignInClick = { navController.navigate(AppScreen.SIGN_IN) }, +// onGuestClick = { navController.navigate(AppScreen.CREATE_PROFILE) } +// ) +// } +// +// composable(AppScreen.SIGN_IN) { +// SignInScreen( +// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) }, +// onSignInClick = { +// navController.navigate(AppScreen.OTP) +// } +// ) +// } +// +// composable(AppScreen.SIGN_UP) { +// SignUpScreen( +// onSignUpClick = { +// navController.navigate(AppScreen.OTP) +// }, +// onSignInClick = { +// navController.navigate(AppScreen.SIGN_IN) { +// popUpTo(AppScreen.SIGN_UP) { inclusive = true } +// } +// } +// ) +// } +// +// composable(AppScreen.OTP) { +// OtpScreen( +// onContinue = { navController.navigate(AppScreen.CREATE_PROFILE) } +// ) +// } + +// composable(AppScreen.CREATE_PROFILE) { +// CreateProfileScreen ( +// onProfileSelected = { profileId -> +// if (profileId == "buyer_seller") { +// navController.navigate(AppScreen.BUY_ANIMALS) +// } else { +// navController.navigate(AppScreen.CHOOSE_SERVICE) +// } }, +// ) +// } +// +// composable(AppScreen.CHOOSE_SERVICE) { +// ChooseServiceScreen ( +// onServiceSelected = { navController.navigate(AppScreen.LANDING) }, +// ) +// } +// +// composable(AppScreen.BUY_ANIMALS) { +// BuyScreen( +// onBackClick = { +// navController.popBackStack() +// }, +// onProductClick = { animalId -> +// navController.navigate( +// AppScreen.animalProfile(animalId) +// ) +// }, +// onNavClick = onNavClick, +// onFilterClick = {navController.navigate(AppScreen.BUY_ANIMALS_FILTERS)}, +// onSortClick = {navController.navigate(AppScreen.BUY_ANIMALS_SORT)}, +// onSellerClick = { sellerId -> +// navController.navigate( +// AppScreen.sellerProfile(sellerId) +// ) +// }, +// ) +// } +// +// composable(AppScreen.BUY_ANIMALS_FILTERS) { +// FilterScreen( +// onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, +// onBackClick = { +// navController.popBackStack() +// }, +// onSkipClick = { +// navController.popBackStack() +// }, +// onCancelClick = { +// navController.popBackStack() +// }, +// +// ) +// } +// +// composable(AppScreen.BUY_ANIMALS_SORT) { +// SortScreen( +// onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, +// onBackClick = { +// navController.popBackStack() +// }, +// onSkipClick = { +// navController.popBackStack() +// }, +// onCancelClick = { +// navController.popBackStack() +// }, +// +// ) +// } +// +// composable(AppScreen.CREATE_ANIMAL_LISTING) { +// NewListingScreen ( +// onSaveClick = {navController.navigate( +// AppScreen.postSaleSurvey("2") +// )}, +// onBackClick = { +// navController.popBackStack() +// } +// ) +// } +// +// composable( +// route = "${AppScreen.SALE_ARCHIVE}/{saleId}", +// arguments = listOf( +// navArgument("saleId") { type = NavType.StringType } +// ) +// ) { backStackEntry -> +// +// val saleId = backStackEntry +// .arguments +// ?.getString("saleId") +// ?: return@composable +// +// SaleArchiveScreen( +// saleId = saleId, +// onBackClick = { +// navController.popBackStack() +// }, +// onSaleSurveyClick = { saleId -> +// navController.navigate( +// AppScreen.sellerProfile(saleId) +// ) +// }, +// ) +// } +// +// composable( +// route = "${AppScreen.POST_SALE_SURVEY}/{animalId}", +// arguments = listOf( +// navArgument("animalId") { type = NavType.StringType } +// ) +// ) { backStackEntry -> +// +// val animalId = backStackEntry +// .arguments +// ?.getString("animalId") +// ?: return@composable +// +// PostSaleSurveyScreen ( +// animalId = animalId, +// onBackClick = { +// navController.popBackStack() +// }, +// onSubmit = {navController.navigate( +// AppScreen.saleArchive("2") +// )} +// ) +// } +// +// composable( +// route = "${AppScreen.ANIMAL_PROFILE}/{animalId}", +// arguments = listOf( +// navArgument("animalId") { type = NavType.StringType } +// ) +// ) { backStackEntry -> +// +// val animalId = backStackEntry +// .arguments +// ?.getString("animalId") +// ?: return@composable +// +// AnimalProfileScreen( +// animalId = animalId, +// onBackClick = { +// navController.popBackStack() +// }, +// onSellerClick = { sellerId -> +// navController.navigate( +// AppScreen.sellerProfile(sellerId) +// ) +// }, +// ) +// } +// +// composable( +// route = "${AppScreen.SELLER_PROFILE}/{sellerId}", +// arguments = listOf( +// navArgument("sellerId") { type = NavType.StringType } +// ) +// ) { backStackEntry -> +// +// val sellerId = backStackEntry +// .arguments +// ?.getString("sellerId") +// ?: return@composable +// +// SellerProfileScreen( +// sellerId = sellerId, +// onBackClick = { +// navController.popBackStack() +// } +// ) +// } +// composable(AppScreen.SELLER_PROFILE) { +// SellerProfileScreen ( +// onBackClick = { +// navController.popBackStack() +// } +// ) +// } + + +// } +} + + diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt index ae6c7d2..517a9f1 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.Logout +import androidx.compose.material.icons.filled.Construction import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -34,7 +35,8 @@ import kotlinx.coroutines.launch @Composable fun AccountsScreen( onBackClick: () -> Unit, - onLogout: () -> Unit + onLogout: () -> Unit, + onApiTest: () -> Unit, ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -123,6 +125,43 @@ fun AccountsScreen( } } } + + Surface( + modifier = Modifier + .fillMaxWidth().padding(vertical = 12.dp) + .clickable { + onApiTest() + }, + shape = RoundedCornerShape(12.dp), + color = Color.White, + shadowElevation = 2.dp + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Icon( + imageVector = Icons.Default.Construction, + contentDescription = "Api test", + tint = Color.Gray, + modifier = Modifier.size(24.dp) + ) + Text( + text = "Test API", + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = Color.Gray + ) + } + } + } } } } diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt index 9ab53f7..c256ef0 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt @@ -36,6 +36,7 @@ import com.example.livingai_lg.R import com.example.livingai_lg.ui.components.ActionPopup import com.example.livingai_lg.ui.components.AddressSelectorOverlay import com.example.livingai_lg.ui.components.FilterOverlay +import com.example.livingai_lg.ui.components.InfoOverlay import com.example.livingai_lg.ui.components.NotificationsOverlay import com.example.livingai_lg.ui.components.SortOverlay import com.example.livingai_lg.ui.models.sampleNotifications @@ -59,6 +60,9 @@ fun BuyScreen( var showSavedPopup by remember { mutableStateOf(false) } var showNotifications by remember { mutableStateOf(false) } val sampleNotifications = sampleNotifications + var showInfoOverlay by remember { mutableStateOf(false) } + var selectedItemId by remember { mutableStateOf(null) } + Box( modifier = Modifier @@ -168,7 +172,9 @@ fun BuyScreen( onSavedChange = { isSaved.value = it }, onProductClick = { onProductClick(animal.id)}, onSellerClick = onSellerClick, - onBookmarkClick = { showSavedPopup = true } + onBookmarkClick = { showSavedPopup = true }, + onInfoClick = { showInfoOverlay = true + selectedItemId=animal.id}, ) Spacer(modifier = Modifier.height(16.dp)) @@ -213,6 +219,15 @@ fun BuyScreen( } ) + val animal = sampleAnimals.find { it.id == selectedItemId } + + InfoOverlay( + visible = showInfoOverlay, + title = animal?.breed ?:"", + text = animal?.breedInfo ?: "", + onDismiss = { showInfoOverlay = false } + ) + ActionPopup( visible = showSavedPopup, text = "Saved", diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt index 6ed0591..8d66d1c 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt @@ -239,7 +239,7 @@ Column( val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...") // Refresh auth status - this will trigger navigation via AppNavigation's LaunchedEffect - mainViewModel.refreshAuthStatus() +// mainViewModel.refreshAuthStatus() try { if (needsProfile) { android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") @@ -247,6 +247,7 @@ Column( } else { // Don't manually navigate - let AppNavigation handle it android.util.Log.d("OTPScreen", "Signup successful - auth state updated, navigation will happen automatically") + onSuccess() } } catch (e: Exception) { android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) @@ -266,6 +267,7 @@ Column( } else { // Don't manually navigate - let AppNavigation handle it android.util.Log.d("OTPScreen", "Signup successful - auth state updated, navigation will happen automatically") + onSuccess() } } catch (e: Exception) { android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) @@ -325,7 +327,12 @@ Column( // Tokens are now saved (synchronously via commit()) // Refresh auth status - this will optimistically set authState to Authenticated // The LaunchedEffect in AppNavigation will automatically navigate to ChooseServiceScreen - mainViewModel.refreshAuthStatus() + //mainViewModel.refreshAuthStatus() + if(response.needsProfile) { + onCreateProfile(name) + } else { + onSuccess() + } android.util.Log.d("OTPScreen", "Auth status refreshed - navigation will happen automatically via LaunchedEffect") } .onFailure { error -> diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/testing/ApiTestScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/testing/ApiTestScreen.kt new file mode 100644 index 0000000..4ee421b --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/testing/ApiTestScreen.kt @@ -0,0 +1,156 @@ +package com.example.livingai_lg.ui.screens.testing + + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ApiTestScreen( + onBackClick: () -> Unit +) { + val scope = rememberCoroutineScope() + + // Default API endpoint + var apiEndpoint by remember { + mutableStateOf("https://jsonplaceholder.typicode.com/posts/1") + } + + var isLoading by remember { mutableStateOf(false) } + var responseText by remember { mutableStateOf(null) } + var errorText by remember { mutableStateOf(null) } + + val client = remember { OkHttpClient() } + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF7F4EE)) + ) { + // Top App Bar + TopAppBar( + title = { + Text( + text = "API Test", + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + color = Color.Black + ) + }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = Color.Black + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color(0xFFF7F4EE) + ) + ) + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + + // API endpoint input + OutlinedTextField( + value = apiEndpoint, + onValueChange = { apiEndpoint = it }, + label = { Text("API Endpoint") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + // Call API button + Button( + onClick = { + scope.launch { + isLoading = true + responseText = null + errorText = null + + try { + val result = withContext(Dispatchers.IO) { + val request = Request.Builder() + .url(apiEndpoint) + .get() + .build() + + client.newCall(request).execute().use { response -> + val body = response.body?.string() + Pair(response.code, body) + } + } + + val (code, body) = result + responseText = "Status: $code\n\n$body" + } catch (e: Exception) { + e.printStackTrace() + errorText = e.toString() + } finally { + isLoading = false + } + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading + ) { + Text(if (isLoading) "Calling API..." else "Call API") + } + + // Result display + when { + responseText != null -> { + Surface( + shape = RoundedCornerShape(12.dp), + color = Color.White, + shadowElevation = 2.dp, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = responseText ?: "", + modifier = Modifier.padding(16.dp), + fontSize = 14.sp + ) + } + } + + errorText != null -> { + Surface( + shape = RoundedCornerShape(12.dp), + color = Color(0xFFFFEBEE), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = errorText ?: "", + modifier = Modifier.padding(16.dp), + fontSize = 14.sp, + color = Color(0xFFE53935) + ) + } + } + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9f6fa38..2121c01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ navigationCompose = "2.7.7" ktor = "2.3.12" kotlinxSerialization = "1.6.3" securityCrypto = "1.1.0-alpha06" +navigationComposeJvmstubs = "2.9.6" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -42,6 +43,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- # AndroidX Security androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" } +androidx-navigation-compose-jvmstubs = { group = "androidx.navigation", name = "navigation-compose-jvmstubs", version.ref = "navigationComposeJvmstubs" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }