Updated FIxed routes and Logout

This commit is contained in:
Chandresh Kerkar 2025-12-20 00:39:58 +05:30
parent 95c6c598a0
commit 682ed78491
12 changed files with 356 additions and 57 deletions

View File

@ -29,7 +29,7 @@ class MainActivity : ComponentActivity() {
val mainViewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current)) val mainViewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current))
val authState by mainViewModel.authState.collectAsState() val authState by mainViewModel.authState.collectAsState()
AppNavigation(authState) AppNavigation(authState = authState, mainViewModel = mainViewModel)
// when (authState) { // when (authState) {
// is AuthState.Unknown -> { // is AuthState.Unknown -> {
// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { // Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {

View File

@ -111,6 +111,20 @@ class AuthApiClient(private val context: Context) {
client.get("users/me").body() client.get("users/me").body()
} }
suspend fun refreshToken(): Result<RefreshResponse> = 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<LogoutResponse> = runCatching { suspend fun logout(): Result<LogoutResponse> = runCatching {
val refreshToken = tokenManager.getRefreshToken() ?: throw IllegalStateException("No refresh token found") val refreshToken = tokenManager.getRefreshToken() ?: throw IllegalStateException("No refresh token found")
val response: LogoutResponse = client.post("auth/logout") { val response: LogoutResponse = client.post("auth/logout") {

View File

@ -39,6 +39,10 @@ class AuthManager(
return apiClient.updateProfile(name, userType) return apiClient.updateProfile(name, userType)
} }
suspend fun logout(): Result<com.example.livingai_lg.api.LogoutResponse> {
return apiClient.logout()
}
private fun getDeviceId(): String { private fun getDeviceId(): String {
return Settings.Secure.getString( return Settings.Secure.getString(
context.contentResolver, context.contentResolver,

View File

@ -88,8 +88,8 @@ data class User(
val id: String, val id: String,
@SerialName("phone_number") val phoneNumber: String, @SerialName("phone_number") val phoneNumber: String,
val name: String?, val name: String?,
val role: String, val role: String? = null, // Optional field - can be missing from JSON, defaults to null
@SerialName("user_type") val userType: String?, @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("created_at") val createdAt: String? = null,
@SerialName("country_code") val countryCode: String? = null @SerialName("country_code") val countryCode: String? = null
) )

View File

@ -40,27 +40,80 @@ class MainViewModel(context: Context) : ViewModel() {
checkAuthStatus() 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() { private fun checkAuthStatus() {
viewModelScope.launch { viewModelScope.launch {
if (tokenManager.getAccessToken() != null) { val accessToken = tokenManager.getAccessToken()
_authState.value = AuthState.Authenticated val refreshToken = tokenManager.getRefreshToken()
fetchUserDetails()
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 { } else {
// No tokens, user is not authenticated
_authState.value = AuthState.Unauthenticated _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() { fun fetchUserDetails() {
viewModelScope.launch { viewModelScope.launch {
_userState.value = UserState.Loading _userState.value = UserState.Loading
authApiClient.getUserDetails() authApiClient.getUserDetails()
.onSuccess { .onSuccess {
_userState.value = UserState.Success(it) _userState.value = UserState.Success(it)
_authState.value = AuthState.Authenticated
} }
.onFailure { .onFailure {
_userState.value = UserState.Error(it.message ?: "Unknown error") _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
} }
} }
} }

View File

@ -33,7 +33,8 @@ fun UserLocationHeader(
user: UserProfile, user: UserProfile,
selectedAddressId: String, selectedAddressId: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onOpenAddressOverlay: () -> Unit onOpenAddressOverlay: () -> Unit,
onProfileClick: () -> Unit = {} // New callback for profile icon click
) { ) {
Row( Row(
modifier = modifier.wrapContentWidth(), modifier = modifier.wrapContentWidth(),
@ -41,12 +42,18 @@ fun UserLocationHeader(
) { ) {
val selectedAddress = user.addresses.find { it.id == selectedAddressId } ?: user.addresses.first() val selectedAddress = user.addresses.find { it.id == selectedAddressId } ?: user.addresses.first()
// Profile image // Profile image - make it clickable
Box( Box(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.clip(CircleShape) .clip(CircleShape)
.background(Color.Black), .background(Color.Black)
.clickable(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
) {
onProfileClick()
},
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
if (user.profileImageUrl != null) { if (user.profileImageUrl != null) {

View File

@ -70,6 +70,8 @@ object AppScreen {
const val CHATS = "chats" const val CHATS = "chats"
const val ACCOUNTS = "accounts"
fun chats(contact: String) = fun chats(contact: String) =
"$CHAT/$contact" "$CHAT/$contact"
@ -100,7 +102,7 @@ object AppScreen {
object Graph { object Graph {
const val AUTH = "auth" const val AUTH = "auth"
const val MAIN = "auth" const val MAIN = "main"
fun auth(route: String)= fun auth(route: String)=
"$AUTH/$route" "$AUTH/$route"
@ -110,7 +112,8 @@ object Graph {
} }
@Composable @Composable
fun AppNavigation( fun AppNavigation(
authState: AuthState authState: AuthState,
mainViewModel: com.example.livingai_lg.ui.MainViewModel
) { ) {
val navController = rememberNavController() val navController = rememberNavController()
@ -120,21 +123,39 @@ fun AppNavigation(
navController = navController, navController = navController,
startDestination = Graph.AUTH startDestination = Graph.AUTH
) { ) {
authNavGraph(navController) authNavGraph(navController, mainViewModel)
mainNavGraph(navController) mainNavGraph(navController)
} }
// Navigate to MAIN graph if user is authenticated // Handle navigation based on auth state
LaunchedEffect(authState) { LaunchedEffect(authState) {
if (authState is AuthState.Authenticated) { when (authState) {
val currentRoute = navController.currentBackStackEntry?.destination?.route is AuthState.Authenticated -> {
// Only navigate if we're not already in the MAIN graph // User is authenticated, navigate to main graph
if (currentRoute?.startsWith(Graph.MAIN) != true) { val currentRoute = navController.currentBackStackEntry?.destination?.route
navController.navigate(Graph.MAIN) { // Only navigate if we're not already in the MAIN graph
// Clear back stack to prevent going back to auth screens if (currentRoute?.startsWith(Graph.MAIN) != true &&
popUpTo(Graph.AUTH) { inclusive = 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) // MainNavGraph(navController)

View File

@ -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.SignInScreen
import com.example.livingai_lg.ui.screens.auth.SignUpScreen 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( navigation(
route = Graph.AUTH, route = Graph.AUTH,
startDestination = AppScreen.LANDING startDestination = AppScreen.LANDING
@ -67,6 +67,7 @@ fun NavGraphBuilder.authNavGraph(navController: NavController) {
OtpScreen( OtpScreen(
phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "", phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "",
name = backStackEntry.arguments?.getString("name") ?: "", name = backStackEntry.arguments?.getString("name") ?: "",
mainViewModel = mainViewModel,
onCreateProfile = {name -> onCreateProfile = {name ->
android.util.Log.d("AuthNavGraph", "Navigating to create profile with name: $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 // 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) 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 = { onSuccess = {
android.util.Log.d("AuthNavGraph", "Navigating to choose service") android.util.Log.d("AuthNavGraph", "Navigating to choose service")
// Navigate to main graph first without popping, then navigate to specific route // 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"), signupState = backStackEntry.arguments?.getString("state"),
signupDistrict = backStackEntry.arguments?.getString("district"), signupDistrict = backStackEntry.arguments?.getString("district"),
signupVillage = backStackEntry.arguments?.getString("village"), signupVillage = backStackEntry.arguments?.getString("village"),
mainViewModel = mainViewModel,
onCreateProfile = {name -> onCreateProfile = {name ->
android.util.Log.d("AuthNavGraph", "Navigating to create profile with name: $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 // 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) 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 = { onSuccess = {
android.util.Log.d("AuthNavGraph", "Navigating to choose service") android.util.Log.d("AuthNavGraph", "Navigating to choose service")
// Navigate to main graph first without popping, then navigate to specific route // Navigate to main graph first without popping, then navigate to specific route

View File

@ -8,9 +8,12 @@ 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.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.AnimalProfileScreen
import com.example.livingai_lg.ui.screens.BuyScreen import com.example.livingai_lg.ui.screens.BuyScreen
import com.example.livingai_lg.ui.screens.ChooseServiceScreen import com.example.livingai_lg.ui.screens.ChooseServiceScreen
@ -126,6 +129,18 @@ fun NavGraphBuilder.mainNavGraph(navController: NavController) {
onBackClick = { navController.popBackStack() }) 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) { composable(AppScreen.CREATE_ANIMAL_LISTING) {
NewListingScreen ( NewListingScreen (

View File

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

View File

@ -94,6 +94,9 @@ fun BuyScreen(
user = userProfile, user = userProfile,
onOpenAddressOverlay = { showAddressSelector = true }, onOpenAddressOverlay = { showAddressSelector = true },
selectedAddressId = selectedAddressId?:"1", selectedAddressId = selectedAddressId?:"1",
onProfileClick = {
onNavClick(AppScreen.ACCOUNTS)
}
) )
// Right-side actions (notifications, etc.) // Right-side actions (notifications, etc.)

View File

@ -48,8 +48,10 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
fun OtpScreen( fun OtpScreen(
phoneNumber: String, phoneNumber: String,
name: String, name: String,
mainViewModel: com.example.livingai_lg.ui.MainViewModel,
onSuccess: () -> Unit = {}, onSuccess: () -> Unit = {},
onCreateProfile: (name: String) -> Unit = {}, onCreateProfile: (name: String) -> Unit = {},
onLanding: () -> Unit = {}, // New callback for navigating to landing page
// Optional signup data for signup flow // Optional signup data for signup flow
signupState: String? = null, signupState: String? = null,
signupDistrict: String? = null, signupDistrict: String? = null,
@ -164,6 +166,8 @@ Column(
.onSuccess { verifyResponse -> .onSuccess { verifyResponse ->
android.util.Log.d("OTPScreen", "OTP verified successfully. Calling signup API...") android.util.Log.d("OTPScreen", "OTP verified successfully. Calling signup API...")
// OTP verified successfully - user is now logged in // 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 // Now call signup API to update user with name and location
val signupRequest = SignupRequest( val signupRequest = SignupRequest(
name = name, name = name,
@ -174,57 +178,76 @@ Column(
) )
authManager.signup(signupRequest) authManager.signup(signupRequest)
.onSuccess { signupResponse -> .onSuccess { signupResponse ->
android.util.Log.d("OTPScreen", "Signup API response: success=${signupResponse.success}, userExists=${signupResponse.userExists}, needsProfile=${signupResponse.needsProfile}") android.util.Log.d("OTPScreen", "Signup API response: success=${signupResponse.success}, userExists=${signupResponse.userExists}, needsProfile=${signupResponse.needsProfile}, isNewAccount=${signupResponse.isNewAccount}")
// Signup API response - check if successful or user exists // Check if this is a new user account
if (signupResponse.success || signupResponse.userExists == true) { val isNewUser = signupResponse.isNewAccount == true
// Success - user is created/updated and logged in
// Check if profile needs completion if (isNewUser) {
val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile // New user - navigate to landing page
android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...") android.util.Log.d("OTPScreen", "New user detected - navigating to landing page")
try { try {
if (needsProfile) { onLanding()
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) { } catch (e: Exception) {
android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e) android.util.Log.e("OTPScreen", "Navigation error: ${e.message}", e)
Toast.makeText(context, "Navigation error: ${e.message}", Toast.LENGTH_LONG).show() Toast.makeText(context, "Navigation error: ${e.message}", Toast.LENGTH_LONG).show()
} }
} else { } else {
// Signup failed but OTP was verified - user is logged in // Existing user or signup update - use existing logic
// Navigate to success anyway since verify-otp succeeded if (signupResponse.success || signupResponse.userExists == true) {
android.util.Log.d("OTPScreen", "Signup API returned false, but OTP verified. Navigating anyway...") // Success - user is created/updated and logged in
val needsProfile = verifyResponse.needsProfile // Check if profile needs completion
try { val needsProfile = signupResponse.needsProfile == true || verifyResponse.needsProfile
if (needsProfile) { android.util.Log.d("OTPScreen", "Signup successful. needsProfile=$needsProfile, navigating...")
android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name") try {
onCreateProfile(name) if (needsProfile) {
} else { android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name")
android.util.Log.d("OTPScreen", "Navigating to success screen") onCreateProfile(name)
onSuccess() } 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 -> .onFailure { signupError ->
android.util.Log.e("OTPScreen", "Signup API failed: ${signupError.message}", signupError) android.util.Log.e("OTPScreen", "Signup API failed: ${signupError.message}", signupError)
// Signup API failed but OTP was verified - user is logged in // 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 val needsProfile = verifyResponse.needsProfile
android.util.Log.d("OTPScreen", "Navigating despite signup failure. needsProfile=$needsProfile") android.util.Log.d("OTPScreen", "Navigating despite signup failure. needsProfile=$needsProfile")
try { 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") android.util.Log.d("OTPScreen", "Navigating to create profile screen with name: $name")
onCreateProfile(name) onCreateProfile(name)
} else { } else {
@ -253,6 +276,8 @@ Column(
authManager.login(phoneNumber, otp.value) authManager.login(phoneNumber, otp.value)
.onSuccess { response -> .onSuccess { response ->
android.util.Log.d("OTPScreen", "Sign-in OTP verified. needsProfile=${response.needsProfile}") 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 { try {
if (isSignInFlow) { if (isSignInFlow) {
// For existing users, always go to the success screen. // For existing users, always go to the success screen.