package com.tt.honeyDue import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.tt.honeyDue.ui.screens.AddResidenceScreen import com.tt.honeyDue.ui.screens.EditResidenceScreen import com.tt.honeyDue.ui.screens.EditTaskScreen import com.tt.honeyDue.ui.screens.ForgotPasswordScreen import com.tt.honeyDue.ui.screens.HomeScreen import com.tt.honeyDue.ui.screens.LoginScreen import com.tt.honeyDue.ui.screens.RegisterScreen import com.tt.honeyDue.ui.screens.ResetPasswordScreen import com.tt.honeyDue.ui.screens.ResidenceDetailScreen import com.tt.honeyDue.ui.screens.ResidencesScreen import com.tt.honeyDue.ui.screens.TasksScreen import com.tt.honeyDue.ui.screens.VerifyEmailScreen import com.tt.honeyDue.ui.screens.VerifyResetCodeScreen import com.tt.honeyDue.ui.screens.onboarding.OnboardingScreen import com.tt.honeyDue.ui.screens.subscription.FeatureComparisonScreen import com.tt.honeyDue.ui.screens.task.AddTaskWithResidenceScreen import com.tt.honeyDue.ui.screens.task.TaskSuggestionsScreen import com.tt.honeyDue.ui.screens.task.TaskTemplatesBrowserScreen import com.tt.honeyDue.ui.screens.theme.ThemeSelectionScreen import com.tt.honeyDue.viewmodel.OnboardingViewModel import com.tt.honeyDue.viewmodel.PasswordResetViewModel import androidx.lifecycle.viewmodel.compose.viewModel import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.ui.tooling.preview.Preview import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.composable import androidx.navigation.toRoute import com.tt.honeyDue.ui.screens.MainScreen import com.tt.honeyDue.ui.screens.ManageUsersScreen import com.tt.honeyDue.ui.screens.NotificationPreferencesScreen import com.tt.honeyDue.ui.screens.ProfileScreen import com.tt.honeyDue.ui.theme.HoneyDueTheme import com.tt.honeyDue.ui.theme.ThemeManager import com.tt.honeyDue.navigation.* import com.tt.honeyDue.repository.LookupsRepository import com.tt.honeyDue.models.Residence import com.tt.honeyDue.models.TaskCategory import com.tt.honeyDue.models.TaskDetail import com.tt.honeyDue.models.TaskFrequency import com.tt.honeyDue.models.TaskPriority import com.tt.honeyDue.network.ApiResult import com.tt.honeyDue.network.AuthApi import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.network.APILayer import com.tt.honeyDue.platform.ContractorImportHandler import com.tt.honeyDue.platform.PlatformUpgradeScreen import com.tt.honeyDue.platform.ResidenceImportHandler import com.tt.honeyDue.storage.BiometricPreference import com.tt.honeyDue.ui.screens.BiometricLockScreen import androidx.compose.runtime.saveable.rememberSaveable import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import honeydue.composeapp.generated.resources.Res import honeydue.composeapp.generated.resources.compose_multiplatform @Composable @Preview fun App( deepLinkResetToken: String? = null, onClearDeepLinkToken: () -> Unit = {}, navigateToTaskId: Int? = null, onClearNavigateToTask: () -> Unit = {}, pendingContractorImportUri: Any? = null, onClearContractorImport: () -> Unit = {}, pendingResidenceImportUri: Any? = null, onClearResidenceImport: () -> Unit = {} ) { var isLoggedIn by remember { mutableStateOf(DataManager.authToken.value != null) } var isVerified by remember { mutableStateOf(false) } var isCheckingAuth by remember { mutableStateOf(true) } var hasCompletedOnboarding by remember { mutableStateOf(DataManager.hasCompletedOnboarding.value) } val navController = rememberNavController() // Biometric lock state - starts locked if biometric is enabled and user is logged in var isLocked by rememberSaveable { mutableStateOf(BiometricPreference.isBiometricEnabled() && DataManager.authToken.value != null) } // Lock the app when returning from background val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP) { // App going to background - lock if biometric is enabled if (BiometricPreference.isBiometricEnabled() && DataManager.authToken.value != null) { isLocked = true } } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } // Handle navigation from notification tap // Note: The actual navigation to the task column happens in MainScreen -> AllTasksScreen // We just need to ensure the user is on MainRoute when a task navigation is requested LaunchedEffect(navigateToTaskId) { if (navigateToTaskId != null && isLoggedIn && isVerified) { // Ensure we're on the main screen - MainScreen will handle navigating to the tasks tab navController.navigate(MainRoute) { popUpTo(MainRoute) { inclusive = true } } } } // Check for stored token and verification status on app start LaunchedEffect(Unit) { val hasToken = DataManager.authToken.value != null isLoggedIn = hasToken if (hasToken) { // Fetch current user to check verification status when (val result = APILayer.getCurrentUser(forceRefresh = true)) { is ApiResult.Success -> { isVerified = result.data.verified APILayer.initializeLookups() } else -> { // If fetching user fails, clear DataManager and logout DataManager.clear() isLoggedIn = false } } } isCheckingAuth = false } val currentTheme by remember { derivedStateOf { ThemeManager.currentTheme } } val useDynamicColor by remember { derivedStateOf { ThemeManager.useDynamicColor } } HoneyDueTheme(themeColors = currentTheme, useDynamicColor = useDynamicColor) { // Handle contractor file imports (Android-specific, no-op on other platforms) ContractorImportHandler( pendingContractorImportUri = pendingContractorImportUri, onClearContractorImport = onClearContractorImport ) // Handle residence file imports (Android-specific, no-op on other platforms) ResidenceImportHandler( pendingResidenceImportUri = pendingResidenceImportUri, onClearResidenceImport = onClearResidenceImport ) if (isCheckingAuth) { // Show loading screen while checking auth Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } return@HoneyDueTheme } val startDestination = when { deepLinkResetToken != null -> ForgotPasswordRoute !hasCompletedOnboarding -> OnboardingRoute !isLoggedIn -> LoginRoute !isVerified -> VerifyEmailRoute else -> MainRoute } Box(modifier = Modifier.fillMaxSize()) { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { NavHost( navController = navController, startDestination = startDestination ) { composable { val onboardingViewModel: OnboardingViewModel = viewModel { OnboardingViewModel() } OnboardingScreen( viewModel = onboardingViewModel, onComplete = { // Mark onboarding as complete DataManager.setHasCompletedOnboarding(true) hasCompletedOnboarding = true isLoggedIn = true isVerified = true // Note: Lookups are already initialized by APILayer during login/register // Navigate to main screen navController.navigate(MainRoute) { popUpTo { inclusive = true } } }, onLoginSuccess = { verified -> // User logged in through onboarding login dialog DataManager.setHasCompletedOnboarding(true) hasCompletedOnboarding = true isLoggedIn = true isVerified = verified // Note: Lookups are already initialized by APILayer.login() if (verified) { navController.navigate(MainRoute) { popUpTo { inclusive = true } } } else { navController.navigate(VerifyEmailRoute) { popUpTo { inclusive = true } } } } ) } composable { LoginScreen( onLoginSuccess = { user -> isLoggedIn = true isVerified = user.verified // Note: Lookups are already initialized by APILayer.login() // Check if user is verified if (user.verified) { navController.navigate(MainRoute) { popUpTo { inclusive = true } } } else { navController.navigate(VerifyEmailRoute) { popUpTo { inclusive = true } } } }, onNavigateToRegister = { navController.navigate(RegisterRoute) }, onNavigateToForgotPassword = { navController.navigate(ForgotPasswordRoute) } ) } composable { RegisterScreen( onRegisterSuccess = { isLoggedIn = true isVerified = false // Note: Lookups are already initialized by APILayer.register() navController.navigate(VerifyEmailRoute) { popUpTo { inclusive = true } } }, onNavigateBack = { navController.popBackStack() } ) } composable { backStackEntry -> // Create shared ViewModel for all password reset screens val parentEntry = remember(backStackEntry) { navController.getBackStackEntry() } val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel(deepLinkToken = deepLinkResetToken) } ForgotPasswordScreen( onNavigateBack = { // Clear deep link token when navigating back to login onClearDeepLinkToken() navController.popBackStack() }, onNavigateToVerify = { navController.navigate(VerifyResetCodeRoute) }, onNavigateToReset = { navController.navigate(ResetPasswordRoute) }, viewModel = passwordResetViewModel ) } composable { backStackEntry -> // Use shared ViewModel from ForgotPasswordRoute val parentEntry = remember(backStackEntry) { navController.getBackStackEntry() } val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() } VerifyResetCodeScreen( onNavigateBack = { navController.popBackStack() }, onNavigateToReset = { navController.navigate(ResetPasswordRoute) }, viewModel = passwordResetViewModel ) } composable { backStackEntry -> // Use shared ViewModel from ForgotPasswordRoute val parentEntry = remember(backStackEntry) { navController.getBackStackEntry() } val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() } // Set up auto-login callback LaunchedEffect(Unit) { passwordResetViewModel.onLoginSuccess = { verified -> isLoggedIn = true isVerified = verified onClearDeepLinkToken() // Navigate directly to main app or verification screen if (verified) { navController.navigate(MainRoute) { popUpTo { inclusive = true } } } else { navController.navigate(VerifyEmailRoute) { popUpTo { inclusive = true } } } } } ResetPasswordScreen( onPasswordResetSuccess = { // Fallback: manual return to login (only shown if auto-login fails) onClearDeepLinkToken() navController.navigate(LoginRoute) { popUpTo { inclusive = true } } }, onNavigateBack = { navController.popBackStack() }, viewModel = passwordResetViewModel ) } composable { VerifyEmailScreen( onVerifySuccess = { isVerified = true navController.navigate(MainRoute) { popUpTo { inclusive = true } } }, onLogout = { // Clear token and lookups on logout DataManager.clear() isLoggedIn = false isVerified = false navController.navigate(LoginRoute) { popUpTo { inclusive = true } } } ) } composable { MainScreen( onLogout = { // Clear token and lookups on logout DataManager.clear() isLoggedIn = false isVerified = false navController.navigate(LoginRoute) { popUpTo { inclusive = true } } }, onResidenceClick = { residenceId -> navController.navigate(ResidenceDetailRoute(residenceId)) }, onAddResidence = { navController.navigate(AddResidenceRoute) }, onAddTask = { // Tasks are added from within a residence // Navigate to first residence or show message if no residences exist // For now, this will be handled by the UI showing "add a property first" }, navigateToTaskId = navigateToTaskId, onClearNavigateToTask = onClearNavigateToTask, onNavigateToEditResidence = { residence -> navController.navigate( EditResidenceRoute( residenceId = residence.id, name = residence.name, propertyType = residence.propertyTypeId, streetAddress = residence.streetAddress, apartmentUnit = residence.apartmentUnit, city = residence.city, stateProvince = residence.stateProvince, postalCode = residence.postalCode, country = residence.country, bedrooms = residence.bedrooms, bathrooms = residence.bathrooms?.toFloat(), squareFootage = residence.squareFootage, lotSize = residence.lotSize?.toFloat(), yearBuilt = residence.yearBuilt, description = residence.description, isPrimary = residence.isPrimary, ownerUserName = residence.ownerUsername, createdAt = residence.createdAt, updatedAt = residence.updatedAt, owner = residence.ownerId ) ) }, onNavigateToEditTask = { task -> navController.navigate( EditTaskRoute( taskId = task.id, residenceId = task.residenceId, title = task.title, description = task.description, categoryId = task.category?.id ?: 0, categoryName = task.category?.name ?: "", frequencyId = task.frequency?.id ?: 0, frequencyName = task.frequency?.name ?: "", priorityId = task.priority?.id ?: 0, priorityName = task.priority?.name ?: "", inProgress = task.inProgress, dueDate = task.dueDate, estimatedCost = task.estimatedCost?.toString(), createdAt = task.createdAt, updatedAt = task.updatedAt ) ) } ) } composable { HomeScreen( onNavigateToResidences = { navController.navigate(MainRoute) }, onNavigateToTasks = { navController.navigate(TasksRoute) }, onLogout = { // Clear token and lookups on logout DataManager.clear() isLoggedIn = false isVerified = false navController.navigate(LoginRoute) { popUpTo { inclusive = true } } } ) } composable { backStackEntry -> // Get refresh flag from saved state (set when returning from add/edit) val shouldRefresh = backStackEntry.savedStateHandle.get("refresh") ?: false ResidencesScreen( onResidenceClick = { residenceId -> navController.navigate(ResidenceDetailRoute(residenceId)) }, onAddResidence = { navController.navigate(AddResidenceRoute) }, onJoinResidence = { navController.navigate(JoinResidenceRoute) }, onNavigateToProfile = { navController.navigate(ProfileRoute) }, shouldRefresh = shouldRefresh, onLogout = { // Clear token and lookups on logout DataManager.clear() isLoggedIn = false isVerified = false navController.navigate(LoginRoute) { popUpTo { inclusive = true } } } ) } composable { com.tt.honeyDue.ui.screens.residence.JoinResidenceScreen( onNavigateBack = { navController.popBackStack() }, onJoined = { residenceId -> navController.popBackStack() navController.navigate(ResidenceDetailRoute(residenceId)) }, ) } composable { AddResidenceScreen( onNavigateBack = { navController.popBackStack() }, onResidenceCreated = { // Set refresh flag before navigating back navController.previousBackStackEntry?.savedStateHandle?.set("refresh", true) navController.popBackStack() } ) } composable { backStackEntry -> val route = backStackEntry.toRoute() EditResidenceScreen( residence = Residence( id = route.residenceId, ownerId = route.owner ?: 0, name = route.name, propertyTypeId = route.propertyType, streetAddress = route.streetAddress ?: "", apartmentUnit = route.apartmentUnit ?: "", city = route.city ?: "", stateProvince = route.stateProvince ?: "", postalCode = route.postalCode ?: "", country = route.country ?: "", bedrooms = route.bedrooms, bathrooms = route.bathrooms?.toDouble(), squareFootage = route.squareFootage, lotSize = route.lotSize?.toDouble(), yearBuilt = route.yearBuilt, description = route.description ?: "", purchaseDate = null, purchasePrice = null, isPrimary = route.isPrimary, createdAt = route.createdAt, updatedAt = route.updatedAt ), onNavigateBack = { navController.popBackStack() }, onResidenceUpdated = { // Set refresh flag before navigating back navController.previousBackStackEntry?.savedStateHandle?.set("refresh", true) navController.popBackStack() } ) } composable { TasksScreen( onNavigateBack = { navController.popBackStack() } ) } composable { backStackEntry -> val route = backStackEntry.toRoute() ResidenceDetailScreen( residenceId = route.residenceId, onNavigateBack = { navController.popBackStack() }, onNavigateToEditResidence = { residence -> navController.navigate( EditResidenceRoute( residenceId = residence.id, name = residence.name, propertyType = residence.propertyTypeId, streetAddress = residence.streetAddress, apartmentUnit = residence.apartmentUnit, city = residence.city, stateProvince = residence.stateProvince, postalCode = residence.postalCode, country = residence.country, bedrooms = residence.bedrooms, bathrooms = residence.bathrooms?.toFloat(), squareFootage = residence.squareFootage, lotSize = residence.lotSize?.toFloat(), yearBuilt = residence.yearBuilt, description = residence.description, isPrimary = residence.isPrimary, ownerUserName = residence.ownerUsername, createdAt = residence.createdAt, updatedAt = residence.updatedAt, owner = residence.ownerId ) ) }, onNavigateToEditTask = { task -> navController.navigate( EditTaskRoute( taskId = task.id, residenceId = task.residenceId, title = task.title, description = task.description, categoryId = task.category?.id ?: 0, categoryName = task.category?.name ?: "", frequencyId = task.frequency?.id ?: 0, frequencyName = task.frequency?.name ?: "", priorityId = task.priority?.id ?: 0, priorityName = task.priority?.name ?: "", inProgress = task.inProgress, dueDate = task.dueDate, estimatedCost = task.estimatedCost?.toString(), createdAt = task.createdAt, updatedAt = task.updatedAt ) ) }, onNavigateToContractorDetail = { contractorId -> navController.navigate(ContractorDetailRoute(contractorId)) }, onNavigateToManageUsers = { residenceId, residenceName, isPrimaryOwner, ownerId -> navController.navigate( ManageUsersRoute( residenceId = residenceId, residenceName = residenceName, isPrimaryOwner = isPrimaryOwner, residenceOwnerId = ownerId ) ) } ) } composable { backStackEntry -> val route = backStackEntry.toRoute() ManageUsersScreen( residenceId = route.residenceId, residenceName = route.residenceName, isPrimaryOwner = route.isPrimaryOwner, residenceOwnerId = route.residenceOwnerId, onNavigateBack = { navController.popBackStack() } ) } composable { PlatformUpgradeScreen( onNavigateBack = { navController.popBackStack() }, onSubscriptionChanged = { // Subscription state updated via DataManager } ) } composable { // P2 Stream E — full-screen Free vs. Pro comparison. FeatureComparisonScreen( onNavigateBack = { navController.popBackStack() }, onNavigateToUpgrade = { navController.popBackStack() navController.navigate(UpgradeRoute) }, ) } composable { backStackEntry -> // P2 Stream H — standalone personalized-task suggestions. val route = backStackEntry.toRoute() TaskSuggestionsScreen( residenceId = route.residenceId, onNavigateBack = { navController.popBackStack() }, ) } composable { backStackEntry -> // P2 Stream I — Android port of iOS AddTaskWithResidenceView. val route = backStackEntry.toRoute() AddTaskWithResidenceScreen( residenceId = route.residenceId, onNavigateBack = { navController.popBackStack() }, onCreated = { navController.popBackStack() }, ) } composable { backStackEntry -> // P2 Stream G — full-screen template catalog browse. val route = backStackEntry.toRoute() TaskTemplatesBrowserScreen( residenceId = route.residenceId, fromOnboarding = route.fromOnboarding, onNavigateBack = { navController.popBackStack() }, ) } composable { backStackEntry -> val route = backStackEntry.toRoute() EditTaskScreen( task = TaskDetail( id = route.taskId, residenceId = route.residenceId, createdById = 0, title = route.title, description = route.description ?: "", category = TaskCategory(id = route.categoryId, name = route.categoryName), frequency = TaskFrequency( id = route.frequencyId, name = route.frequencyName, days = null ), priority = TaskPriority(id = route.priorityId, name = route.priorityName), inProgress = route.inProgress, dueDate = route.dueDate, estimatedCost = route.estimatedCost?.toDoubleOrNull(), createdAt = route.createdAt, updatedAt = route.updatedAt, completions = emptyList() ), onNavigateBack = { navController.popBackStack() }, onTaskUpdated = { navController.popBackStack() } ) } composable { ProfileScreen( onNavigateBack = { navController.popBackStack() }, onLogout = { // Clear token and lookups on logout DataManager.clear() isLoggedIn = false isVerified = false navController.navigate(LoginRoute) { popUpTo { inclusive = true } } }, onAccountDeleted = { // Clear token and lookups on account deletion DataManager.clear() isLoggedIn = false isVerified = false navController.navigate(LoginRoute) { popUpTo { inclusive = true } } }, onNavigateToNotificationPreferences = { navController.navigate(NotificationPreferencesRoute) }, onNavigateToThemeSelection = { navController.navigate(ThemeSelectionRoute) }, onNavigateToUpgrade = { navController.navigate(UpgradeRoute) } ) } composable { NotificationPreferencesScreen( onNavigateBack = { navController.popBackStack() } ) } composable { ThemeSelectionScreen( onNavigateBack = { navController.popBackStack() } ) } } } // Biometric lock overlay - shown when app is locked if (isLocked && isLoggedIn && isVerified) { BiometricLockScreen( onUnlocked = { isLocked = false } ) } } // close Box } /* MaterialTheme { var showContent by remember { mutableStateOf(false) } Column( modifier = Modifier .background(MaterialTheme.colorScheme.primaryContainer) .safeContentPadding() .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { Button(onClick = { showContent = !showContent }) { Text("Click me!") } AnimatedVisibility(showContent) { val greeting = remember { Greeting().greet() } Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { Image(painterResource(Res.drawable.compose_multiplatform), null) Text("Compose: $greeting") } } } } */ }