P0.2: migrate screens to LocalDataManager.current

Swaps direct `DataManager.xxx` access for `LocalDataManager.current.xxx`
across every Compose screen under ui/screens/** that references the
singleton. Each composable resolves the ambient once at the top of its
body and reuses the local val for subsequent reads — keeping rewrites
minimal and predictable.

Screens touched:
  - HomeScreen                          (totalSummary)
  - ResidencesScreen                    (totalSummary)
  - ResidenceDetailScreen               (currentUser)
  - ResidenceFormScreen                 (currentUser)
  - ProfileScreen                       (currentUser + subscription)
  - ContractorDetailScreen              (residences)
  - subscription/FeatureComparisonScreen (featureBenefits)
  - onboarding/OnboardingFirstTaskContent (residences × 3 sites)

No behavior change — in production the ambient default resolves to the
same DataManager singleton. The change is purely so tests, previews, and
the parity-gallery can `CompositionLocalProvider(LocalDataManager provides fake)`
to substitute a fake without tearing screens apart.

Files under ui/subscription/** and ui/components/AddTaskDialog.kt also
reference DataManager but live outside ui/screens/** (plan's scope) —
flagged for a follow-up pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 19:08:58 -05:00
parent 98b775d335
commit 00e215920a
8 changed files with 30 additions and 20 deletions

View File

@@ -21,7 +21,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.ui.components.AddContractorDialog import com.tt.honeyDue.ui.components.AddContractorDialog
import com.tt.honeyDue.ui.components.ApiResultHandler import com.tt.honeyDue.ui.components.ApiResultHandler
import com.tt.honeyDue.ui.components.HandleErrors import com.tt.honeyDue.ui.components.HandleErrors
@@ -42,6 +42,7 @@ fun ContractorDetailScreen(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
viewModel: ContractorViewModel = viewModel { ContractorViewModel() } viewModel: ContractorViewModel = viewModel { ContractorViewModel() }
) { ) {
val dataManager = LocalDataManager.current
val contractorState by viewModel.contractorDetailState.collectAsStateWithLifecycle() val contractorState by viewModel.contractorDetailState.collectAsStateWithLifecycle()
val deleteState by viewModel.deleteState.collectAsStateWithLifecycle() val deleteState by viewModel.deleteState.collectAsStateWithLifecycle()
val toggleFavoriteState by viewModel.toggleFavoriteState.collectAsStateWithLifecycle() val toggleFavoriteState by viewModel.toggleFavoriteState.collectAsStateWithLifecycle()
@@ -146,7 +147,7 @@ fun ContractorDetailScreen(
.testTag(AccessibilityIds.Contractor.detailView) .testTag(AccessibilityIds.Contractor.detailView)
) { ) {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val residences = DataManager.residences.value val residences = dataManager.residences.value
ApiResultHandler( ApiResultHandler(
state = contractorState, state = contractorState,

View File

@@ -20,7 +20,7 @@ import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.ResidenceViewModel import com.tt.honeyDue.viewmodel.ResidenceViewModel
import com.tt.honeyDue.viewmodel.TaskViewModel import com.tt.honeyDue.viewmodel.TaskViewModel
import com.tt.honeyDue.network.ApiResult import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import honeydue.composeapp.generated.resources.* import honeydue.composeapp.generated.resources.*
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@@ -33,8 +33,9 @@ fun HomeScreen(
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() }, viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() },
taskViewModel: TaskViewModel = viewModel { TaskViewModel() } taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
) { ) {
val dataManager = LocalDataManager.current
val summaryState by viewModel.myResidencesState.collectAsStateWithLifecycle() val summaryState by viewModel.myResidencesState.collectAsStateWithLifecycle()
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle() val totalSummary by dataManager.totalSummary.collectAsStateWithLifecycle()
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {

View File

@@ -28,7 +28,7 @@ import com.tt.honeyDue.ui.theme.*
import com.tt.honeyDue.viewmodel.AuthViewModel import com.tt.honeyDue.viewmodel.AuthViewModel
import com.tt.honeyDue.network.ApiResult import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.network.APILayer import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.ui.subscription.UpgradePromptDialog import com.tt.honeyDue.ui.subscription.UpgradePromptDialog
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -68,8 +68,9 @@ fun ProfileScreen(
val updateState by viewModel.updateProfileState.collectAsStateWithLifecycle() val updateState by viewModel.updateProfileState.collectAsStateWithLifecycle()
val deleteAccountState by viewModel.deleteAccountState.collectAsStateWithLifecycle() val deleteAccountState by viewModel.deleteAccountState.collectAsStateWithLifecycle()
val currentTheme by remember { derivedStateOf { ThemeManager.currentTheme } } val currentTheme by remember { derivedStateOf { ThemeManager.currentTheme } }
val currentSubscription by DataManager.subscription.collectAsStateWithLifecycle() val dataManager = LocalDataManager.current
val currentUser by DataManager.currentUser.collectAsStateWithLifecycle() val currentSubscription by dataManager.subscription.collectAsStateWithLifecycle()
val currentUser by dataManager.currentUser.collectAsStateWithLifecycle()
// Handle errors for profile update // Handle errors for profile update
updateState.HandleErrors( updateState.HandleErrors(

View File

@@ -40,7 +40,7 @@ import com.tt.honeyDue.models.ContractorSummary
import com.tt.honeyDue.network.ApiResult import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.utils.SubscriptionHelper import com.tt.honeyDue.utils.SubscriptionHelper
import com.tt.honeyDue.ui.subscription.UpgradePromptDialog import com.tt.honeyDue.ui.subscription.UpgradePromptDialog
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.util.DateUtils import com.tt.honeyDue.util.DateUtils
import com.tt.honeyDue.platform.rememberShareResidence import com.tt.honeyDue.platform.rememberShareResidence
import com.tt.honeyDue.analytics.PostHogAnalytics import com.tt.honeyDue.analytics.PostHogAnalytics
@@ -88,7 +88,8 @@ fun ResidenceDetailScreen(
var upgradeTriggerKey by remember { mutableStateOf<String?>(null) } var upgradeTriggerKey by remember { mutableStateOf<String?>(null) }
// Get current user for ownership checks // Get current user for ownership checks
val currentUser by DataManager.currentUser.collectAsStateWithLifecycle() val dataManager = LocalDataManager.current
val currentUser by dataManager.currentUser.collectAsStateWithLifecycle()
// Residence sharing state and function // Residence sharing state and function
val (shareState, shareResidence) = rememberShareResidence() val (shareState, shareResidence) = rememberShareResidence()

View File

@@ -21,7 +21,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.testing.AccessibilityIds import com.tt.honeyDue.testing.AccessibilityIds
import com.tt.honeyDue.viewmodel.ResidenceViewModel import com.tt.honeyDue.viewmodel.ResidenceViewModel
import com.tt.honeyDue.repository.LookupsRepository import com.tt.honeyDue.repository.LookupsRepository
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.models.Residence import com.tt.honeyDue.models.Residence
import com.tt.honeyDue.models.ResidenceCreateRequest import com.tt.honeyDue.models.ResidenceCreateRequest
import com.tt.honeyDue.models.ResidenceType import com.tt.honeyDue.models.ResidenceType
@@ -68,8 +68,9 @@ fun ResidenceFormScreen(
} else { } else {
viewModel.createResidenceState.collectAsStateWithLifecycle() viewModel.createResidenceState.collectAsStateWithLifecycle()
} }
val dataManager = LocalDataManager.current
val propertyTypes by LookupsRepository.residenceTypes.collectAsStateWithLifecycle() val propertyTypes by LookupsRepository.residenceTypes.collectAsStateWithLifecycle()
val currentUser by DataManager.currentUser.collectAsStateWithLifecycle() val currentUser by dataManager.currentUser.collectAsStateWithLifecycle()
// Check if current user is the owner // Check if current user is the owner
val isCurrentUserOwner = remember(existingResidence, currentUser) { val isCurrentUserOwner = remember(existingResidence, currentUser) {

View File

@@ -41,7 +41,7 @@ import com.tt.honeyDue.utils.SubscriptionHelper
import com.tt.honeyDue.ui.subscription.UpgradePromptDialog import com.tt.honeyDue.ui.subscription.UpgradePromptDialog
import com.tt.honeyDue.analytics.PostHogAnalytics import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.analytics.AnalyticsEvents import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.ui.theme.* import com.tt.honeyDue.ui.theme.*
import honeydue.composeapp.generated.resources.* import honeydue.composeapp.generated.resources.*
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@@ -61,8 +61,9 @@ fun ResidencesScreen(
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() }, viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() },
taskViewModel: TaskViewModel = viewModel { TaskViewModel() } taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
) { ) {
val dataManager = LocalDataManager.current
val myResidencesState by viewModel.myResidencesState.collectAsStateWithLifecycle() val myResidencesState by viewModel.myResidencesState.collectAsStateWithLifecycle()
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle() val totalSummary by dataManager.totalSummary.collectAsStateWithLifecycle()
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
var showUpgradePrompt by remember { mutableStateOf(false) } var showUpgradePrompt by remember { mutableStateOf(false) }
var upgradeTriggerKey by remember { mutableStateOf<String?>(null) } var upgradeTriggerKey by remember { mutableStateOf<String?>(null) }

View File

@@ -24,7 +24,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.AnalyticsEvents import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.analytics.PostHogAnalytics import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.models.TaskCreateRequest import com.tt.honeyDue.models.TaskCreateRequest
import com.tt.honeyDue.models.TaskSuggestionResponse import com.tt.honeyDue.models.TaskSuggestionResponse
import com.tt.honeyDue.models.TaskSuggestionsResponse import com.tt.honeyDue.models.TaskSuggestionsResponse
@@ -66,6 +66,7 @@ fun OnboardingFirstTaskContent(
viewModel: OnboardingViewModel, viewModel: OnboardingViewModel,
onTasksAdded: () -> Unit onTasksAdded: () -> Unit
) { ) {
val dataManager = LocalDataManager.current
var selectedBrowseIds by remember { mutableStateOf(setOf<Int>()) } var selectedBrowseIds by remember { mutableStateOf(setOf<Int>()) }
var selectedSuggestionIds by remember { mutableStateOf(setOf<Int>()) } var selectedSuggestionIds by remember { mutableStateOf(setOf<Int>()) }
var expandedCategoryId by remember { mutableStateOf<Int?>(null) } var expandedCategoryId by remember { mutableStateOf<Int?>(null) }
@@ -79,7 +80,7 @@ fun OnboardingFirstTaskContent(
// Kick off both network calls on mount. Suggestions needs a residence; // Kick off both network calls on mount. Suggestions needs a residence;
// the grouped catalog is user-independent and safe to load immediately. // the grouped catalog is user-independent and safe to load immediately.
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
val residence = DataManager.residences.value.firstOrNull() val residence = dataManager.residences.value.firstOrNull()
if (residence != null) { if (residence != null) {
viewModel.loadSuggestions(residence.id) viewModel.loadSuggestions(residence.id)
} }
@@ -256,7 +257,7 @@ fun OnboardingFirstTaskContent(
} }
// Tab content // Tab content
val residenceForRetry = DataManager.residences.value.firstOrNull() val residenceForRetry = dataManager.residences.value.firstOrNull()
if (selectedTabIndex == 0 && showTabs) { if (selectedTabIndex == 0 && showTabs) {
ForYouTabContent( ForYouTabContent(
suggestionsState = suggestionsState, suggestionsState = suggestionsState,
@@ -328,7 +329,7 @@ fun OnboardingFirstTaskContent(
if (selectedBrowseIds.isEmpty() && selectedSuggestionIds.isEmpty()) { if (selectedBrowseIds.isEmpty() && selectedSuggestionIds.isEmpty()) {
skipOnboarding("user_skip") skipOnboarding("user_skip")
} else { } else {
val residence = DataManager.residences.value.firstOrNull() val residence = dataManager.residences.value.firstOrNull()
if (residence != null) { if (residence != null) {
val today = DateUtils.getTodayString() val today = DateUtils.getTodayString()
val taskRequests = mutableListOf<TaskCreateRequest>() val taskRequests = mutableListOf<TaskCreateRequest>()

View File

@@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tt.honeyDue.analytics.AnalyticsEvents import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.analytics.PostHogAnalytics import com.tt.honeyDue.analytics.PostHogAnalytics
import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.LocalDataManager
import com.tt.honeyDue.models.FeatureBenefit import com.tt.honeyDue.models.FeatureBenefit
import com.tt.honeyDue.ui.components.common.StandardCard import com.tt.honeyDue.ui.components.common.StandardCard
import com.tt.honeyDue.ui.theme.AppSpacing import com.tt.honeyDue.ui.theme.AppSpacing
@@ -56,7 +56,9 @@ import com.tt.honeyDue.ui.theme.AppSpacing
* - Header ("Choose Your Plan" + subtitle) * - Header ("Choose Your Plan" + subtitle)
* - Two-column comparison table rendered inside a [StandardCard] * - Two-column comparison table rendered inside a [StandardCard]
* (Feature | Free | Pro) with N rows sourced from * (Feature | Free | Pro) with N rows sourced from
* `DataManager.featureBenefits`, falling back to a 4-row default list. * `LocalDataManager.current.featureBenefits` (resolves to the
* `DataManager` singleton in production), falling back to a 4-row
* default list.
* - CTA button at the bottom ("Upgrade to Pro") which fires analytics * - CTA button at the bottom ("Upgrade to Pro") which fires analytics
* event [AnalyticsEvents.PAYWALL_COMPARE_CTA] and invokes * event [AnalyticsEvents.PAYWALL_COMPARE_CTA] and invokes
* [onNavigateToUpgrade]. * [onNavigateToUpgrade].
@@ -71,7 +73,8 @@ fun FeatureComparisonScreen(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateToUpgrade: () -> Unit, onNavigateToUpgrade: () -> Unit,
) { ) {
val benefits by DataManager.featureBenefits.collectAsStateWithLifecycle() val dataManager = LocalDataManager.current
val benefits by dataManager.featureBenefits.collectAsStateWithLifecycle()
val rows = FeatureComparisonScreenState.resolveFeatureRows(benefits) val rows = FeatureComparisonScreenState.resolveFeatureRows(benefits)
Scaffold( Scaffold(