From 682ed78491a02626f0ea5b7371a06029c65ef127 Mon Sep 17 00:00:00 2001 From: Chandresh Kerkar Date: Sat, 20 Dec 2025 00:39:58 +0530 Subject: [PATCH] Updated FIxed routes and Logout --- .../com/example/livingai_lg/MainActivity.kt | 2 +- .../example/livingai_lg/api/AuthApiClient.kt | 14 ++ .../example/livingai_lg/api/AuthManager.kt | 4 + .../com/example/livingai_lg/api/models.kt | 4 +- .../example/livingai_lg/ui/MainViewModel.kt | 61 ++++++++- .../ui/components/UserLocationHeader.kt | 13 +- .../ui/navigation/AppNavigation.kt | 43 ++++-- .../livingai_lg/ui/navigation/AuthNavGraph.kt | 30 +++- .../livingai_lg/ui/navigation/MainNavGraph.kt | 15 ++ .../livingai_lg/ui/screens/AccountsScreen.kt | 129 ++++++++++++++++++ .../livingai_lg/ui/screens/BuyScreen.kt | 3 + .../livingai_lg/ui/screens/auth/OTPScreen.kt | 95 ++++++++----- 12 files changed, 356 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt diff --git a/app/src/main/java/com/example/livingai_lg/MainActivity.kt b/app/src/main/java/com/example/livingai_lg/MainActivity.kt index 52878ed..3ae27fb 100644 --- a/app/src/main/java/com/example/livingai_lg/MainActivity.kt +++ b/app/src/main/java/com/example/livingai_lg/MainActivity.kt @@ -29,7 +29,7 @@ class MainActivity : ComponentActivity() { val mainViewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current)) val authState by mainViewModel.authState.collectAsState() - AppNavigation(authState) + AppNavigation(authState = authState, mainViewModel = mainViewModel) // when (authState) { // is AuthState.Unknown -> { // Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { diff --git a/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt b/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt index 549194d..de710f3 100644 --- a/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt +++ b/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt @@ -111,6 +111,20 @@ class AuthApiClient(private val context: Context) { client.get("users/me").body() } + suspend fun refreshToken(): Result = runCatching { + val refreshToken = tokenManager.getRefreshToken() + ?: throw IllegalStateException("No refresh token found") + + val response: RefreshResponse = client.post("auth/refresh") { + contentType(ContentType.Application.Json) + setBody(RefreshRequest(refreshToken)) + }.body() + + // Save the new tokens (refresh token rotates) + tokenManager.saveTokens(response.accessToken, response.refreshToken) + response + } + suspend fun logout(): Result = runCatching { val refreshToken = tokenManager.getRefreshToken() ?: throw IllegalStateException("No refresh token found") val response: LogoutResponse = client.post("auth/logout") { diff --git a/app/src/main/java/com/example/livingai_lg/api/AuthManager.kt b/app/src/main/java/com/example/livingai_lg/api/AuthManager.kt index 62a213e..faa9004 100644 --- a/app/src/main/java/com/example/livingai_lg/api/AuthManager.kt +++ b/app/src/main/java/com/example/livingai_lg/api/AuthManager.kt @@ -39,6 +39,10 @@ class AuthManager( return apiClient.updateProfile(name, userType) } + suspend fun logout(): Result { + return apiClient.logout() + } + private fun getDeviceId(): String { return Settings.Secure.getString( context.contentResolver, diff --git a/app/src/main/java/com/example/livingai_lg/api/models.kt b/app/src/main/java/com/example/livingai_lg/api/models.kt index abe17a9..333447a 100644 --- a/app/src/main/java/com/example/livingai_lg/api/models.kt +++ b/app/src/main/java/com/example/livingai_lg/api/models.kt @@ -88,8 +88,8 @@ data class User( val id: String, @SerialName("phone_number") val phoneNumber: String, val name: String?, - val role: String, - @SerialName("user_type") val userType: String?, + val role: String? = null, // Optional field - can be missing from JSON, defaults to null + @SerialName("user_type") val userType: String? = null, // Optional field - can be missing from JSON, defaults to null @SerialName("created_at") val createdAt: String? = null, @SerialName("country_code") val countryCode: String? = null ) diff --git a/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt b/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt index f2388f9..d484622 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/MainViewModel.kt @@ -40,27 +40,80 @@ class MainViewModel(context: Context) : ViewModel() { checkAuthStatus() } + /** + * Public method to refresh auth status after login/signup + * Call this after tokens are saved to update the auth state + */ + fun refreshAuthStatus() { + checkAuthStatus() + } + private fun checkAuthStatus() { viewModelScope.launch { - if (tokenManager.getAccessToken() != null) { - _authState.value = AuthState.Authenticated - fetchUserDetails() + val accessToken = tokenManager.getAccessToken() + val refreshToken = tokenManager.getRefreshToken() + + if (accessToken != null && refreshToken != null) { + // Tokens exist, validate them by fetching user details + // The Ktor Auth plugin will automatically refresh if access token is expired + validateTokens() } else { + // No tokens, user is not authenticated _authState.value = AuthState.Unauthenticated } } } + private fun validateTokens() { + viewModelScope.launch { + // Try to fetch user details - this will validate the access token + // If access token is expired, Ktor's Auth plugin will auto-refresh + authApiClient.getUserDetails() + .onSuccess { userDetails -> + // Tokens are valid, user is authenticated + _authState.value = AuthState.Authenticated + _userState.value = UserState.Success(userDetails) + } + .onFailure { error -> + // If fetching user details failed, try manual refresh + Log.d(TAG, "Failed to fetch user details, attempting token refresh: ${error.message}") + attemptTokenRefresh() + } + } + } + + private fun attemptTokenRefresh() { + viewModelScope.launch { + authApiClient.refreshToken() + .onSuccess { refreshResponse -> + // Refresh successful, tokens are valid + Log.d(TAG, "Token refresh successful") + _authState.value = AuthState.Authenticated + // Fetch user details with new token + fetchUserDetails() + } + .onFailure { error -> + // Refresh failed, tokens are invalid - clear and logout + Log.d(TAG, "Token refresh failed: ${error.message}") + tokenManager.clearTokens() + _authState.value = AuthState.Unauthenticated + _userState.value = UserState.Error("Session expired. Please sign in again.") + } + } + } + fun fetchUserDetails() { viewModelScope.launch { _userState.value = UserState.Loading authApiClient.getUserDetails() .onSuccess { _userState.value = UserState.Success(it) + _authState.value = AuthState.Authenticated } .onFailure { _userState.value = UserState.Error(it.message ?: "Unknown error") - _authState.value = AuthState.Unauthenticated + // Don't automatically set to Unauthenticated here - let the caller decide + // or try refresh if needed } } } diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt b/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt index 27fe06e..78ade0f 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/components/UserLocationHeader.kt @@ -33,7 +33,8 @@ fun UserLocationHeader( user: UserProfile, selectedAddressId: String, modifier: Modifier = Modifier, - onOpenAddressOverlay: () -> Unit + onOpenAddressOverlay: () -> Unit, + onProfileClick: () -> Unit = {} // New callback for profile icon click ) { Row( modifier = modifier.wrapContentWidth(), @@ -41,12 +42,18 @@ fun UserLocationHeader( ) { val selectedAddress = user.addresses.find { it.id == selectedAddressId } ?: user.addresses.first() - // Profile image + // Profile image - make it clickable Box( modifier = Modifier .size(48.dp) .clip(CircleShape) - .background(Color.Black), + .background(Color.Black) + .clickable( + indication = LocalIndication.current, + interactionSource = remember { MutableInteractionSource() } + ) { + onProfileClick() + }, contentAlignment = Alignment.Center ) { if (user.profileImageUrl != null) { diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt index 94cb893..de5f9c7 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt @@ -70,6 +70,8 @@ object AppScreen { const val CHATS = "chats" + const val ACCOUNTS = "accounts" + fun chats(contact: String) = "$CHAT/$contact" @@ -100,7 +102,7 @@ object AppScreen { object Graph { const val AUTH = "auth" - const val MAIN = "auth" + const val MAIN = "main" fun auth(route: String)= "$AUTH/$route" @@ -110,7 +112,8 @@ object Graph { } @Composable fun AppNavigation( - authState: AuthState + authState: AuthState, + mainViewModel: com.example.livingai_lg.ui.MainViewModel ) { val navController = rememberNavController() @@ -120,21 +123,39 @@ fun AppNavigation( navController = navController, startDestination = Graph.AUTH ) { - authNavGraph(navController) + authNavGraph(navController, mainViewModel) mainNavGraph(navController) } - // Navigate to MAIN graph if user is authenticated + // Handle navigation based on auth state LaunchedEffect(authState) { - if (authState is AuthState.Authenticated) { - val currentRoute = navController.currentBackStackEntry?.destination?.route - // Only navigate if we're not already in the MAIN graph - if (currentRoute?.startsWith(Graph.MAIN) != true) { - navController.navigate(Graph.MAIN) { - // Clear back stack to prevent going back to auth screens - popUpTo(Graph.AUTH) { inclusive = true } + when (authState) { + is AuthState.Authenticated -> { + // User is authenticated, navigate to main graph + val currentRoute = navController.currentBackStackEntry?.destination?.route + // Only navigate if we're not already in the MAIN graph + if (currentRoute?.startsWith(Graph.MAIN) != true && + currentRoute?.startsWith(Graph.AUTH) == true) { + navController.navigate(Graph.MAIN) { + // Clear back stack to prevent going back to auth screens + popUpTo(Graph.AUTH) { inclusive = true } + } } } + 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) { + navController.navigate(Graph.AUTH) { + // Clear back stack to prevent going back to main screens + popUpTo(0) { inclusive = true } + } + } + } + is AuthState.Unknown -> { + // Still checking auth status, stay on landing screen + // Don't navigate anywhere yet + } } } // MainNavGraph(navController) diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt index fe7c09d..2d5b7fd 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AuthNavGraph.kt @@ -20,7 +20,7 @@ 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) { +fun NavGraphBuilder.authNavGraph(navController: NavController, mainViewModel: com.example.livingai_lg.ui.MainViewModel) { navigation( route = Graph.AUTH, startDestination = AppScreen.LANDING @@ -67,6 +67,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController) { OtpScreen( phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "", name = backStackEntry.arguments?.getString("name") ?: "", + mainViewModel = mainViewModel, onCreateProfile = {name -> android.util.Log.d("AuthNavGraph", "Navigating to create profile with name: $name") // Navigate to main graph first without popping, then navigate to specific route @@ -92,6 +93,19 @@ fun NavGraphBuilder.authNavGraph(navController: NavController) { android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) } }, + onLanding = { + android.util.Log.d("AuthNavGraph", "Navigating to landing page for new user") + // Navigate to landing page within AUTH graph + try { + navController.navigate(AppScreen.LANDING) { + // Pop all screens up to and including the current OTP screen + popUpTo(Graph.AUTH) { inclusive = false } + launchSingleTop = true + } + } catch (e: Exception) { + android.util.Log.e("AuthNavGraph", "Navigation to landing error: ${e.message}", e) + } + }, onSuccess = { android.util.Log.d("AuthNavGraph", "Navigating to choose service") // Navigate to main graph first without popping, then navigate to specific route @@ -136,6 +150,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController) { signupState = backStackEntry.arguments?.getString("state"), signupDistrict = backStackEntry.arguments?.getString("district"), signupVillage = backStackEntry.arguments?.getString("village"), + mainViewModel = mainViewModel, onCreateProfile = {name -> android.util.Log.d("AuthNavGraph", "Navigating to create profile with name: $name") // Navigate to main graph first without popping, then navigate to specific route @@ -161,6 +176,19 @@ fun NavGraphBuilder.authNavGraph(navController: NavController) { android.util.Log.e("AuthNavGraph", "Navigation error: ${e.message}", e) } }, + onLanding = { + android.util.Log.d("AuthNavGraph", "Navigating to landing page for new user") + // Navigate to landing page within AUTH graph + try { + navController.navigate(AppScreen.LANDING) { + // Pop all screens up to and including the current OTP screen + popUpTo(Graph.AUTH) { inclusive = false } + launchSingleTop = true + } + } catch (e: Exception) { + android.util.Log.e("AuthNavGraph", "Navigation to landing error: ${e.message}", e) + } + }, onSuccess = { android.util.Log.d("AuthNavGraph", "Navigating to choose service") // Navigate to main graph first without popping, then navigate to specific route diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt index 3d09a54..7a825c2 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/MainNavGraph.kt @@ -8,9 +8,12 @@ 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.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 @@ -126,6 +129,18 @@ fun NavGraphBuilder.mainNavGraph(navController: NavController) { onBackClick = { navController.popBackStack() }) } + composable(AppScreen.ACCOUNTS) { + AccountsScreen( + onBackClick = { navController.popBackStack() }, + onLogout = { + // Navigate to auth graph after logout + navController.navigate(Graph.AUTH) { + popUpTo(0) { inclusive = true } + } + } + ) + } + composable(AppScreen.CREATE_ANIMAL_LISTING) { NewListingScreen ( diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt new file mode 100644 index 0000000..ae6c7d2 --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/AccountsScreen.kt @@ -0,0 +1,129 @@ +package com.example.livingai_lg.ui.screens + +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.material.icons.automirrored.filled.Logout +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.livingai_lg.api.AuthApiClient +import com.example.livingai_lg.api.AuthManager +import com.example.livingai_lg.api.TokenManager +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountsScreen( + onBackClick: () -> Unit, + onLogout: () -> Unit +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val authManager = remember { AuthManager(context, AuthApiClient(context), TokenManager(context)) } + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF7F4EE)) + ) { + // Top App Bar + TopAppBar( + title = { + Text( + text = "Accounts", + 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) + ) + ) + + // Content + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 24.dp) + ) { + // Logout option + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable { + scope.launch { + authManager.logout() + .onSuccess { _: com.example.livingai_lg.api.LogoutResponse -> + Toast.makeText(context, "Logged out successfully", Toast.LENGTH_SHORT).show() + onLogout() + } + .onFailure { error: Throwable -> + Toast.makeText(context, "Logout failed: ${error.message}", Toast.LENGTH_SHORT).show() + // Still call onLogout to navigate away even if API call fails + onLogout() + } + } + }, + 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.AutoMirrored.Filled.Logout, + contentDescription = "Logout", + tint = Color(0xFFE53935), + modifier = Modifier.size(24.dp) + ) + Text( + text = "Log Out", + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFFE53935) + ) + } + } + } + } + } +} + diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt index dc3ce7a..9ab53f7 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/BuyScreen.kt @@ -94,6 +94,9 @@ fun BuyScreen( user = userProfile, onOpenAddressOverlay = { showAddressSelector = true }, selectedAddressId = selectedAddressId?:"1", + onProfileClick = { + onNavClick(AppScreen.ACCOUNTS) + } ) // Right-side actions (notifications, etc.) diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt index a3a61fc..1f47563 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/OTPScreen.kt @@ -48,8 +48,10 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent fun OtpScreen( phoneNumber: String, name: String, + mainViewModel: com.example.livingai_lg.ui.MainViewModel, onSuccess: () -> Unit = {}, onCreateProfile: (name: String) -> Unit = {}, + onLanding: () -> Unit = {}, // New callback for navigating to landing page // Optional signup data for signup flow signupState: String? = null, signupDistrict: String? = null, @@ -164,6 +166,8 @@ Column( .onSuccess { verifyResponse -> android.util.Log.d("OTPScreen", "OTP verified successfully. Calling signup API...") // OTP verified successfully - user is now logged in + // Refresh auth status in MainViewModel so AppNavigation knows user is authenticated + mainViewModel.refreshAuthStatus() // Now call signup API to update user with name and location val signupRequest = SignupRequest( name = name, @@ -174,57 +178,76 @@ Column( ) authManager.signup(signupRequest) .onSuccess { signupResponse -> - android.util.Log.d("OTPScreen", "Signup API response: success=${signupResponse.success}, userExists=${signupResponse.userExists}, needsProfile=${signupResponse.needsProfile}") - // Signup API response - check if successful or user exists - if (signupResponse.success || signupResponse.userExists == true) { - // Success - user is created/updated and logged in - // Check if profile needs completion - val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile - android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...") + android.util.Log.d("OTPScreen", "Signup API response: success=${signupResponse.success}, userExists=${signupResponse.userExists}, needsProfile=${signupResponse.needsProfile}, isNewAccount=${signupResponse.isNewAccount}") + // Check if this is a new user account + val isNewUser = signupResponse.isNewAccount == true + + if (isNewUser) { + // New user - navigate to landing page + android.util.Log.d("OTPScreen", "New user detected - navigating to landing page") try { - if (needsProfile) { - android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") - onCreateProfile(name) - } else { - android.util.Log.d("OTPScreen", "Navigating to success screen") - onSuccess() - } + onLanding() } catch (e: Exception) { android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) Toast.makeText(context, "Navigation error: ${e.message}", Toast.LENGTH_LONG).show() } } else { - // Signup failed but OTP was verified - user is logged in - // Navigate to success anyway since verify-otp succeeded - android.util.Log.d("OTPScreen", "Signup API returned false, but OTP verified. Navigating anyway...") - val needsProfile = verifyResponse.needsProfile - try { - if (needsProfile) { - android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") - onCreateProfile(name) - } else { - android.util.Log.d("OTPScreen", "Navigating to success screen") - onSuccess() + // Existing user or signup update - use existing logic + if (signupResponse.success || signupResponse.userExists == true) { + // Success - user is created/updated and logged in + // Check if profile needs completion + val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile + android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...") + try { + if (needsProfile) { + android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") + onCreateProfile(name) + } else { + android.util.Log.d("OTPScreen", "Navigating to success screen") + onSuccess() + } + } catch (e: Exception) { + android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) + Toast.makeText(context, "Navigation error: ${e.message}", Toast.LENGTH_LONG).show() + } + } else { + // Signup failed but OTP was verified - user is logged in + // Navigate to success anyway since verify-otp succeeded + android.util.Log.d("OTPScreen", "Signup API returned false, but OTP verified. Navigating anyway...") + val needsProfile = verifyResponse.needsProfile + try { + if (needsProfile) { + android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") + onCreateProfile(name) + } else { + android.util.Log.d("OTPScreen", "Navigating to success screen") + onSuccess() + } + } catch (e: Exception) { + android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) + Toast.makeText(context, "Navigation error: ${e.message}", Toast.LENGTH_LONG).show() + } + // Show warning if signup update failed + val errorMsg = signupResponse.message + if (errorMsg != null) { + Toast.makeText(context, "Profile update: $errorMsg", Toast.LENGTH_SHORT).show() } - } catch (e: Exception) { - android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) - Toast.makeText(context, "Navigation error: ${e.message}", Toast.LENGTH_LONG).show() - } - // Show warning if signup update failed - val errorMsg = signupResponse.message - if (errorMsg != null) { - Toast.makeText(context, "Profile update: $errorMsg", Toast.LENGTH_SHORT).show() } } } .onFailure { signupError -> android.util.Log.e("OTPScreen", "Signup API failed: ${signupError.message}", signupError) // Signup API failed but OTP was verified - user is logged in - // Navigate to success anyway since verify-otp succeeded + // For new users, navigate to landing page + // For existing users, use existing logic val needsProfile = verifyResponse.needsProfile android.util.Log.d("OTPScreen", "Navigating despite signup failure. needsProfile=$needsProfile") try { - if (needsProfile) { + // If this is a signup flow and signup failed, treat as new user + if (isSignupFlow) { + android.util.Log.d("OTPScreen", "Signup flow failed - navigating to landing page") + onLanding() + } else if (needsProfile) { android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") onCreateProfile(name) } else { @@ -253,6 +276,8 @@ Column( authManager.login(phoneNumber, otp.value) .onSuccess { response -> android.util.Log.d("OTPScreen", "Sign-in OTP verified. needsProfile=${response.needsProfile}") + // Refresh auth status in MainViewModel so AppNavigation knows user is authenticated + mainViewModel.refreshAuthStatus() try { if (isSignInFlow) { // For existing users, always go to the success screen.