Smart onboarding: home profile, tabbed tasks, free app
New onboarding step: "Tell us about your home" with chip-based pickers for systems (heating/cooling/water heater), features (pool, fireplace, garage, etc.), exterior (roof, siding), interior (flooring, landscaping). All optional, skippable. Tabbed task selection: "For You" tab shows personalized suggestions based on home profile, "Browse All" has existing category browser. Removed 5-task limit — users can add unlimited tasks. Removed subscription upsell from onboarding flow — app is free. Fixed picker capsule squishing bug with .fixedSize() modifier. Both iOS and Compose implementations updated.
This commit is contained in:
@@ -8,6 +8,7 @@ import com.tt.honeyDue.models.LoginRequest
|
||||
import com.tt.honeyDue.models.RegisterRequest
|
||||
import com.tt.honeyDue.models.ResidenceCreateRequest
|
||||
import com.tt.honeyDue.models.TaskCreateRequest
|
||||
import com.tt.honeyDue.models.TaskSuggestionsResponse
|
||||
import com.tt.honeyDue.models.TaskTemplate
|
||||
import com.tt.honeyDue.models.VerifyEmailRequest
|
||||
import com.tt.honeyDue.network.ApiResult
|
||||
@@ -37,6 +38,7 @@ enum class OnboardingStep {
|
||||
VERIFY_EMAIL,
|
||||
JOIN_RESIDENCE,
|
||||
RESIDENCE_LOCATION,
|
||||
HOME_PROFILE,
|
||||
FIRST_TASK,
|
||||
SUBSCRIPTION_UPSELL
|
||||
}
|
||||
@@ -90,6 +92,53 @@ class OnboardingViewModel : ViewModel() {
|
||||
private val _postalCode = MutableStateFlow("")
|
||||
val postalCode: StateFlow<String> = _postalCode
|
||||
|
||||
// Home profile fields
|
||||
private val _heatingType = MutableStateFlow<String?>(null)
|
||||
val heatingType: StateFlow<String?> = _heatingType
|
||||
|
||||
private val _coolingType = MutableStateFlow<String?>(null)
|
||||
val coolingType: StateFlow<String?> = _coolingType
|
||||
|
||||
private val _waterHeaterType = MutableStateFlow<String?>(null)
|
||||
val waterHeaterType: StateFlow<String?> = _waterHeaterType
|
||||
|
||||
private val _roofType = MutableStateFlow<String?>(null)
|
||||
val roofType: StateFlow<String?> = _roofType
|
||||
|
||||
private val _hasPool = MutableStateFlow(false)
|
||||
val hasPool: StateFlow<Boolean> = _hasPool
|
||||
|
||||
private val _hasSprinklerSystem = MutableStateFlow(false)
|
||||
val hasSprinklerSystem: StateFlow<Boolean> = _hasSprinklerSystem
|
||||
|
||||
private val _hasSeptic = MutableStateFlow(false)
|
||||
val hasSeptic: StateFlow<Boolean> = _hasSeptic
|
||||
|
||||
private val _hasFireplace = MutableStateFlow(false)
|
||||
val hasFireplace: StateFlow<Boolean> = _hasFireplace
|
||||
|
||||
private val _hasGarage = MutableStateFlow(false)
|
||||
val hasGarage: StateFlow<Boolean> = _hasGarage
|
||||
|
||||
private val _hasBasement = MutableStateFlow(false)
|
||||
val hasBasement: StateFlow<Boolean> = _hasBasement
|
||||
|
||||
private val _hasAttic = MutableStateFlow(false)
|
||||
val hasAttic: StateFlow<Boolean> = _hasAttic
|
||||
|
||||
private val _exteriorType = MutableStateFlow<String?>(null)
|
||||
val exteriorType: StateFlow<String?> = _exteriorType
|
||||
|
||||
private val _flooringPrimary = MutableStateFlow<String?>(null)
|
||||
val flooringPrimary: StateFlow<String?> = _flooringPrimary
|
||||
|
||||
private val _landscapingType = MutableStateFlow<String?>(null)
|
||||
val landscapingType: StateFlow<String?> = _landscapingType
|
||||
|
||||
// Task suggestions state
|
||||
private val _suggestionsState = MutableStateFlow<ApiResult<TaskSuggestionsResponse>>(ApiResult.Idle)
|
||||
val suggestionsState: StateFlow<ApiResult<TaskSuggestionsResponse>> = _suggestionsState
|
||||
|
||||
// Whether onboarding is complete
|
||||
private val _isComplete = MutableStateFlow(false)
|
||||
val isComplete: StateFlow<Boolean> = _isComplete
|
||||
@@ -106,6 +155,32 @@ class OnboardingViewModel : ViewModel() {
|
||||
_shareCode.value = code
|
||||
}
|
||||
|
||||
// Home profile setters
|
||||
fun setHeatingType(value: String?) { _heatingType.value = value }
|
||||
fun setCoolingType(value: String?) { _coolingType.value = value }
|
||||
fun setWaterHeaterType(value: String?) { _waterHeaterType.value = value }
|
||||
fun setRoofType(value: String?) { _roofType.value = value }
|
||||
fun setHasPool(value: Boolean) { _hasPool.value = value }
|
||||
fun setHasSprinklerSystem(value: Boolean) { _hasSprinklerSystem.value = value }
|
||||
fun setHasSeptic(value: Boolean) { _hasSeptic.value = value }
|
||||
fun setHasFireplace(value: Boolean) { _hasFireplace.value = value }
|
||||
fun setHasGarage(value: Boolean) { _hasGarage.value = value }
|
||||
fun setHasBasement(value: Boolean) { _hasBasement.value = value }
|
||||
fun setHasAttic(value: Boolean) { _hasAttic.value = value }
|
||||
fun setExteriorType(value: String?) { _exteriorType.value = value }
|
||||
fun setFlooringPrimary(value: String?) { _flooringPrimary.value = value }
|
||||
fun setLandscapingType(value: String?) { _landscapingType.value = value }
|
||||
|
||||
/**
|
||||
* Load personalized task suggestions for the given residence.
|
||||
*/
|
||||
fun loadSuggestions(residenceId: Int) {
|
||||
viewModelScope.launch {
|
||||
_suggestionsState.value = ApiResult.Loading
|
||||
_suggestionsState.value = APILayer.getTaskSuggestions(residenceId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the next step in the flow
|
||||
* Flow: Welcome → Features → Name Residence → Create Account → Verify → Tasks → Upsell
|
||||
@@ -129,9 +204,16 @@ class OnboardingViewModel : ViewModel() {
|
||||
OnboardingStep.RESIDENCE_LOCATION
|
||||
}
|
||||
}
|
||||
OnboardingStep.JOIN_RESIDENCE -> OnboardingStep.SUBSCRIPTION_UPSELL
|
||||
OnboardingStep.RESIDENCE_LOCATION -> OnboardingStep.FIRST_TASK
|
||||
OnboardingStep.FIRST_TASK -> OnboardingStep.SUBSCRIPTION_UPSELL
|
||||
OnboardingStep.JOIN_RESIDENCE -> {
|
||||
completeOnboarding()
|
||||
OnboardingStep.JOIN_RESIDENCE
|
||||
}
|
||||
OnboardingStep.RESIDENCE_LOCATION -> OnboardingStep.HOME_PROFILE
|
||||
OnboardingStep.HOME_PROFILE -> OnboardingStep.FIRST_TASK
|
||||
OnboardingStep.FIRST_TASK -> {
|
||||
completeOnboarding()
|
||||
OnboardingStep.FIRST_TASK
|
||||
}
|
||||
OnboardingStep.SUBSCRIPTION_UPSELL -> {
|
||||
completeOnboarding()
|
||||
OnboardingStep.SUBSCRIPTION_UPSELL
|
||||
@@ -171,9 +253,10 @@ class OnboardingViewModel : ViewModel() {
|
||||
fun skipStep() {
|
||||
when (_currentStep.value) {
|
||||
OnboardingStep.VALUE_PROPS,
|
||||
OnboardingStep.JOIN_RESIDENCE,
|
||||
OnboardingStep.RESIDENCE_LOCATION,
|
||||
OnboardingStep.FIRST_TASK -> nextStep()
|
||||
OnboardingStep.HOME_PROFILE -> nextStep()
|
||||
OnboardingStep.JOIN_RESIDENCE,
|
||||
OnboardingStep.FIRST_TASK,
|
||||
OnboardingStep.SUBSCRIPTION_UPSELL -> completeOnboarding()
|
||||
else -> {}
|
||||
}
|
||||
@@ -272,7 +355,21 @@ class OnboardingViewModel : ViewModel() {
|
||||
description = null,
|
||||
purchaseDate = null,
|
||||
purchasePrice = null,
|
||||
isPrimary = true
|
||||
isPrimary = true,
|
||||
heatingType = _heatingType.value,
|
||||
coolingType = _coolingType.value,
|
||||
waterHeaterType = _waterHeaterType.value,
|
||||
roofType = _roofType.value,
|
||||
hasPool = _hasPool.value.takeIf { it },
|
||||
hasSprinklerSystem = _hasSprinklerSystem.value.takeIf { it },
|
||||
hasSeptic = _hasSeptic.value.takeIf { it },
|
||||
hasFireplace = _hasFireplace.value.takeIf { it },
|
||||
hasGarage = _hasGarage.value.takeIf { it },
|
||||
hasBasement = _hasBasement.value.takeIf { it },
|
||||
hasAttic = _hasAttic.value.takeIf { it },
|
||||
exteriorType = _exteriorType.value,
|
||||
flooringPrimary = _flooringPrimary.value,
|
||||
landscapingType = _landscapingType.value
|
||||
)
|
||||
)
|
||||
|
||||
@@ -362,6 +459,21 @@ class OnboardingViewModel : ViewModel() {
|
||||
_createTasksState.value = ApiResult.Idle
|
||||
_regionalTemplates.value = ApiResult.Idle
|
||||
_postalCode.value = ""
|
||||
_heatingType.value = null
|
||||
_coolingType.value = null
|
||||
_waterHeaterType.value = null
|
||||
_roofType.value = null
|
||||
_hasPool.value = false
|
||||
_hasSprinklerSystem.value = false
|
||||
_hasSeptic.value = false
|
||||
_hasFireplace.value = false
|
||||
_hasGarage.value = false
|
||||
_hasBasement.value = false
|
||||
_hasAttic.value = false
|
||||
_exteriorType.value = null
|
||||
_flooringPrimary.value = null
|
||||
_landscapingType.value = null
|
||||
_suggestionsState.value = ApiResult.Idle
|
||||
_isComplete.value = false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user