Files
honeyDueKMP/composeApp/src/commonMain/kotlin/com/tt/honeyDue/App.kt
Trey T ba1ec2a69b Audit 9b: Dynamic color (Material You) + 48dp min touch target helpers
Dynamic color opt-in on Android 12+ via expect/actual DynamicColor:
- commonMain: expect isDynamicColorSupported() + rememberDynamicColorScheme()
- androidMain: SDK>=31 gate, dynamicLight/DarkColorScheme(context)
- iOS/JVM/JS/WASM: no-op actuals
- ThemeManager gains useDynamicColor StateFlow, persisted via ThemeStorage
- App.kt wires both currentTheme + useDynamicColor into HoneyDueTheme
- ThemeSelectionScreen exposes the "Use system colors" toggle

Touch target helpers:
- Modifier.minTouchTarget(48.dp) + Modifier.clickableWithRipple
- Applied at audit-flagged sites in CompleteTaskScreen

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:39:22 -05:00

831 lines
36 KiB
Kotlin

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<OnboardingRoute> {
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<OnboardingRoute> { 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<OnboardingRoute> { inclusive = true }
}
} else {
navController.navigate(VerifyEmailRoute) {
popUpTo<OnboardingRoute> { inclusive = true }
}
}
}
)
}
composable<LoginRoute> {
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<LoginRoute> { inclusive = true }
}
} else {
navController.navigate(VerifyEmailRoute) {
popUpTo<LoginRoute> { inclusive = true }
}
}
},
onNavigateToRegister = {
navController.navigate(RegisterRoute)
},
onNavigateToForgotPassword = {
navController.navigate(ForgotPasswordRoute)
}
)
}
composable<RegisterRoute> {
RegisterScreen(
onRegisterSuccess = {
isLoggedIn = true
isVerified = false
// Note: Lookups are already initialized by APILayer.register()
navController.navigate(VerifyEmailRoute) {
popUpTo<RegisterRoute> { inclusive = true }
}
},
onNavigateBack = {
navController.popBackStack()
}
)
}
composable<ForgotPasswordRoute> { backStackEntry ->
// Create shared ViewModel for all password reset screens
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry<ForgotPasswordRoute>()
}
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<VerifyResetCodeRoute> { backStackEntry ->
// Use shared ViewModel from ForgotPasswordRoute
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry<ForgotPasswordRoute>()
}
val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() }
VerifyResetCodeScreen(
onNavigateBack = {
navController.popBackStack()
},
onNavigateToReset = {
navController.navigate(ResetPasswordRoute)
},
viewModel = passwordResetViewModel
)
}
composable<ResetPasswordRoute> { backStackEntry ->
// Use shared ViewModel from ForgotPasswordRoute
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry<ForgotPasswordRoute>()
}
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<ForgotPasswordRoute> { inclusive = true }
}
} else {
navController.navigate(VerifyEmailRoute) {
popUpTo<ForgotPasswordRoute> { inclusive = true }
}
}
}
}
ResetPasswordScreen(
onPasswordResetSuccess = {
// Fallback: manual return to login (only shown if auto-login fails)
onClearDeepLinkToken()
navController.navigate(LoginRoute) {
popUpTo<ForgotPasswordRoute> { inclusive = true }
}
},
onNavigateBack = {
navController.popBackStack()
},
viewModel = passwordResetViewModel
)
}
composable<VerifyEmailRoute> {
VerifyEmailScreen(
onVerifySuccess = {
isVerified = true
navController.navigate(MainRoute) {
popUpTo<VerifyEmailRoute> { inclusive = true }
}
},
onLogout = {
// Clear token and lookups on logout
DataManager.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<VerifyEmailRoute> { inclusive = true }
}
}
)
}
composable<MainRoute> {
MainScreen(
onLogout = {
// Clear token and lookups on logout
DataManager.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<MainRoute> { 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<HomeRoute> {
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<HomeRoute> { inclusive = true }
}
}
)
}
composable<ResidencesRoute> { backStackEntry ->
// Get refresh flag from saved state (set when returning from add/edit)
val shouldRefresh = backStackEntry.savedStateHandle.get<Boolean>("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<HomeRoute> { inclusive = true }
}
}
)
}
composable<JoinResidenceRoute> {
com.tt.honeyDue.ui.screens.residence.JoinResidenceScreen(
onNavigateBack = { navController.popBackStack() },
onJoined = { residenceId ->
navController.popBackStack()
navController.navigate(ResidenceDetailRoute(residenceId))
},
)
}
composable<AddResidenceRoute> {
AddResidenceScreen(
onNavigateBack = {
navController.popBackStack()
},
onResidenceCreated = {
// Set refresh flag before navigating back
navController.previousBackStackEntry?.savedStateHandle?.set("refresh", true)
navController.popBackStack()
}
)
}
composable<EditResidenceRoute> { backStackEntry ->
val route = backStackEntry.toRoute<EditResidenceRoute>()
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<TasksRoute> {
TasksScreen(
onNavigateBack = {
navController.popBackStack()
}
)
}
composable<ResidenceDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<ResidenceDetailRoute>()
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<ManageUsersRoute> { backStackEntry ->
val route = backStackEntry.toRoute<ManageUsersRoute>()
ManageUsersScreen(
residenceId = route.residenceId,
residenceName = route.residenceName,
isPrimaryOwner = route.isPrimaryOwner,
residenceOwnerId = route.residenceOwnerId,
onNavigateBack = {
navController.popBackStack()
}
)
}
composable<UpgradeRoute> {
PlatformUpgradeScreen(
onNavigateBack = {
navController.popBackStack()
},
onSubscriptionChanged = {
// Subscription state updated via DataManager
}
)
}
composable<FeatureComparisonRoute> {
// P2 Stream E — full-screen Free vs. Pro comparison.
FeatureComparisonScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToUpgrade = {
navController.popBackStack()
navController.navigate(UpgradeRoute)
},
)
}
composable<TaskSuggestionsRoute> { backStackEntry ->
// P2 Stream H — standalone personalized-task suggestions.
val route = backStackEntry.toRoute<TaskSuggestionsRoute>()
TaskSuggestionsScreen(
residenceId = route.residenceId,
onNavigateBack = { navController.popBackStack() },
)
}
composable<AddTaskWithResidenceRoute> { backStackEntry ->
// P2 Stream I — Android port of iOS AddTaskWithResidenceView.
val route = backStackEntry.toRoute<AddTaskWithResidenceRoute>()
AddTaskWithResidenceScreen(
residenceId = route.residenceId,
onNavigateBack = { navController.popBackStack() },
onCreated = { navController.popBackStack() },
)
}
composable<TaskTemplatesBrowserRoute> { backStackEntry ->
// P2 Stream G — full-screen template catalog browse.
val route = backStackEntry.toRoute<TaskTemplatesBrowserRoute>()
TaskTemplatesBrowserScreen(
residenceId = route.residenceId,
fromOnboarding = route.fromOnboarding,
onNavigateBack = { navController.popBackStack() },
)
}
composable<EditTaskRoute> { backStackEntry ->
val route = backStackEntry.toRoute<EditTaskRoute>()
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<ProfileRoute> {
ProfileScreen(
onNavigateBack = {
navController.popBackStack()
},
onLogout = {
// Clear token and lookups on logout
DataManager.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<ProfileRoute> { inclusive = true }
}
},
onAccountDeleted = {
// Clear token and lookups on account deletion
DataManager.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<ProfileRoute> { inclusive = true }
}
},
onNavigateToNotificationPreferences = {
navController.navigate(NotificationPreferencesRoute)
},
onNavigateToThemeSelection = {
navController.navigate(ThemeSelectionRoute)
},
onNavigateToUpgrade = {
navController.navigate(UpgradeRoute)
}
)
}
composable<NotificationPreferencesRoute> {
NotificationPreferencesScreen(
onNavigateBack = {
navController.popBackStack()
}
)
}
composable<ThemeSelectionRoute> {
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")
}
}
}
}
*/
}