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.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<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,
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()},
)

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",
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"),

View File

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

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

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

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

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.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<String?>(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",

View File

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

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