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.
344 lines
12 KiB
Kotlin
344 lines
12 KiB
Kotlin
package com.tt.honeyDue.models
|
|
|
|
import kotlinx.serialization.SerialName
|
|
import kotlinx.serialization.Serializable
|
|
|
|
/**
|
|
* User reference for residence responses - matching Go API ResidenceUserResponse
|
|
*/
|
|
@Serializable
|
|
data class ResidenceUserResponse(
|
|
val id: Int,
|
|
val username: String,
|
|
val email: String,
|
|
@SerialName("first_name") val firstName: String = "",
|
|
@SerialName("last_name") val lastName: String = ""
|
|
) {
|
|
val displayName: String
|
|
get() = when {
|
|
firstName.isNotBlank() && lastName.isNotBlank() -> "$firstName $lastName"
|
|
firstName.isNotBlank() -> firstName
|
|
lastName.isNotBlank() -> lastName
|
|
else -> username
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Residence response matching Go API ResidenceResponse
|
|
*/
|
|
@Serializable
|
|
data class ResidenceResponse(
|
|
val id: Int,
|
|
@SerialName("owner_id") val ownerId: Int,
|
|
val owner: ResidenceUserResponse? = null,
|
|
val users: List<ResidenceUserResponse> = emptyList(),
|
|
val name: String,
|
|
@SerialName("property_type_id") val propertyTypeId: Int? = null,
|
|
@SerialName("property_type") val propertyType: ResidenceType? = null,
|
|
@SerialName("street_address") val streetAddress: String = "",
|
|
@SerialName("apartment_unit") val apartmentUnit: String = "",
|
|
val city: String = "",
|
|
@SerialName("state_province") val stateProvince: String = "",
|
|
@SerialName("postal_code") val postalCode: String = "",
|
|
val country: String = "",
|
|
val bedrooms: Int? = null,
|
|
val bathrooms: Double? = null,
|
|
@SerialName("square_footage") val squareFootage: Int? = null,
|
|
@SerialName("lot_size") val lotSize: Double? = null,
|
|
@SerialName("year_built") val yearBuilt: Int? = null,
|
|
val description: String = "",
|
|
@SerialName("purchase_date") val purchaseDate: String? = null,
|
|
@SerialName("purchase_price") val purchasePrice: Double? = null,
|
|
@SerialName("is_primary") val isPrimary: Boolean = false,
|
|
@SerialName("is_active") val isActive: Boolean = true,
|
|
@SerialName("overdue_count") val overdueCount: Int = 0,
|
|
@SerialName("completion_summary") val completionSummary: CompletionSummary? = null,
|
|
@SerialName("heating_type") val heatingType: String? = null,
|
|
@SerialName("cooling_type") val coolingType: String? = null,
|
|
@SerialName("water_heater_type") val waterHeaterType: String? = null,
|
|
@SerialName("roof_type") val roofType: String? = null,
|
|
@SerialName("has_pool") val hasPool: Boolean = false,
|
|
@SerialName("has_sprinkler_system") val hasSprinklerSystem: Boolean = false,
|
|
@SerialName("has_septic") val hasSeptic: Boolean = false,
|
|
@SerialName("has_fireplace") val hasFireplace: Boolean = false,
|
|
@SerialName("has_garage") val hasGarage: Boolean = false,
|
|
@SerialName("has_basement") val hasBasement: Boolean = false,
|
|
@SerialName("has_attic") val hasAttic: Boolean = false,
|
|
@SerialName("exterior_type") val exteriorType: String? = null,
|
|
@SerialName("flooring_primary") val flooringPrimary: String? = null,
|
|
@SerialName("landscaping_type") val landscapingType: String? = null,
|
|
@SerialName("created_at") val createdAt: String,
|
|
@SerialName("updated_at") val updatedAt: String
|
|
) {
|
|
// Helper to get owner username
|
|
val ownerUsername: String get() = owner?.displayName ?: ""
|
|
|
|
// Helper to get property type name
|
|
val propertyTypeName: String? get() = propertyType?.name
|
|
|
|
// Backwards compatibility for UI code
|
|
// Note: isPrimaryOwner requires comparing with current user - can't be computed here
|
|
// UI components should check ownerId == currentUserId instead
|
|
|
|
// Stub task summary for UI compatibility (Go API doesn't return this per-residence)
|
|
val taskSummary: ResidenceTaskSummary get() = ResidenceTaskSummary()
|
|
|
|
// Stub summary for UI compatibility
|
|
val summary: ResidenceSummaryResponse get() = ResidenceSummaryResponse(id = id, name = name)
|
|
}
|
|
|
|
/**
|
|
* Residence create request matching Go API CreateResidenceRequest
|
|
*/
|
|
@Serializable
|
|
data class ResidenceCreateRequest(
|
|
val name: String,
|
|
@SerialName("property_type_id") val propertyTypeId: Int? = null,
|
|
@SerialName("street_address") val streetAddress: String? = null,
|
|
@SerialName("apartment_unit") val apartmentUnit: String? = null,
|
|
val city: String? = null,
|
|
@SerialName("state_province") val stateProvince: String? = null,
|
|
@SerialName("postal_code") val postalCode: String? = null,
|
|
val country: String? = null,
|
|
val bedrooms: Int? = null,
|
|
val bathrooms: Double? = null,
|
|
@SerialName("square_footage") val squareFootage: Int? = null,
|
|
@SerialName("lot_size") val lotSize: Double? = null,
|
|
@SerialName("year_built") val yearBuilt: Int? = null,
|
|
val description: String? = null,
|
|
@SerialName("purchase_date") val purchaseDate: String? = null,
|
|
@SerialName("purchase_price") val purchasePrice: Double? = null,
|
|
@SerialName("is_primary") val isPrimary: Boolean? = null,
|
|
@SerialName("heating_type") val heatingType: String? = null,
|
|
@SerialName("cooling_type") val coolingType: String? = null,
|
|
@SerialName("water_heater_type") val waterHeaterType: String? = null,
|
|
@SerialName("roof_type") val roofType: String? = null,
|
|
@SerialName("has_pool") val hasPool: Boolean? = null,
|
|
@SerialName("has_sprinkler_system") val hasSprinklerSystem: Boolean? = null,
|
|
@SerialName("has_septic") val hasSeptic: Boolean? = null,
|
|
@SerialName("has_fireplace") val hasFireplace: Boolean? = null,
|
|
@SerialName("has_garage") val hasGarage: Boolean? = null,
|
|
@SerialName("has_basement") val hasBasement: Boolean? = null,
|
|
@SerialName("has_attic") val hasAttic: Boolean? = null,
|
|
@SerialName("exterior_type") val exteriorType: String? = null,
|
|
@SerialName("flooring_primary") val flooringPrimary: String? = null,
|
|
@SerialName("landscaping_type") val landscapingType: String? = null
|
|
)
|
|
|
|
/**
|
|
* Residence update request matching Go API UpdateResidenceRequest
|
|
*/
|
|
@Serializable
|
|
data class ResidenceUpdateRequest(
|
|
val name: String? = null,
|
|
@SerialName("property_type_id") val propertyTypeId: Int? = null,
|
|
@SerialName("street_address") val streetAddress: String? = null,
|
|
@SerialName("apartment_unit") val apartmentUnit: String? = null,
|
|
val city: String? = null,
|
|
@SerialName("state_province") val stateProvince: String? = null,
|
|
@SerialName("postal_code") val postalCode: String? = null,
|
|
val country: String? = null,
|
|
val bedrooms: Int? = null,
|
|
val bathrooms: Double? = null,
|
|
@SerialName("square_footage") val squareFootage: Int? = null,
|
|
@SerialName("lot_size") val lotSize: Double? = null,
|
|
@SerialName("year_built") val yearBuilt: Int? = null,
|
|
val description: String? = null,
|
|
@SerialName("purchase_date") val purchaseDate: String? = null,
|
|
@SerialName("purchase_price") val purchasePrice: Double? = null,
|
|
@SerialName("is_primary") val isPrimary: Boolean? = null,
|
|
@SerialName("heating_type") val heatingType: String? = null,
|
|
@SerialName("cooling_type") val coolingType: String? = null,
|
|
@SerialName("water_heater_type") val waterHeaterType: String? = null,
|
|
@SerialName("roof_type") val roofType: String? = null,
|
|
@SerialName("has_pool") val hasPool: Boolean? = null,
|
|
@SerialName("has_sprinkler_system") val hasSprinklerSystem: Boolean? = null,
|
|
@SerialName("has_septic") val hasSeptic: Boolean? = null,
|
|
@SerialName("has_fireplace") val hasFireplace: Boolean? = null,
|
|
@SerialName("has_garage") val hasGarage: Boolean? = null,
|
|
@SerialName("has_basement") val hasBasement: Boolean? = null,
|
|
@SerialName("has_attic") val hasAttic: Boolean? = null,
|
|
@SerialName("exterior_type") val exteriorType: String? = null,
|
|
@SerialName("flooring_primary") val flooringPrimary: String? = null,
|
|
@SerialName("landscaping_type") val landscapingType: String? = null
|
|
)
|
|
|
|
/**
|
|
* Share code response matching Go API ShareCodeResponse
|
|
*/
|
|
@Serializable
|
|
data class ShareCodeResponse(
|
|
val id: Int,
|
|
val code: String,
|
|
@SerialName("residence_id") val residenceId: Int,
|
|
@SerialName("created_by_id") val createdById: Int,
|
|
@SerialName("is_active") val isActive: Boolean,
|
|
@SerialName("expires_at") val expiresAt: String?,
|
|
@SerialName("created_at") val createdAt: String
|
|
)
|
|
|
|
/**
|
|
* Generate share code request
|
|
*/
|
|
@Serializable
|
|
data class GenerateShareCodeRequest(
|
|
@SerialName("expires_in_hours") val expiresInHours: Int? = null
|
|
)
|
|
|
|
/**
|
|
* Generate share code response matching Go API GenerateShareCodeResponse
|
|
*/
|
|
@Serializable
|
|
data class GenerateShareCodeResponse(
|
|
val message: String,
|
|
@SerialName("share_code") val shareCode: ShareCodeResponse
|
|
)
|
|
|
|
/**
|
|
* Join residence request
|
|
*/
|
|
@Serializable
|
|
data class JoinResidenceRequest(
|
|
val code: String
|
|
)
|
|
|
|
/**
|
|
* Join residence response matching Go API JoinResidenceResponse
|
|
*/
|
|
@Serializable
|
|
data class JoinResidenceResponse(
|
|
val message: String,
|
|
val residence: ResidenceResponse,
|
|
val summary: TotalSummary
|
|
)
|
|
|
|
/**
|
|
* Total summary for dashboard display
|
|
*/
|
|
@Serializable
|
|
data class TotalSummary(
|
|
@SerialName("total_residences") val totalResidences: Int = 0,
|
|
@SerialName("total_tasks") val totalTasks: Int = 0,
|
|
@SerialName("total_pending") val totalPending: Int = 0,
|
|
@SerialName("total_overdue") val totalOverdue: Int = 0,
|
|
@SerialName("tasks_due_next_week") val tasksDueNextWeek: Int = 0,
|
|
@SerialName("tasks_due_next_month") val tasksDueNextMonth: Int = 0
|
|
)
|
|
|
|
/**
|
|
* Generic wrapper for CRUD responses that include TotalSummary.
|
|
* Used for Task and TaskCompletion operations to eliminate extra API calls
|
|
* for updating dashboard stats.
|
|
*
|
|
* Usage examples:
|
|
* - WithSummaryResponse<TaskResponse> for task CRUD
|
|
* - WithSummaryResponse<TaskCompletionResponse> for completion CRUD
|
|
* - WithSummaryResponse<String> for delete operations (data = "task deleted")
|
|
*/
|
|
@Serializable
|
|
data class WithSummaryResponse<T>(
|
|
val data: T,
|
|
val summary: TotalSummary
|
|
)
|
|
|
|
/**
|
|
* My residences response - list of user's residences
|
|
* NOTE: Summary statistics are calculated client-side from kanban data
|
|
*/
|
|
@Serializable
|
|
data class MyResidencesResponse(
|
|
val residences: List<ResidenceResponse>
|
|
)
|
|
|
|
/**
|
|
* Residence summary response for dashboard
|
|
*/
|
|
@Serializable
|
|
data class ResidenceSummaryResponse(
|
|
val id: Int = 0,
|
|
val name: String = "",
|
|
@SerialName("task_count") val taskCount: Int = 0,
|
|
@SerialName("pending_count") val pendingCount: Int = 0,
|
|
@SerialName("overdue_count") val overdueCount: Int = 0
|
|
)
|
|
|
|
/**
|
|
* Task category summary for residence
|
|
*/
|
|
@Serializable
|
|
data class TaskCategorySummary(
|
|
val name: String,
|
|
@SerialName("display_name") val displayName: String,
|
|
val icons: TaskCategoryIcons,
|
|
val color: String,
|
|
val count: Int
|
|
)
|
|
|
|
/**
|
|
* Icons for task category (Android/iOS)
|
|
*/
|
|
@Serializable
|
|
data class TaskCategoryIcons(
|
|
val android: String = "",
|
|
val ios: String = ""
|
|
)
|
|
|
|
/**
|
|
* Task summary per residence (for UI backwards compatibility)
|
|
*/
|
|
@Serializable
|
|
data class ResidenceTaskSummary(
|
|
val categories: List<TaskCategorySummary> = emptyList()
|
|
)
|
|
|
|
/**
|
|
* Residence users response - API returns a flat list of all users with access
|
|
*/
|
|
typealias ResidenceUsersResponse = List<ResidenceUserResponse>
|
|
|
|
/**
|
|
* Remove user response
|
|
*/
|
|
@Serializable
|
|
data class RemoveUserResponse(
|
|
val message: String
|
|
)
|
|
|
|
/**
|
|
* Completion summary for honeycomb grid display.
|
|
* Returned by the residence detail endpoint.
|
|
*/
|
|
@Serializable
|
|
data class CompletionSummary(
|
|
@SerialName("total_all_time") val totalAllTime: Int = 0,
|
|
@SerialName("total_last_12_months") val totalLast12Months: Int = 0,
|
|
val months: List<MonthlyCompletionSummary> = emptyList()
|
|
)
|
|
|
|
/**
|
|
* Monthly completion breakdown by kanban column.
|
|
*/
|
|
@Serializable
|
|
data class MonthlyCompletionSummary(
|
|
val month: String, // "2025-04" format
|
|
val completions: List<ColumnCompletionCount> = emptyList(),
|
|
val total: Int = 0,
|
|
val overflow: Int = 0
|
|
)
|
|
|
|
/**
|
|
* Count of completions from a specific kanban column with its color.
|
|
*/
|
|
@Serializable
|
|
data class ColumnCompletionCount(
|
|
val column: String,
|
|
val color: String,
|
|
val count: Int
|
|
)
|
|
|
|
// Type aliases for backwards compatibility with existing code
|
|
typealias Residence = ResidenceResponse
|
|
typealias ResidenceShareCode = ShareCodeResponse
|
|
typealias ResidenceUser = ResidenceUserResponse
|
|
typealias TaskSummary = ResidenceTaskSummary
|
|
typealias TaskColumnCategory = TaskCategorySummary |