UI fix 2/5: lifecycle-aware StateFlow collection in screens

Replace collectAsState() with collectAsStateWithLifecycle() so StateFlows
stop collecting when the host is in background — prevents memory/CPU leaks
on lifecycle transitions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 14:15:03 -05:00
parent a78494c529
commit 0ec2ac7744
34 changed files with 143 additions and 113 deletions

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.AddNewTaskWithResidenceDialog
import com.tt.honeyDue.ui.components.ApiResultHandler
import com.tt.honeyDue.ui.components.CompleteTaskDialog
@@ -38,10 +39,10 @@ fun AllTasksScreen(
onClearNavigateToTask: () -> Unit = {},
onNavigateToCompleteTask: ((TaskDetail, String) -> Unit)? = null
) {
val tasksState by viewModel.tasksState.collectAsState()
val completionState by taskCompletionViewModel.createCompletionState.collectAsState()
val myResidencesState by residenceViewModel.myResidencesState.collectAsState()
val createTaskState by viewModel.taskAddNewCustomTaskState.collectAsState()
val tasksState by viewModel.tasksState.collectAsStateWithLifecycle()
val completionState by taskCompletionViewModel.createCompletionState.collectAsStateWithLifecycle()
val myResidencesState by residenceViewModel.myResidencesState.collectAsStateWithLifecycle()
val createTaskState by viewModel.taskAddNewCustomTaskState.collectAsStateWithLifecycle()
var showCompleteDialog by remember { mutableStateOf(false) }
var showNewTaskDialog by remember { mutableStateOf(false) }

View File

@@ -24,6 +24,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import honeydue.composeapp.generated.resources.*
import com.tt.honeyDue.models.TaskCompletionCreateRequest
import com.tt.honeyDue.models.ContractorSummary
@@ -55,7 +56,7 @@ fun CompleteTaskScreen(
var showContractorPicker by remember { mutableStateOf(false) }
var isSubmitting by remember { mutableStateOf(false) }
val contractorsState by contractorViewModel.contractorsState.collectAsState()
val contractorsState by contractorViewModel.contractorsState.collectAsStateWithLifecycle()
val hapticFeedback = rememberHapticFeedback()
LaunchedEffect(Unit) {

View File

@@ -18,6 +18,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.ui.components.AddContractorDialog
import com.tt.honeyDue.ui.components.ApiResultHandler
@@ -39,9 +40,9 @@ fun ContractorDetailScreen(
onNavigateBack: () -> Unit,
viewModel: ContractorViewModel = viewModel { ContractorViewModel() }
) {
val contractorState by viewModel.contractorDetailState.collectAsState()
val deleteState by viewModel.deleteState.collectAsState()
val toggleFavoriteState by viewModel.toggleFavoriteState.collectAsState()
val contractorState by viewModel.contractorDetailState.collectAsStateWithLifecycle()
val deleteState by viewModel.deleteState.collectAsStateWithLifecycle()
val toggleFavoriteState by viewModel.toggleFavoriteState.collectAsStateWithLifecycle()
var showEditDialog by remember { mutableStateOf(false) }
var showDeleteConfirmation by remember { mutableStateOf(false) }

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.AddContractorDialog
import com.tt.honeyDue.ui.components.ApiResultHandler
import com.tt.honeyDue.ui.components.HandleErrors
@@ -37,10 +38,10 @@ fun ContractorsScreen(
onNavigateToContractorDetail: (Int) -> Unit,
viewModel: ContractorViewModel = viewModel { ContractorViewModel() }
) {
val contractorsState by viewModel.contractorsState.collectAsState()
val deleteState by viewModel.deleteState.collectAsState()
val toggleFavoriteState by viewModel.toggleFavoriteState.collectAsState()
val contractorSpecialties by LookupsRepository.contractorSpecialties.collectAsState()
val contractorsState by viewModel.contractorsState.collectAsStateWithLifecycle()
val deleteState by viewModel.deleteState.collectAsStateWithLifecycle()
val toggleFavoriteState by viewModel.toggleFavoriteState.collectAsStateWithLifecycle()
val contractorSpecialties by LookupsRepository.contractorSpecialties.collectAsStateWithLifecycle()
// Check if screen should be blocked (limit=0)
val isBlocked = SubscriptionHelper.isContractorsBlocked()

View File

@@ -33,6 +33,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.SubcomposeAsyncImage
import coil3.compose.SubcomposeAsyncImageContent
import coil3.compose.AsyncImagePainter
@@ -50,8 +51,8 @@ fun DocumentDetailScreen(
onNavigateToEdit: (Int) -> Unit,
documentViewModel: DocumentViewModel = viewModel { DocumentViewModel() }
) {
val documentState by documentViewModel.documentDetailState.collectAsState()
val deleteState by documentViewModel.deleteState.collectAsState()
val documentState by documentViewModel.documentDetailState.collectAsStateWithLifecycle()
val deleteState by documentViewModel.deleteState.collectAsStateWithLifecycle()
var showDeleteDialog by remember { mutableStateOf(false) }
var showPhotoViewer by remember { mutableStateOf(false) }
var selectedPhotoIndex by remember { mutableStateOf(0) }

View File

@@ -18,6 +18,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.tt.honeyDue.ui.components.AuthenticatedImage
import com.tt.honeyDue.viewmodel.DocumentViewModel
@@ -83,12 +84,12 @@ fun DocumentFormScreen(
var providerError by remember { mutableStateOf("") }
var residenceError by remember { mutableStateOf("") }
val residencesState by residenceViewModel.residencesState.collectAsState()
val documentDetailState by documentViewModel.documentDetailState.collectAsState()
val residencesState by residenceViewModel.residencesState.collectAsStateWithLifecycle()
val documentDetailState by documentViewModel.documentDetailState.collectAsStateWithLifecycle()
val operationState by if (isEditMode) {
documentViewModel.updateState.collectAsState()
documentViewModel.updateState.collectAsStateWithLifecycle()
} else {
documentViewModel.createState.collectAsState()
documentViewModel.createState.collectAsStateWithLifecycle()
}
val isWarranty = selectedDocumentType == "warranty"

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.documents.DocumentsTabContent
import com.tt.honeyDue.ui.subscription.UpgradeFeatureScreen
import com.tt.honeyDue.utils.SubscriptionHelper
@@ -37,7 +38,7 @@ fun DocumentsScreen(
documentViewModel: DocumentViewModel = viewModel { DocumentViewModel() }
) {
var selectedTab by remember { mutableStateOf(DocumentTab.WARRANTIES) }
val documentsState by documentViewModel.documentsState.collectAsState()
val documentsState by documentViewModel.documentsState.collectAsStateWithLifecycle()
// Check if screen should be blocked (limit=0)
val isBlocked = SubscriptionHelper.isDocumentsBlocked()

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.viewmodel.ResidenceViewModel
import com.tt.honeyDue.repository.LookupsRepository
@@ -43,10 +44,10 @@ fun EditTaskScreen(
var frequencyExpanded by remember { mutableStateOf(false) }
var priorityExpanded by remember { mutableStateOf(false) }
val updateTaskState by viewModel.updateTaskState.collectAsState()
val categories by LookupsRepository.taskCategories.collectAsState()
val frequencies by LookupsRepository.taskFrequencies.collectAsState()
val priorities by LookupsRepository.taskPriorities.collectAsState()
val updateTaskState by viewModel.updateTaskState.collectAsStateWithLifecycle()
val categories by LookupsRepository.taskCategories.collectAsStateWithLifecycle()
val frequencies by LookupsRepository.taskFrequencies.collectAsStateWithLifecycle()
val priorities by LookupsRepository.taskPriorities.collectAsStateWithLifecycle()
// Validation errors
var titleError by remember { mutableStateOf("") }

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.components.auth.AuthHeader
import com.tt.honeyDue.ui.components.common.ErrorCard
@@ -30,8 +31,8 @@ fun ForgotPasswordScreen(
viewModel: PasswordResetViewModel
) {
var email by remember { mutableStateOf("") }
val forgotPasswordState by viewModel.forgotPasswordState.collectAsState()
val currentStep by viewModel.currentStep.collectAsState()
val forgotPasswordState by viewModel.forgotPasswordState.collectAsStateWithLifecycle()
val currentStep by viewModel.currentStep.collectAsStateWithLifecycle()
// Handle errors for forgot password
forgotPasswordState.HandleErrors(

View File

@@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.ResidenceViewModel
@@ -29,8 +30,8 @@ fun HomeScreen(
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() },
taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
) {
val summaryState by viewModel.myResidencesState.collectAsState()
val totalSummary by DataManager.totalSummary.collectAsState()
val summaryState by viewModel.myResidencesState.collectAsStateWithLifecycle()
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.loadMyResidences()

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.components.auth.AuthHeader
import com.tt.honeyDue.ui.components.auth.GoogleSignInButton
@@ -44,8 +45,8 @@ fun LoginScreen(
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
var googleSignInError by remember { mutableStateOf<String?>(null) }
val loginState by viewModel.loginState.collectAsState()
val googleSignInState by viewModel.googleSignInState.collectAsState()
val loginState by viewModel.loginState.collectAsStateWithLifecycle()
val googleSignInState by viewModel.googleSignInState.collectAsStateWithLifecycle()
// Handle errors for login
loginState.HandleErrors(

View File

@@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.network.ApiResult
@@ -57,8 +58,8 @@ fun NotificationPreferencesScreen(
onNavigateBack: () -> Unit,
viewModel: NotificationPreferencesViewModel = viewModel { NotificationPreferencesViewModel() },
) {
val preferencesState by viewModel.preferencesState.collectAsState()
val categoryState by viewModel.categoryState.collectAsState()
val preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle()
val categoryState by viewModel.categoryState.collectAsStateWithLifecycle()
// Platform-specific wiring: Android provides real controller + settings
// launcher; every other target returns null and the matching section

View File

@@ -31,6 +31,7 @@ import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.ui.subscription.UpgradePromptDialog
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.platform.BiometricResult
@@ -64,11 +65,11 @@ fun ProfileScreen(
val isBiometricAvailable = remember { biometricAuth.isBiometricAvailable() }
var isBiometricEnabled by remember { mutableStateOf(BiometricPreference.isBiometricEnabled()) }
val updateState by viewModel.updateProfileState.collectAsState()
val deleteAccountState by viewModel.deleteAccountState.collectAsState()
val updateState by viewModel.updateProfileState.collectAsStateWithLifecycle()
val deleteAccountState by viewModel.deleteAccountState.collectAsStateWithLifecycle()
val currentTheme by remember { derivedStateOf { ThemeManager.currentTheme } }
val currentSubscription by DataManager.subscription.collectAsState()
val currentUser by DataManager.currentUser.collectAsState()
val currentSubscription by DataManager.subscription.collectAsStateWithLifecycle()
val currentUser by DataManager.currentUser.collectAsStateWithLifecycle()
// Handle errors for profile update
updateState.HandleErrors(

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.components.auth.AuthHeader
import com.tt.honeyDue.ui.components.auth.RequirementItem
@@ -41,7 +42,7 @@ fun RegisterScreen(
var errorMessage by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
val createState by viewModel.registerState.collectAsState()
val createState by viewModel.registerState.collectAsStateWithLifecycle()
// Handle errors for registration
createState.HandleErrors(

View File

@@ -13,6 +13,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.components.auth.AuthHeader
import com.tt.honeyDue.ui.components.auth.RequirementItem
@@ -35,9 +36,9 @@ fun ResetPasswordScreen(
var newPasswordVisible by remember { mutableStateOf(false) }
var confirmPasswordVisible by remember { mutableStateOf(false) }
val resetPasswordState by viewModel.resetPasswordState.collectAsState()
val loginState by viewModel.loginState.collectAsState()
val currentStep by viewModel.currentStep.collectAsState()
val resetPasswordState by viewModel.resetPasswordState.collectAsStateWithLifecycle()
val loginState by viewModel.loginState.collectAsStateWithLifecycle()
val currentStep by viewModel.currentStep.collectAsStateWithLifecycle()
// Handle errors for password reset
resetPasswordState.HandleErrors(

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.AddNewTaskDialog
import com.tt.honeyDue.ui.components.ApiResultHandler
import com.tt.honeyDue.ui.components.CompleteTaskDialog
@@ -57,13 +58,13 @@ fun ResidenceDetailScreen(
taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
) {
var residenceState by remember { mutableStateOf<ApiResult<Residence>>(ApiResult.Loading) }
val tasksState by residenceViewModel.residenceTasksState.collectAsState()
val contractorsState by residenceViewModel.residenceContractorsState.collectAsState()
val completionState by taskCompletionViewModel.createCompletionState.collectAsState()
val taskAddNewTaskState by taskViewModel.taskAddNewCustomTaskState.collectAsState()
val cancelTaskState by residenceViewModel.cancelTaskState.collectAsState()
val uncancelTaskState by residenceViewModel.uncancelTaskState.collectAsState()
val generateReportState by residenceViewModel.generateReportState.collectAsState()
val tasksState by residenceViewModel.residenceTasksState.collectAsStateWithLifecycle()
val contractorsState by residenceViewModel.residenceContractorsState.collectAsStateWithLifecycle()
val completionState by taskCompletionViewModel.createCompletionState.collectAsStateWithLifecycle()
val taskAddNewTaskState by taskViewModel.taskAddNewCustomTaskState.collectAsStateWithLifecycle()
val cancelTaskState by residenceViewModel.cancelTaskState.collectAsStateWithLifecycle()
val uncancelTaskState by residenceViewModel.uncancelTaskState.collectAsStateWithLifecycle()
val generateReportState by residenceViewModel.generateReportState.collectAsStateWithLifecycle()
var showCompleteDialog by remember { mutableStateOf(false) }
var selectedTask by remember { mutableStateOf<TaskDetail?>(null) }
@@ -77,12 +78,12 @@ fun ResidenceDetailScreen(
var showArchiveTaskConfirmation by remember { mutableStateOf(false) }
var taskToCancel by remember { mutableStateOf<TaskDetail?>(null) }
var taskToArchive by remember { mutableStateOf<TaskDetail?>(null) }
val deleteState by residenceViewModel.deleteResidenceState.collectAsState()
val deleteState by residenceViewModel.deleteResidenceState.collectAsStateWithLifecycle()
var showUpgradePrompt by remember { mutableStateOf(false) }
var upgradeTriggerKey by remember { mutableStateOf<String?>(null) }
// Get current user for ownership checks
val currentUser by DataManager.currentUser.collectAsState()
val currentUser by DataManager.currentUser.collectAsStateWithLifecycle()
// Residence sharing state and function
val (shareState, shareResidence) = rememberShareResidence()

View File

@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.viewmodel.ResidenceViewModel
import com.tt.honeyDue.repository.LookupsRepository
import com.tt.honeyDue.data.DataManager
@@ -59,12 +60,12 @@ fun ResidenceFormScreen(
var expanded by remember { mutableStateOf(false) }
val operationState by if (isEditMode) {
viewModel.updateResidenceState.collectAsState()
viewModel.updateResidenceState.collectAsStateWithLifecycle()
} else {
viewModel.createResidenceState.collectAsState()
viewModel.createResidenceState.collectAsStateWithLifecycle()
}
val propertyTypes by LookupsRepository.residenceTypes.collectAsState()
val currentUser by DataManager.currentUser.collectAsState()
val propertyTypes by LookupsRepository.residenceTypes.collectAsStateWithLifecycle()
val currentUser by DataManager.currentUser.collectAsStateWithLifecycle()
// Check if current user is the owner
val isCurrentUserOwner = remember(existingResidence, currentUser) {

View File

@@ -24,6 +24,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.ApiResultHandler
import com.tt.honeyDue.ui.components.common.StatItem
import com.tt.honeyDue.ui.components.residence.TaskStatChip
@@ -51,8 +52,8 @@ fun ResidencesScreen(
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() },
taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
) {
val myResidencesState by viewModel.myResidencesState.collectAsState()
val totalSummary by DataManager.totalSummary.collectAsState()
val myResidencesState by viewModel.myResidencesState.collectAsStateWithLifecycle()
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle()
var isRefreshing by remember { mutableStateOf(false) }
var showUpgradePrompt by remember { mutableStateOf(false) }
var upgradeTriggerKey by remember { mutableStateOf<String?>(null) }

View File

@@ -12,6 +12,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.CompleteTaskDialog
import com.tt.honeyDue.ui.components.ErrorDialog
import com.tt.honeyDue.ui.components.task.TaskCard
@@ -35,8 +36,8 @@ fun TasksScreen(
viewModel: TaskViewModel = viewModel { TaskViewModel() },
taskCompletionViewModel: TaskCompletionViewModel = viewModel { TaskCompletionViewModel() }
) {
val tasksState by viewModel.tasksState.collectAsState()
val completionState by taskCompletionViewModel.createCompletionState.collectAsState()
val tasksState by viewModel.tasksState.collectAsStateWithLifecycle()
val completionState by taskCompletionViewModel.createCompletionState.collectAsStateWithLifecycle()
var expandedColumns by remember { mutableStateOf(setOf<String>()) }
var showCompleteDialog by remember { mutableStateOf(false) }
var selectedTask by remember { mutableStateOf<com.tt.honeyDue.models.TaskDetail?>(null) }

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.components.auth.AuthHeader
import com.tt.honeyDue.ui.components.common.ErrorCard
@@ -35,7 +36,7 @@ fun VerifyEmailScreen(
var errorMessage by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
val verifyState by viewModel.verifyEmailState.collectAsState()
val verifyState by viewModel.verifyEmailState.collectAsStateWithLifecycle()
// Handle errors for email verification
verifyState.HandleErrors(

View File

@@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.components.HandleErrors
import com.tt.honeyDue.ui.components.auth.AuthHeader
import com.tt.honeyDue.ui.components.common.ErrorCard
@@ -30,9 +31,9 @@ fun VerifyResetCodeScreen(
viewModel: PasswordResetViewModel
) {
var code by remember { mutableStateOf("") }
val email by viewModel.email.collectAsState()
val verifyCodeState by viewModel.verifyCodeState.collectAsState()
val currentStep by viewModel.currentStep.collectAsState()
val email by viewModel.email.collectAsStateWithLifecycle()
val verifyCodeState by viewModel.verifyCodeState.collectAsStateWithLifecycle()
val currentStep by viewModel.currentStep.collectAsStateWithLifecycle()
// Handle errors for code verification
verifyCodeState.HandleErrors(

View File

@@ -19,6 +19,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.ui.components.auth.RequirementItem
import com.tt.honeyDue.ui.theme.*
@@ -39,7 +40,7 @@ fun OnboardingCreateAccountContent(
var showLoginDialog by remember { mutableStateOf(false) }
var localErrorMessage by remember { mutableStateOf<String?>(null) }
val registerState by viewModel.registerState.collectAsState()
val registerState by viewModel.registerState.collectAsStateWithLifecycle()
LaunchedEffect(registerState) {
when (registerState) {
@@ -327,7 +328,7 @@ private fun OnboardingLoginDialog(
) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val loginState by viewModel.loginState.collectAsState()
val loginState by viewModel.loginState.collectAsStateWithLifecycle()
LaunchedEffect(loginState) {
when (loginState) {

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.data.DataManager
@@ -71,9 +72,9 @@ fun OnboardingFirstTaskContent(
var isCreatingTasks by remember { mutableStateOf(false) }
var selectedTabIndex by remember { mutableStateOf(0) }
val createTasksState by viewModel.createTasksState.collectAsState()
val suggestionsState by viewModel.suggestionsState.collectAsState()
val templatesGroupedState by viewModel.templatesGroupedState.collectAsState()
val createTasksState by viewModel.createTasksState.collectAsStateWithLifecycle()
val suggestionsState by viewModel.suggestionsState.collectAsStateWithLifecycle()
val templatesGroupedState by viewModel.templatesGroupedState.collectAsStateWithLifecycle()
// Kick off both network calls on mount. Suggestions needs a residence;
// the grouped catalog is user-independent and safe to load immediately.

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.models.HomeProfileOptions
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.OnboardingViewModel
@@ -27,20 +28,20 @@ fun OnboardingHomeProfileContent(
onContinue: () -> Unit,
onSkip: () -> Unit
) {
val heatingType by viewModel.heatingType.collectAsState()
val coolingType by viewModel.coolingType.collectAsState()
val waterHeaterType by viewModel.waterHeaterType.collectAsState()
val roofType by viewModel.roofType.collectAsState()
val hasPool by viewModel.hasPool.collectAsState()
val hasSprinklerSystem by viewModel.hasSprinklerSystem.collectAsState()
val hasSeptic by viewModel.hasSeptic.collectAsState()
val hasFireplace by viewModel.hasFireplace.collectAsState()
val hasGarage by viewModel.hasGarage.collectAsState()
val hasBasement by viewModel.hasBasement.collectAsState()
val hasAttic by viewModel.hasAttic.collectAsState()
val exteriorType by viewModel.exteriorType.collectAsState()
val flooringPrimary by viewModel.flooringPrimary.collectAsState()
val landscapingType by viewModel.landscapingType.collectAsState()
val heatingType by viewModel.heatingType.collectAsStateWithLifecycle()
val coolingType by viewModel.coolingType.collectAsStateWithLifecycle()
val waterHeaterType by viewModel.waterHeaterType.collectAsStateWithLifecycle()
val roofType by viewModel.roofType.collectAsStateWithLifecycle()
val hasPool by viewModel.hasPool.collectAsStateWithLifecycle()
val hasSprinklerSystem by viewModel.hasSprinklerSystem.collectAsStateWithLifecycle()
val hasSeptic by viewModel.hasSeptic.collectAsStateWithLifecycle()
val hasFireplace by viewModel.hasFireplace.collectAsStateWithLifecycle()
val hasGarage by viewModel.hasGarage.collectAsStateWithLifecycle()
val hasBasement by viewModel.hasBasement.collectAsStateWithLifecycle()
val hasAttic by viewModel.hasAttic.collectAsStateWithLifecycle()
val exteriorType by viewModel.exteriorType.collectAsStateWithLifecycle()
val flooringPrimary by viewModel.flooringPrimary.collectAsStateWithLifecycle()
val landscapingType by viewModel.landscapingType.collectAsStateWithLifecycle()
Column(modifier = Modifier.fillMaxSize()) {
LazyColumn(

View File

@@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.OnboardingViewModel
@@ -27,7 +28,7 @@ fun OnboardingJoinResidenceContent(
var shareCode by remember { mutableStateOf("") }
var localErrorMessage by remember { mutableStateOf<String?>(null) }
val joinState by viewModel.joinResidenceState.collectAsState()
val joinState by viewModel.joinResidenceState.collectAsStateWithLifecycle()
LaunchedEffect(joinState) {
when (joinState) {

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.OnboardingViewModel
import honeydue.composeapp.generated.resources.*
@@ -22,7 +23,7 @@ fun OnboardingNameResidenceContent(
viewModel: OnboardingViewModel,
onContinue: () -> Unit
) {
val residenceName by viewModel.residenceName.collectAsState()
val residenceName by viewModel.residenceName.collectAsStateWithLifecycle()
var localName by remember { mutableStateOf(residenceName) }
WarmGradientBackground(

View File

@@ -14,6 +14,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.OnboardingStep
import com.tt.honeyDue.viewmodel.OnboardingViewModel
@@ -26,9 +27,9 @@ fun OnboardingScreen(
onLoginSuccess: (Boolean) -> Unit, // Boolean = isVerified
viewModel: OnboardingViewModel = viewModel { OnboardingViewModel() }
) {
val currentStep by viewModel.currentStep.collectAsState()
val userIntent by viewModel.userIntent.collectAsState()
val isComplete by viewModel.isComplete.collectAsState()
val currentStep by viewModel.currentStep.collectAsStateWithLifecycle()
val userIntent by viewModel.userIntent.collectAsStateWithLifecycle()
val isComplete by viewModel.isComplete.collectAsStateWithLifecycle()
// Handle onboarding completion
LaunchedEffect(isComplete) {

View File

@@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.OnboardingViewModel
@@ -27,7 +28,7 @@ fun OnboardingVerifyEmailContent(
var code by remember { mutableStateOf("") }
var localErrorMessage by remember { mutableStateOf<String?>(null) }
val verifyState by viewModel.verifyEmailState.collectAsState()
val verifyState by viewModel.verifyEmailState.collectAsStateWithLifecycle()
LaunchedEffect(verifyState) {
when (verifyState) {

View File

@@ -14,6 +14,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.AuthViewModel
import com.tt.honeyDue.network.ApiResult
@@ -152,7 +153,7 @@ private fun LoginDialog(
val authViewModel: AuthViewModel = viewModel { AuthViewModel() }
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val loginState by authViewModel.loginState.collectAsState()
val loginState by authViewModel.loginState.collectAsStateWithLifecycle()
LaunchedEffect(loginState) {
when (loginState) {

View File

@@ -30,7 +30,6 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -39,6 +38,7 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.ui.components.common.StandardCard
import com.tt.honeyDue.ui.components.forms.FormTextField
@@ -62,9 +62,9 @@ fun JoinResidenceScreen(
onJoined: (Int) -> Unit,
viewModel: JoinResidenceViewModel = viewModel { JoinResidenceViewModel() },
) {
val code by viewModel.code.collectAsState()
val error by viewModel.errorMessage.collectAsState()
val submitState by viewModel.submitState.collectAsState()
val code by viewModel.code.collectAsStateWithLifecycle()
val error by viewModel.errorMessage.collectAsStateWithLifecycle()
val submitState by viewModel.submitState.collectAsStateWithLifecycle()
val isLoading = submitState is ApiResult.Loading

View File

@@ -32,12 +32,12 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.data.DataManager
@@ -71,7 +71,7 @@ fun FeatureComparisonScreen(
onNavigateBack: () -> Unit,
onNavigateToUpgrade: () -> Unit,
) {
val benefits by DataManager.featureBenefits.collectAsState()
val benefits by DataManager.featureBenefits.collectAsStateWithLifecycle()
val rows = FeatureComparisonScreenState.resolveFeatureRows(benefits)
Scaffold(

View File

@@ -28,13 +28,13 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.repository.LookupsRepository
import com.tt.honeyDue.ui.components.common.StandardCard
@@ -65,20 +65,20 @@ fun AddTaskWithResidenceScreen(
AddTaskWithResidenceViewModel(residenceId = residenceId)
}
) {
val title by viewModel.title.collectAsState()
val description by viewModel.description.collectAsState()
val priorityId by viewModel.priorityId.collectAsState()
val categoryId by viewModel.categoryId.collectAsState()
val frequencyId by viewModel.frequencyId.collectAsState()
val dueDate by viewModel.dueDate.collectAsState()
val estimatedCost by viewModel.estimatedCost.collectAsState()
val titleError by viewModel.titleError.collectAsState()
val canSubmit by viewModel.canSubmit.collectAsState()
val submitState by viewModel.submitState.collectAsState()
val title by viewModel.title.collectAsStateWithLifecycle()
val description by viewModel.description.collectAsStateWithLifecycle()
val priorityId by viewModel.priorityId.collectAsStateWithLifecycle()
val categoryId by viewModel.categoryId.collectAsStateWithLifecycle()
val frequencyId by viewModel.frequencyId.collectAsStateWithLifecycle()
val dueDate by viewModel.dueDate.collectAsStateWithLifecycle()
val estimatedCost by viewModel.estimatedCost.collectAsStateWithLifecycle()
val titleError by viewModel.titleError.collectAsStateWithLifecycle()
val canSubmit by viewModel.canSubmit.collectAsStateWithLifecycle()
val submitState by viewModel.submitState.collectAsStateWithLifecycle()
val priorities by LookupsRepository.taskPriorities.collectAsState()
val categories by LookupsRepository.taskCategories.collectAsState()
val frequencies by LookupsRepository.taskFrequencies.collectAsState()
val priorities by LookupsRepository.taskPriorities.collectAsStateWithLifecycle()
val categories by LookupsRepository.taskCategories.collectAsStateWithLifecycle()
val frequencies by LookupsRepository.taskFrequencies.collectAsStateWithLifecycle()
val isSubmitting = submitState is ApiResult.Loading

View File

@@ -35,7 +35,6 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -45,6 +44,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.models.TaskSuggestionResponse
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.ui.components.common.StandardCard
@@ -76,8 +76,8 @@ fun TaskSuggestionsScreen(
TaskSuggestionsViewModel(residenceId = residenceId)
}
) {
val suggestionsState by viewModel.suggestionsState.collectAsState()
val acceptState by viewModel.acceptState.collectAsState()
val suggestionsState by viewModel.suggestionsState.collectAsStateWithLifecycle()
val acceptState by viewModel.acceptState.collectAsStateWithLifecycle()
var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {

View File

@@ -38,6 +38,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.models.TaskTemplate
@@ -90,10 +91,10 @@ fun TaskTemplatesBrowserScreen(
)
}
) {
val templatesState by viewModel.templatesState.collectAsState()
val selectedIds by viewModel.selectedTemplateIds.collectAsState()
val selectedCategory by viewModel.selectedCategory.collectAsState()
val applyState by viewModel.applyState.collectAsState()
val templatesState by viewModel.templatesState.collectAsStateWithLifecycle()
val selectedIds by viewModel.selectedTemplateIds.collectAsStateWithLifecycle()
val selectedCategory by viewModel.selectedCategory.collectAsStateWithLifecycle()
val applyState by viewModel.applyState.collectAsStateWithLifecycle()
var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {