New simplified navigation.

Info overlay
api testing screen
This commit is contained in:
ankitsaraf 2025-12-20 17:25:57 +05:30
parent feca34f892
commit f8882c1dcc
15 changed files with 1412 additions and 489 deletions

View File

@ -7,7 +7,10 @@ import androidx.lifecycle.viewModelScope
import com.example.livingai_lg.api.AuthApiClient import com.example.livingai_lg.api.AuthApiClient
import com.example.livingai_lg.api.TokenManager import com.example.livingai_lg.api.TokenManager
import com.example.livingai_lg.api.UserDetails 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.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -343,4 +346,14 @@ class MainViewModel(context: Context) : ViewModel() {
} }
} }
} }
// Used in navigation logic.
private val _navEvents = MutableSharedFlow<NavEvent>()
val navEvents = _navEvents.asSharedFlow()
fun emitNavEvent(event: NavEvent) {
viewModelScope.launch {
_navEvents.emit(event)
}
}
} }

View File

@ -44,7 +44,8 @@ fun BuyAnimalCard(
onSavedChange: (Boolean) -> Unit, onSavedChange: (Boolean) -> Unit,
onProductClick: () -> Unit, onProductClick: () -> Unit,
onSellerClick:(sellerId: String)-> Unit, onSellerClick:(sellerId: String)-> Unit,
onBookmarkClick: () -> Unit onBookmarkClick: () -> Unit,
onInfoClick: () -> Unit,
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@ -164,7 +165,7 @@ fun BuyAnimalCard(
Icon( Icon(
imageVector = Icons.Outlined.Info, imageVector = Icons.Outlined.Info,
contentDescription = null, 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()},
) )

View File

@ -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))
}
}
}

View File

@ -29,7 +29,7 @@ val sampleAnimals = listOf(
name = "Rita", name = "Rita",
age = 45, age = 45,
breed = "Gir", breed = "Gir",
breedInfo = "The 2nd best in India", breedInfo = "The best in India",
location = "Punjab", location = "Punjab",
distance = 12000, 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"), 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"),

View File

@ -1,455 +1,492 @@
package com.example.livingai_lg.ui.navigation package com.example.livingai_lg.ui.navigation
import androidx.compose.foundation.layout.Box import android.util.Log
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Composable import androidx.navigation.NavController
import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavType
import androidx.compose.runtime.mutableStateOf import androidx.navigation.compose.NavHost
import androidx.compose.runtime.remember import androidx.navigation.compose.composable
import androidx.compose.runtime.setValue import androidx.navigation.compose.rememberNavController
import androidx.compose.ui.Alignment import androidx.navigation.navArgument
import androidx.compose.ui.Modifier import com.example.farmmarketplace.ui.screens.CallsScreen
import androidx.navigation.NavController import com.example.farmmarketplace.ui.screens.ContactsScreen
import androidx.navigation.NavHostController import com.example.livingai_lg.ui.AuthState
import androidx.navigation.compose.NavHost import com.example.livingai_lg.ui.MainViewModel
import androidx.navigation.compose.composable import com.example.livingai_lg.ui.screens.AccountsScreen
import androidx.navigation.compose.rememberNavController import com.example.livingai_lg.ui.screens.AnimalProfileScreen
import androidx.navigation.NavType import com.example.livingai_lg.ui.screens.BuyScreen
import androidx.navigation.navArgument import com.example.livingai_lg.ui.screens.ChooseServiceScreen
import kotlinx.coroutines.delay import com.example.livingai_lg.ui.screens.CreateProfileScreen
import com.example.livingai_lg.ui.AuthState import com.example.livingai_lg.ui.screens.FilterScreen
import com.example.livingai_lg.ui.screens.AnimalProfileScreen import com.example.livingai_lg.ui.screens.NewListingScreen
import com.example.livingai_lg.ui.screens.BuyScreen import com.example.livingai_lg.ui.screens.PostSaleSurveyScreen
import com.example.livingai_lg.ui.screens.ChooseServiceScreen import com.example.livingai_lg.ui.screens.SaleArchiveScreen
import com.example.livingai_lg.ui.screens.CreateProfileScreen import com.example.livingai_lg.ui.screens.SavedListingsScreen
import com.example.livingai_lg.ui.screens.FilterScreen import com.example.livingai_lg.ui.screens.SellerProfileScreen
import com.example.livingai_lg.ui.screens.NewListingScreen import com.example.livingai_lg.ui.screens.SortScreen
import com.example.livingai_lg.ui.screens.PostSaleSurveyScreen import com.example.livingai_lg.ui.screens.auth.LandingScreen
import com.example.livingai_lg.ui.screens.SaleArchiveScreen import com.example.livingai_lg.ui.screens.auth.OtpScreen
import com.example.livingai_lg.ui.screens.SellerProfileScreen import com.example.livingai_lg.ui.screens.auth.SignInScreen
import com.example.livingai_lg.ui.screens.SortScreen import com.example.livingai_lg.ui.screens.auth.SignUpScreen
import com.example.livingai_lg.ui.screens.auth.LandingScreen import com.example.livingai_lg.ui.screens.chat.ChatScreen
import com.example.livingai_lg.ui.screens.auth.OtpScreen import com.example.livingai_lg.ui.screens.chat.ChatsScreen
import com.example.livingai_lg.ui.screens.auth.SignInScreen import com.example.livingai_lg.ui.screens.testing.ApiTestScreen
import com.example.livingai_lg.ui.screens.auth.SignUpScreen
import com.example.livingai_lg.ui.navigation.authNavGraph fun NavController.navigateIfAuthenticated(
import com.example.livingai_lg.ui.navigation.mainNavGraph authState: AuthState,
targetRoute: String,
object AppScreen { fallbackRoute: String = AppScreen.LANDING
const val LANDING = "landing" ) {
const val SIGN_IN = "sign_in" if (authState is AuthState.Authenticated) {
const val SIGN_UP = "sign_up" navigate(targetRoute) {
launchSingleTop = true
const val OTP = "otp" }
} else {
const val CHOOSE_SERVICE = "choose_service" navigate(fallbackRoute) {
popUpTo(0) { inclusive = true }
const val CREATE_PROFILE = "create_profile" launchSingleTop = true
}
const val BUY_ANIMALS = "buy_animals" }
const val ANIMAL_PROFILE = "animal_profile" }
const val CREATE_ANIMAL_LISTING = "create_animal_listing" @Composable
fun AppNavigation(
const val BUY_ANIMALS_FILTERS = "buy_animals_filters" authState: AuthState,
const val BUY_ANIMALS_SORT = "buy_animals_sort" mainViewModel: MainViewModel
) {
const val SELLER_PROFILE = "seller_profile" val navController = rememberNavController()
const val SALE_ARCHIVE = "sale_archive" /* ---------------------------------------------------
* Collect one-time navigation events (OTP, login, logout)
const val POST_SALE_SURVEY = "post_sale_survey" * --------------------------------------------------- */
LaunchedEffect(Unit) {
const val SAVED_LISTINGS = "saved_listings" mainViewModel.navEvents.collect { event ->
when (event) {
const val CONTACTS = "contacts"
is NavEvent.ToCreateProfile -> {
const val CALLS = "calls" navController.navigate(
AppScreen.createProfile(event.name)
const val CHAT = "chat" ) {
popUpTo(0) { inclusive = true }
const val CHATS = "chats" launchSingleTop = true
}
const val ACCOUNTS = "accounts" }
fun chats(contact: String) = is NavEvent.ToChooseService -> {
"$CHAT/$contact" navController.navigate(
AppScreen.chooseService(event.profileId)
fun otp(phone: String, name: String) = ) {
"$OTP/$phone/$name" popUpTo(0) { inclusive = true }
launchSingleTop = true
fun otpWithSignup(phone: String, name: String, state: String, district: String, village: String) = }
"$OTP/$phone/$name/$state/$district/$village" }
fun createProfile(name: String) = NavEvent.ToLanding -> {
"$CREATE_PROFILE/$name" navController.navigate(AppScreen.LANDING) {
popUpTo(0) { inclusive = true }
fun chooseService(profileId: String) = launchSingleTop = true
"$CHOOSE_SERVICE/$profileId" }
fun postSaleSurvey(animalId: String) = }
"$POST_SALE_SURVEY/$animalId" }
}
fun animalProfile(animalId: String) = }
"$ANIMAL_PROFILE/$animalId"
val onNavClick: (String) -> Unit = { route ->
fun sellerProfile(sellerId: String) = val currentRoute =
"$SELLER_PROFILE/$sellerId" navController.currentBackStackEntry?.destination?.route
Log.d("Current Route:"," $currentRoute $route")
fun saleArchive(saleId: String) =
"$SALE_ARCHIVE/$saleId" if (currentRoute != route) {
navController.navigate(route) {
} launchSingleTop = true
//restoreState = true
object Graph { // popUpTo(navController.graph.startDestinationId) {
const val AUTH = "auth" // saveState = true
const val MAIN = "main" // }
}
fun auth(route: String)= }
"$AUTH/$route" }
fun main(route: String)= /* ---------------------------------------------------
"$MAIN/$route" * Single NavHost
} * --------------------------------------------------- */
@Composable NavHost(
fun AppNavigation( navController = navController,
authState: AuthState, startDestination = AppScreen.LANDING
mainViewModel: com.example.livingai_lg.ui.MainViewModel ) {
) {
val navController = rememberNavController() /* ---------------- AUTH ---------------- */
// Determine start destination based on initial auth state composable(AppScreen.LANDING) {
// This prevents showing landing screen when user is already logged in LandingScreen(
val startDestination = remember(authState) { onSignUpClick = {
when (authState) { navController.navigate(AppScreen.SIGN_UP)
is AuthState.Authenticated -> Graph.MAIN },
is AuthState.Unauthenticated -> Graph.AUTH onSignInClick = {
is AuthState.Unknown -> Graph.AUTH // Show landing while checking navController.navigate(AppScreen.SIGN_IN)
} },
} onGuestClick = {
mainViewModel.emitNavEvent(
NavHost( NavEvent.ToCreateProfile("guest")
navController = navController, )
startDestination = startDestination }
) { )
authNavGraph(navController, mainViewModel) }
mainNavGraph(navController)
} composable(AppScreen.SIGN_IN) {
SignInScreen(
// Handle navigation based on auth state changes onSignUpClick = {
LaunchedEffect(authState) { navController.navigate(AppScreen.SIGN_UP)
android.util.Log.d("AppNavigation", "LaunchedEffect triggered with authState: $authState") },
when (authState) { onSignInClick = { phone, name ->
is AuthState.Authenticated -> { navController.navigate(
// User is authenticated, navigate to ChooseServiceScreen AppScreen.otp(phone, name)
// 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 composable(AppScreen.SIGN_UP) {
if (currentRoute?.startsWith(Graph.MAIN) != true && SignUpScreen(
currentRoute?.startsWith(AppScreen.CHOOSE_SERVICE) != true) { onSignUpClick = { phone, name, state, district, village ->
android.util.Log.d("AppNavigation", "Navigating to ChooseServiceScreen (default profileId: 1)") navController.navigate(
try { AppScreen.otpWithSignup(
// Navigate directly to the start destination route of MAIN graph phone, name, state, district, village
// 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 } onSignInClick = {
// Prevent multiple navigations navController.navigate(AppScreen.SIGN_IN)
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 composable(
try { "${AppScreen.OTP}/{phoneNumber}/{name}",
navController.navigate(Graph.MAIN) { arguments = listOf(
popUpTo(Graph.AUTH) { inclusive = true } navArgument("phoneNumber") { type = NavType.StringType },
launchSingleTop = true navArgument("name") { type = NavType.StringType }
} )
} catch (e2: Exception) { ) { backStackEntry ->
android.util.Log.e("AppNavigation", "Fallback navigation also failed: ${e2.message}", e2) OtpScreen(
} mainViewModel = mainViewModel,
} phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "",
} else { name = backStackEntry.arguments?.getString("name") ?: "",
android.util.Log.d("AppNavigation", "Already in MAIN graph or ChooseServiceScreen, skipping navigation") onCreateProfile = { name ->
} // navController.navigateIfAuthenticated(
} // authState,
is AuthState.Unauthenticated -> { // AppScreen.createProfile(name),
// User is not authenticated, ensure we're in auth graph (landing screen) // AppScreen.LANDING
val currentRoute = navController.currentBackStackEntry?.destination?.route // )
if (currentRoute?.startsWith(Graph.MAIN) == true || mainViewModel.emitNavEvent(
currentRoute?.startsWith(Graph.AUTH) != true) { NavEvent.ToCreateProfile(name)
navController.navigate(Graph.AUTH) { )
// Clear back stack to prevent going back to main screens },
popUpTo(0) { inclusive = true } onSuccess = {
launchSingleTop = true // navController.navigateIfAuthenticated(
} // authState,
} // AppScreen.chooseService(""),
} // AppScreen.LANDING
is AuthState.Unknown -> { // )
// Still checking auth status mainViewModel.emitNavEvent(
// If we're on landing screen, stay there NavEvent.ToChooseService()
// If we're on main screen and checking, don't navigate yet )
// This prevents flickering during token validation }
} )
} }
}
// MainNavGraph(navController) composable(
// AuthNavGraph(navController) "${AppScreen.OTP}/{phoneNumber}/{name}/{state}/{district}/{village}",
// when (authState) { arguments = listOf(
// is AuthState.Unauthenticated -> {AuthNavGraph()} navArgument("phoneNumber") { type = NavType.StringType },
// is AuthState.Authenticated -> {MainNavGraph()} navArgument("name") { type = NavType.StringType },
// is AuthState.Unknown -> { navArgument("state") { type = NavType.StringType },
// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { navArgument("district") { type = NavType.StringType },
// CircularProgressIndicator() navArgument("village") { type = NavType.StringType }
// } )
// } ) { backStackEntry ->
// AuthState.Loading -> SplashScreen() OtpScreen(
// } mainViewModel = mainViewModel,
phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "",
// val onNavClick: (String) -> Unit = { route -> name = backStackEntry.arguments?.getString("name") ?: "",
// val currentRoute = signupState = backStackEntry.arguments?.getString("state"),
// navController.currentBackStackEntry?.destination?.route signupDistrict = backStackEntry.arguments?.getString("district"),
// signupVillage = backStackEntry.arguments?.getString("village"),
// if (currentRoute != route) { onCreateProfile = { name ->
// navController.navigate(route) { mainViewModel.emitNavEvent(
// launchSingleTop = true NavEvent.ToCreateProfile(name)
// restoreState = true )
// popUpTo(navController.graph.startDestinationId) { },
// saveState = true onSuccess = {
// } mainViewModel.emitNavEvent(
// } NavEvent.ToChooseService()
// } )
// } }
)
}
// NavHost( /* ---------------- MAIN ---------------- */
// navController = navController,
// startDestination = AppScreen.LANDING, composable(
// "${AppScreen.CREATE_PROFILE}/{name}",
// ) { arguments = listOf(
// composable(AppScreen.LANDING) { navArgument("name") { type = NavType.StringType }
// LandingScreen( )
// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) }, ) { backStackEntry ->
// onSignInClick = { navController.navigate(AppScreen.SIGN_IN) }, CreateProfileScreen(
// onGuestClick = { navController.navigate(AppScreen.CREATE_PROFILE) } name = backStackEntry.arguments?.getString("name") ?: "",
// ) onProfileSelected = { profileId ->
// } mainViewModel.emitNavEvent(
// NavEvent.ToChooseService(profileId)
// composable(AppScreen.SIGN_IN) { )
// SignInScreen( }
// onSignUpClick = { navController.navigate(AppScreen.SIGN_UP) }, )
// onSignInClick = { }
// navController.navigate(AppScreen.OTP)
// } composable(
// ) "${AppScreen.CHOOSE_SERVICE}/{profileId}",
// } arguments = listOf(
// navArgument("profileId") { type = NavType.StringType }
// composable(AppScreen.SIGN_UP) { )
// SignUpScreen( ) { backStackEntry ->
// onSignUpClick = { ChooseServiceScreen(
// navController.navigate(AppScreen.OTP) profileId = backStackEntry.arguments?.getString("profileId") ?: "",
// }, onServiceSelected = {
// onSignInClick = { navController.navigate(AppScreen.BUY_ANIMALS)
// navController.navigate(AppScreen.SIGN_IN) { // navController.navigateIfAuthenticated(
// popUpTo(AppScreen.SIGN_UP) { inclusive = true } // authState,
// } // AppScreen.BUY_ANIMALS
// } // )
// ) }
// } )
// }
// composable(AppScreen.OTP) {
// OtpScreen( composable(AppScreen.BUY_ANIMALS) {
// onContinue = { navController.navigate(AppScreen.CREATE_PROFILE) } BuyScreen(
// ) onBackClick = {
// } navController.popBackStack()
},
// composable(AppScreen.CREATE_PROFILE) { onProductClick = { animalId ->
// CreateProfileScreen ( navController.navigate(
// onProfileSelected = { profileId -> AppScreen.animalProfile(animalId)
// if (profileId == "buyer_seller") { )
// navController.navigate(AppScreen.BUY_ANIMALS) },
// } else { onNavClick = onNavClick,
// navController.navigate(AppScreen.CHOOSE_SERVICE) onSellerClick = { sellerId ->
// } }, navController.navigate(
// ) AppScreen.sellerProfile(sellerId)
// } )
// },
// composable(AppScreen.CHOOSE_SERVICE) { )
// ChooseServiceScreen ( }
// onServiceSelected = { navController.navigate(AppScreen.LANDING) },
// ) composable(AppScreen.BUY_ANIMALS_FILTERS) {
// } FilterScreen(
// onBackClick = {
// composable(AppScreen.BUY_ANIMALS) { navController.popBackStack()
// BuyScreen( },
// onBackClick = { onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
// navController.popBackStack() onCancelClick = {
// }, navController.popBackStack()
// onProductClick = { animalId -> },
// navController.navigate( )
// AppScreen.animalProfile(animalId) }
// )
// }, composable(AppScreen.BUY_ANIMALS_SORT) {
// onNavClick = onNavClick, SortScreen(
// onFilterClick = {navController.navigate(AppScreen.BUY_ANIMALS_FILTERS)}, onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)},
// onSortClick = {navController.navigate(AppScreen.BUY_ANIMALS_SORT)}, onBackClick = {
// onSellerClick = { sellerId -> navController.popBackStack()
// navController.navigate( },
// AppScreen.sellerProfile(sellerId) onCancelClick = {
// ) navController.popBackStack()
// }, },
// )
// } )
// }
// composable(AppScreen.BUY_ANIMALS_FILTERS) {
// FilterScreen( composable(AppScreen.SAVED_LISTINGS) {
// onSubmitClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, SavedListingsScreen(
// onBackClick = { onNavClick = onNavClick,
// navController.popBackStack() onBackClick = { navController.popBackStack() })
// }, }
// onSkipClick = {
// navController.popBackStack() composable(AppScreen.ACCOUNTS) {
// }, AccountsScreen(
// onCancelClick = { onBackClick = { navController.popBackStack() },
// navController.popBackStack() onLogout = {
// }, // Navigate to auth graph after logout
// navController.navigate(AppScreen.LANDING) {
// ) popUpTo(0) { inclusive = true }
// } }
// },
// composable(AppScreen.BUY_ANIMALS_SORT) { onApiTest = {
// SortScreen( navController.navigate(AppScreen.API_TEST)
// onApplyClick = {navController.navigate(AppScreen.BUY_ANIMALS)}, }
// onBackClick = { )
// navController.popBackStack() }
// },
// onSkipClick = {
// navController.popBackStack() composable(AppScreen.CREATE_ANIMAL_LISTING) {
// }, NewListingScreen (
// onCancelClick = { onSaveClick = {navController.navigate(
// navController.popBackStack() AppScreen.postSaleSurvey("2")
// }, )},
// onBackClick = {
// ) navController.navigate(AppScreen.BUY_ANIMALS){
// } popUpTo(AppScreen.BUY_ANIMALS){
// inclusive = true
// 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 }
// composable( )
// route = "${AppScreen.SALE_ARCHIVE}/{saleId}", ) { backStackEntry ->
// arguments = listOf(
// navArgument("saleId") { type = NavType.StringType } val saleId = backStackEntry
// ) .arguments
// ) { backStackEntry -> ?.getString("saleId")
// ?: return@composable
// val saleId = backStackEntry
// .arguments SaleArchiveScreen(
// ?.getString("saleId") saleId = saleId,
// ?: return@composable onBackClick = {
// navController.popBackStack()
// SaleArchiveScreen( },
// saleId = saleId, onSaleSurveyClick = { saleId ->
// onBackClick = { navController.navigate(
// navController.popBackStack() AppScreen.sellerProfile(saleId)
// }, )
// onSaleSurveyClick = { saleId -> },
// navController.navigate( )
// AppScreen.sellerProfile(saleId) }
// )
// }, composable(
// ) route = "${AppScreen.POST_SALE_SURVEY}/{animalId}",
// } arguments = listOf(
// navArgument("animalId") { type = NavType.StringType }
// composable( )
// route = "${AppScreen.POST_SALE_SURVEY}/{animalId}", ) { backStackEntry ->
// arguments = listOf(
// navArgument("animalId") { type = NavType.StringType } val animalId = backStackEntry
// ) .arguments
// ) { backStackEntry -> ?.getString("animalId")
// ?: return@composable
// val animalId = backStackEntry
// .arguments PostSaleSurveyScreen (
// ?.getString("animalId") animalId = animalId,
// ?: return@composable onBackClick = {
// navController.navigate(AppScreen.CREATE_ANIMAL_LISTING)
// PostSaleSurveyScreen ( },
// animalId = animalId, onSubmit = {navController.navigate(
// onBackClick = { AppScreen.saleArchive("2")
// navController.popBackStack() )}
// }, )
// onSubmit = {navController.navigate( }
// AppScreen.saleArchive("2")
// )} composable(
// ) route = "${AppScreen.ANIMAL_PROFILE}/{animalId}",
// } arguments = listOf(
// navArgument("animalId") { type = NavType.StringType }
// composable( )
// route = "${AppScreen.ANIMAL_PROFILE}/{animalId}", ) { backStackEntry ->
// arguments = listOf(
// navArgument("animalId") { type = NavType.StringType } val animalId = backStackEntry
// ) .arguments
// ) { backStackEntry -> ?.getString("animalId")
// ?: return@composable
// val animalId = backStackEntry
// .arguments AnimalProfileScreen(
// ?.getString("animalId") animalId = animalId,
// ?: return@composable onBackClick = {
// navController.popBackStack()
// AnimalProfileScreen( },
// animalId = animalId, onNavClick = onNavClick,
// onBackClick = { onSellerClick = { sellerId ->
// navController.popBackStack() navController.navigate(
// }, AppScreen.sellerProfile(sellerId)
// onSellerClick = { sellerId -> )
// navController.navigate( },
// AppScreen.sellerProfile(sellerId) )
// ) }
// },
// ) composable(
// } route = "${AppScreen.SELLER_PROFILE}/{sellerId}",
// arguments = listOf(
// composable( navArgument("sellerId") { type = NavType.StringType }
// route = "${AppScreen.SELLER_PROFILE}/{sellerId}", )
// arguments = listOf( ) { backStackEntry ->
// navArgument("sellerId") { type = NavType.StringType }
// ) val sellerId = backStackEntry
// ) { backStackEntry -> .arguments
// ?.getString("sellerId")
// val sellerId = backStackEntry ?: return@composable
// .arguments
// ?.getString("sellerId") SellerProfileScreen(
// ?: return@composable sellerId = sellerId,
// onBackClick = {
// SellerProfileScreen( navController.popBackStack()
// sellerId = sellerId, }
// onBackClick = { )
// navController.popBackStack() }
// }
// ) composable(AppScreen.CONTACTS) {
// } ContactsScreen(
// composable(AppScreen.SELLER_PROFILE) { onBackClick = {navController.navigate(AppScreen.BUY_ANIMALS)},//{navController.popBackStack()},
// SellerProfileScreen ( onTabClick = onNavClick,
// onBackClick = { )
// navController.popBackStack() }
// }
// ) 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() }
)
}
}
}

View File

@ -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"
}

View File

@ -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()
}

View File

@ -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.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
// Note: CoroutineScope, Dispatchers, delay, launch still used in onCreateProfile callbacks // 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.LandingScreen
import com.example.livingai_lg.ui.screens.auth.OtpScreen 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.SignInScreen
import com.example.livingai_lg.ui.screens.auth.SignUpScreen 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( navigation(
route = Graph.AUTH, route = Graph.AUTH,
startDestination = AppScreen.LANDING startDestination = AppScreen.LANDING
@ -70,7 +68,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co
name = backStackEntry.arguments?.getString("name") ?: "", name = backStackEntry.arguments?.getString("name") ?: "",
mainViewModel = mainViewModel, mainViewModel = mainViewModel,
onCreateProfile = {name -> 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 // Navigate to main graph first without popping, then navigate to specific route
try { try {
// Navigate to main graph (this will use its start destination) // 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 } popUpTo(Graph.AUTH) { inclusive = true }
} }
} catch (e: Exception) { } 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) { } catch (e: Exception) {
android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) Log.e("AuthNavGraph", "Navigation error: ${e.message}", e)
} }
}, },
onLanding = { 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 // Navigate to landing page within AUTH graph
try { try {
navController.navigate(AppScreen.LANDING) { navController.navigate(AppScreen.LANDING) {
@ -104,11 +102,11 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co
launchSingleTop = true launchSingleTop = true
} }
} catch (e: Exception) { } 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 = { 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) // Navigate to MAIN graph which starts at ChooseServiceScreen (startDestination)
try { try {
navController.navigate(Graph.MAIN) { navController.navigate(Graph.MAIN) {
@ -117,7 +115,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co
launchSingleTop = true launchSingleTop = true
} }
} catch (e: Exception) { } 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"), signupVillage = backStackEntry.arguments?.getString("village"),
mainViewModel = mainViewModel, mainViewModel = mainViewModel,
onCreateProfile = {name -> 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 // Navigate to main graph first without popping, then navigate to specific route
try { try {
// Navigate to main graph (this will use its start destination) // 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 } popUpTo(Graph.AUTH) { inclusive = true }
} }
} catch (e: Exception) { } 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) { } catch (e: Exception) {
android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) Log.e("AuthNavGraph", "Navigation error: ${e.message}", e)
} }
}, },
onLanding = { 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 // Navigate to landing page within AUTH graph
try { try {
navController.navigate(AppScreen.LANDING) { navController.navigate(AppScreen.LANDING) {
@ -175,11 +173,11 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co
launchSingleTop = true launchSingleTop = true
} }
} catch (e: Exception) { } 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 = { 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) // Navigate to MAIN graph which starts at ChooseServiceScreen (startDestination)
try { try {
navController.navigate(Graph.MAIN) { navController.navigate(Graph.MAIN) {
@ -188,7 +186,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: co
launchSingleTop = true launchSingleTop = true
} }
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) Log.e("AuthNavGraph", "Navigation error: ${e.message}", e)
} }
} }
) )

View File

@ -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 android.util.Log
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation import androidx.navigation.compose.navigation
import androidx.navigation.navArgument 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.CallsScreen
import com.example.farmmarketplace.ui.screens.ContactsScreen 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.AccountsScreen
import com.example.livingai_lg.ui.screens.AnimalProfileScreen import com.example.livingai_lg.ui.screens.AnimalProfileScreen
import com.example.livingai_lg.ui.screens.BuyScreen import com.example.livingai_lg.ui.screens.BuyScreen
@ -137,7 +134,8 @@ fun NavGraphBuilder.mainNavGraph(navController: NavController) {
navController.navigate(Graph.AUTH) { navController.navigate(Graph.AUTH) {
popUpTo(0) { inclusive = true } popUpTo(0) { inclusive = true }
} }
} },
onApiTest = {navController.navigate(AppScreen.API_TEST)}
) )
} }

View File

@ -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()
// }
// )
// }
// }
}

View File

@ -8,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Logout import androidx.compose.material.icons.automirrored.filled.Logout
import androidx.compose.material.icons.filled.Construction
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -34,7 +35,8 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun AccountsScreen( fun AccountsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onLogout: () -> Unit onLogout: () -> Unit,
onApiTest: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() 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
)
}
}
}
} }
} }
} }

View File

@ -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.ActionPopup
import com.example.livingai_lg.ui.components.AddressSelectorOverlay import com.example.livingai_lg.ui.components.AddressSelectorOverlay
import com.example.livingai_lg.ui.components.FilterOverlay 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.NotificationsOverlay
import com.example.livingai_lg.ui.components.SortOverlay import com.example.livingai_lg.ui.components.SortOverlay
import com.example.livingai_lg.ui.models.sampleNotifications import com.example.livingai_lg.ui.models.sampleNotifications
@ -59,6 +60,9 @@ fun BuyScreen(
var showSavedPopup by remember { mutableStateOf(false) } var showSavedPopup by remember { mutableStateOf(false) }
var showNotifications by remember { mutableStateOf(false) } var showNotifications by remember { mutableStateOf(false) }
val sampleNotifications = sampleNotifications val sampleNotifications = sampleNotifications
var showInfoOverlay by remember { mutableStateOf(false) }
var selectedItemId by remember { mutableStateOf<String?>(null) }
Box( Box(
modifier = Modifier modifier = Modifier
@ -168,7 +172,9 @@ fun BuyScreen(
onSavedChange = { isSaved.value = it }, onSavedChange = { isSaved.value = it },
onProductClick = { onProductClick(animal.id)}, onProductClick = { onProductClick(animal.id)},
onSellerClick = onSellerClick, onSellerClick = onSellerClick,
onBookmarkClick = { showSavedPopup = true } onBookmarkClick = { showSavedPopup = true },
onInfoClick = { showInfoOverlay = true
selectedItemId=animal.id},
) )
Spacer(modifier = Modifier.height(16.dp)) 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( ActionPopup(
visible = showSavedPopup, visible = showSavedPopup,
text = "Saved", text = "Saved",

View File

@ -239,7 +239,7 @@ Column(
val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile
android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...") android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...")
// Refresh auth status - this will trigger navigation via AppNavigation's LaunchedEffect // Refresh auth status - this will trigger navigation via AppNavigation's LaunchedEffect
mainViewModel.refreshAuthStatus() // mainViewModel.refreshAuthStatus()
try { try {
if (needsProfile) { if (needsProfile) {
android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name")
@ -247,6 +247,7 @@ Column(
} else { } else {
// Don't manually navigate - let AppNavigation handle it // Don't manually navigate - let AppNavigation handle it
android.util.Log.d("OTPScreen", "Signup successful - auth state updated, navigation will happen automatically") android.util.Log.d("OTPScreen", "Signup successful - auth state updated, navigation will happen automatically")
onSuccess()
} }
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e)
@ -266,6 +267,7 @@ Column(
} else { } else {
// Don't manually navigate - let AppNavigation handle it // Don't manually navigate - let AppNavigation handle it
android.util.Log.d("OTPScreen", "Signup successful - auth state updated, navigation will happen automatically") android.util.Log.d("OTPScreen", "Signup successful - auth state updated, navigation will happen automatically")
onSuccess()
} }
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e)
@ -325,7 +327,12 @@ Column(
// Tokens are now saved (synchronously via commit()) // Tokens are now saved (synchronously via commit())
// Refresh auth status - this will optimistically set authState to Authenticated // Refresh auth status - this will optimistically set authState to Authenticated
// The LaunchedEffect in AppNavigation will automatically navigate to ChooseServiceScreen // 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") android.util.Log.d("OTPScreen", "Auth status refreshed - navigation will happen automatically via LaunchedEffect")
} }
.onFailure { error -> .onFailure { error ->

View File

@ -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<String?>(null) }
var errorText by remember { mutableStateOf<String?>(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)
)
}
}
}
}
}
}

View File

@ -12,6 +12,7 @@ navigationCompose = "2.7.7"
ktor = "2.3.12" ktor = "2.3.12"
kotlinxSerialization = "1.6.3" kotlinxSerialization = "1.6.3"
securityCrypto = "1.1.0-alpha06" securityCrypto = "1.1.0-alpha06"
navigationComposeJvmstubs = "2.9.6"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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
androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }