Rebrand from Casera/MyCrib to honeyDue

Total rebrand across KMM project:
- Kotlin package: com.example.casera -> com.tt.honeyDue (dirs + declarations)
- Gradle: rootProject.name, namespace, applicationId
- Android: manifest, strings.xml (all languages), widget resources
- iOS: pbxproj bundle IDs, Info.plist, entitlements, xcconfig
- iOS directories: Casera/ -> HoneyDue/, CaseraTests/ -> HoneyDueTests/, etc.
- Swift source: all class/struct/enum renames
- Deep links: casera:// -> honeydue://, .casera -> .honeydue
- App icons replaced with honeyDue honeycomb icon
- Domains: casera.treytartt.com -> honeyDue.treytartt.com
- Bundle IDs: com.tt.casera -> com.tt.honeyDue
- Database table names preserved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-07 06:33:57 -06:00
parent 9c574c4343
commit 1e2adf7660
450 changed files with 1730 additions and 1788 deletions

View File

@@ -0,0 +1,715 @@
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.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 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()
// 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 } }
HoneyDueTheme(themeColors = currentTheme) {
// 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
}
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)
},
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<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<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 }
}
},
onNavigateToNotificationPreferences = {
navController.navigate(NotificationPreferencesRoute)
},
onNavigateToUpgrade = {
navController.navigate(UpgradeRoute)
}
)
}
composable<NotificationPreferencesRoute> {
NotificationPreferencesScreen(
onNavigateBack = {
navController.popBackStack()
}
)
}
}
}
}
/*
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")
}
}
}
}
*/
}