Files
honeyDueKMP/composeApp/src/commonMain/kotlin/com/example/mycrib/App.kt
Trey t 1b777049a8 wip
2025-11-07 12:21:48 -06:00

497 lines
21 KiB
Kotlin

package com.example.mycrib
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.mycrib.android.ui.screens.AddResidenceScreen
import com.mycrib.android.ui.screens.EditResidenceScreen
import com.mycrib.android.ui.screens.EditTaskScreen
import com.mycrib.android.ui.screens.HomeScreen
import com.mycrib.android.ui.screens.LoginScreen
import com.mycrib.android.ui.screens.RegisterScreen
import com.mycrib.android.ui.screens.ResidenceDetailScreen
import com.mycrib.android.ui.screens.ResidencesScreen
import com.mycrib.android.ui.screens.TasksScreen
import com.mycrib.android.ui.screens.VerifyEmailScreen
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.mycrib.android.ui.screens.MainScreen
import com.mycrib.android.ui.screens.ProfileScreen
import com.mycrib.android.ui.theme.MyCribTheme
import com.mycrib.navigation.*
import com.mycrib.repository.LookupsRepository
import com.mycrib.shared.models.Residence
import com.mycrib.shared.models.TaskCategory
import com.mycrib.shared.models.TaskDetail
import com.mycrib.shared.models.TaskFrequency
import com.mycrib.shared.models.TaskPriority
import com.mycrib.shared.models.TaskStatus
import com.mycrib.shared.network.ApiResult
import com.mycrib.shared.network.AuthApi
import com.mycrib.storage.TokenStorage
import mycrib.composeapp.generated.resources.Res
import mycrib.composeapp.generated.resources.compose_multiplatform
@Composable
@Preview
fun App() {
var isLoggedIn by remember { mutableStateOf(TokenStorage.hasToken()) }
var isVerified by remember { mutableStateOf(false) }
var isCheckingAuth by remember { mutableStateOf(true) }
val navController = rememberNavController()
// Check for stored token and verification status on app start
LaunchedEffect(Unit) {
val hasToken = TokenStorage.hasToken()
isLoggedIn = hasToken
if (hasToken) {
// Fetch current user to check verification status
val authApi = AuthApi()
val token = TokenStorage.getToken()
if (token != null) {
when (val result = authApi.getCurrentUser(token)) {
is ApiResult.Success -> {
isVerified = result.data.verified
LookupsRepository.initialize()
}
else -> {
// If fetching user fails, clear token and logout
TokenStorage.clearToken()
isLoggedIn = false
}
}
}
}
isCheckingAuth = false
}
MyCribTheme {
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@MyCribTheme
}
val startDestination = when {
!isLoggedIn -> LoginRoute
!isVerified -> VerifyEmailRoute
else -> MainRoute
}
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NavHost(
navController = navController,
startDestination = startDestination
) {
composable<LoginRoute> {
LoginScreen(
onLoginSuccess = { user ->
isLoggedIn = true
isVerified = user.verified
// Initialize lookups after successful login
LookupsRepository.initialize()
// 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)
}
)
}
composable<RegisterRoute> {
RegisterScreen(
onRegisterSuccess = {
isLoggedIn = true
isVerified = false
// Initialize lookups after successful registration
LookupsRepository.initialize()
navController.navigate(VerifyEmailRoute) {
popUpTo<RegisterRoute> { inclusive = true }
}
},
onNavigateBack = {
navController.popBackStack()
}
)
}
composable<VerifyEmailRoute> {
VerifyEmailScreen(
onVerifySuccess = {
isVerified = true
navController.navigate(MainRoute) {
popUpTo<VerifyEmailRoute> { inclusive = true }
}
},
onLogout = {
// Clear token and lookups on logout
TokenStorage.clearToken()
LookupsRepository.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<VerifyEmailRoute> { inclusive = true }
}
}
)
}
composable<MainRoute> {
MainScreen(
onLogout = {
// Clear token and lookups on logout
TokenStorage.clearToken()
LookupsRepository.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"
},
onNavigateToEditResidence = { residence ->
navController.navigate(
EditResidenceRoute(
residenceId = residence.id,
name = residence.name,
propertyType = residence.propertyType.toInt(),
streetAddress = residence.streetAddress,
apartmentUnit = residence.apartmentUnit,
city = residence.city,
stateProvince = residence.stateProvince,
postalCode = residence.postalCode,
country = residence.country,
bedrooms = residence.bedrooms,
bathrooms = residence.bathrooms,
squareFootage = residence.squareFootage,
lotSize = residence.lotSize,
yearBuilt = residence.yearBuilt,
description = residence.description,
isPrimary = residence.isPrimary,
ownerUserName = residence.ownerUsername,
createdAt = residence.createdAt,
updatedAt = residence.updatedAt,
owner = residence.owner
)
)
},
onNavigateToEditTask = { task ->
navController.navigate(
EditTaskRoute(
taskId = task.id,
residenceId = task.residence,
title = task.title,
description = task.description,
categoryId = task.category.id,
categoryName = task.category.name,
frequencyId = task.frequency.id,
frequencyName = task.frequency.name,
priorityId = task.priority.id,
priorityName = task.priority.name,
statusId = task.status?.id,
statusName = task.status?.name,
dueDate = task.dueDate,
estimatedCost = task.estimatedCost,
createdAt = task.createdAt,
updatedAt = task.updatedAt
)
)
}
)
}
composable<HomeRoute> {
HomeScreen(
onNavigateToResidences = {
navController.navigate(MainRoute)
},
onNavigateToTasks = {
navController.navigate(TasksRoute)
},
onLogout = {
// Clear token and lookups on logout
TokenStorage.clearToken()
LookupsRepository.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<HomeRoute> { inclusive = true }
}
}
)
}
composable<ResidencesRoute> {
ResidencesScreen(
onResidenceClick = { residenceId ->
navController.navigate(ResidenceDetailRoute(residenceId))
},
onAddResidence = {
navController.navigate(AddResidenceRoute)
},
onNavigateToProfile = {
navController.navigate(ProfileRoute)
},
onLogout = {
// Clear token and lookups on logout
TokenStorage.clearToken()
LookupsRepository.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<HomeRoute> { inclusive = true }
}
}
)
}
composable<AddResidenceRoute> {
AddResidenceScreen(
onNavigateBack = {
navController.popBackStack()
},
onResidenceCreated = {
navController.popBackStack()
}
)
}
composable<EditResidenceRoute> { backStackEntry ->
val route = backStackEntry.toRoute<EditResidenceRoute>()
EditResidenceScreen(
residence = Residence(
id = route.residenceId,
name = route.name,
propertyType = route.propertyType.toString(), // Will be fetched from lookups
streetAddress = route.streetAddress,
apartmentUnit = route.apartmentUnit,
city = route.city,
stateProvince = route.stateProvince,
postalCode = route.postalCode,
country = route.country,
bedrooms = route.bedrooms,
bathrooms = route.bathrooms,
squareFootage = route.squareFootage,
lotSize = route.lotSize,
yearBuilt = route.yearBuilt,
description = route.description,
purchaseDate = null,
purchasePrice = null,
isPrimary = route.isPrimary,
ownerUsername = route.ownerUserName,
owner = route.owner,
createdAt = route.createdAt,
updatedAt = route.updatedAt
),
onNavigateBack = {
navController.popBackStack()
},
onResidenceUpdated = {
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.propertyType.toInt(),
streetAddress = residence.streetAddress,
apartmentUnit = residence.apartmentUnit,
city = residence.city,
stateProvince = residence.stateProvince,
postalCode = residence.postalCode,
country = residence.country,
bedrooms = residence.bedrooms,
bathrooms = residence.bathrooms,
squareFootage = residence.squareFootage,
lotSize = residence.lotSize,
yearBuilt = residence.yearBuilt,
description = residence.description,
isPrimary = residence.isPrimary,
ownerUserName = residence.ownerUsername,
createdAt = residence.createdAt,
updatedAt = residence.updatedAt,
owner = residence.owner
)
)
},
onNavigateToEditTask = { task ->
navController.navigate(
EditTaskRoute(
taskId = task.id,
residenceId = task.residence,
title = task.title,
description = task.description,
categoryId = task.category.id,
categoryName = task.category.name,
frequencyId = task.frequency.id,
frequencyName = task.frequency.name,
priorityId = task.priority.id,
priorityName = task.priority.name,
statusId = task.status?.id,
statusName = task.status?.name,
dueDate = task.dueDate,
estimatedCost = task.estimatedCost,
createdAt = task.createdAt,
updatedAt = task.updatedAt
)
)
}
)
}
composable<EditTaskRoute> { backStackEntry ->
val route = backStackEntry.toRoute<EditTaskRoute>()
EditTaskScreen(
task = TaskDetail(
id = route.taskId,
residence = route.residenceId,
title = route.title,
description = route.description,
category = TaskCategory(route.categoryId, route.categoryName),
frequency = TaskFrequency(
route.frequencyId, route.frequencyName, "",
daySpan = 0,
notifyDays = 0
),
priority = TaskPriority(route.priorityId, route.priorityName, displayName = route.statusName ?: ""),
status = route.statusId?.let {
TaskStatus(it, route.statusName ?: "", displayName = route.statusName ?: "")
},
dueDate = route.dueDate,
estimatedCost = route.estimatedCost,
actualCost = null,
notes = null,
createdAt = route.createdAt,
updatedAt = route.updatedAt,
nextScheduledDate = null,
showCompletedButton = false,
completions = emptyList()
),
onNavigateBack = { navController.popBackStack() },
onTaskUpdated = { navController.popBackStack() }
)
}
composable<ProfileRoute> {
ProfileScreen(
onNavigateBack = {
navController.popBackStack()
},
onLogout = {
// Clear token and lookups on logout
TokenStorage.clearToken()
LookupsRepository.clear()
isLoggedIn = false
isVerified = false
navController.navigate(LoginRoute) {
popUpTo<ProfileRoute> { inclusive = true }
}
}
)
}
}
}
}
/*
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")
}
}
}
}
*/
}