Updated FIxed routes and Logout
This commit is contained in:
parent
95c6c598a0
commit
682ed78491
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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") {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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.)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue