Make residence address fields optional (only name required)
Updated Kotlin models, Android UI, and iOS UI to make all address fields optional for residences. Only the residence name is now required. Changes: - Kotlin: Made propertyType, streetAddress, city, stateProvince, postalCode, country nullable in Residence, ResidenceSummary, ResidenceWithTasks models - Kotlin: Updated navigation routes to handle nullable address fields - Android: Updated ResidenceFormScreen and ResidenceDetailScreen to handle nulls - iOS: Updated ResidenceFormView validation to only check name field - iOS: Updated PropertyHeaderCard and ResidenceCard to use optional binding for address field displays 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -286,7 +286,7 @@ fun App(
|
||||
EditResidenceRoute(
|
||||
residenceId = residence.id,
|
||||
name = residence.name,
|
||||
propertyType = residence.propertyType.toInt(),
|
||||
propertyType = residence.propertyType?.toInt(),
|
||||
streetAddress = residence.streetAddress,
|
||||
apartmentUnit = residence.apartmentUnit,
|
||||
city = residence.city,
|
||||
@@ -452,7 +452,7 @@ fun App(
|
||||
EditResidenceRoute(
|
||||
residenceId = residence.id,
|
||||
name = residence.name,
|
||||
propertyType = residence.propertyType.toInt(),
|
||||
propertyType = residence.propertyType?.toInt(),
|
||||
streetAddress = residence.streetAddress,
|
||||
apartmentUnit = residence.apartmentUnit,
|
||||
city = residence.city,
|
||||
|
||||
@@ -11,21 +11,21 @@ data class Residence(
|
||||
@SerialName("is_primary_owner") val isPrimaryOwner: Boolean = false,
|
||||
@SerialName("user_count") val userCount: Int = 1,
|
||||
val name: String,
|
||||
@SerialName("property_type") val propertyType: String,
|
||||
@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?,
|
||||
val bathrooms: Float?,
|
||||
@SerialName("square_footage") val squareFootage: Int?,
|
||||
@SerialName("lot_size") val lotSize: Float?,
|
||||
@SerialName("year_built") val yearBuilt: Int?,
|
||||
val description: String?,
|
||||
@SerialName("purchase_date") val purchaseDate: String?,
|
||||
@SerialName("purchase_price") val purchasePrice: Double?,
|
||||
@SerialName("property_type") val propertyType: String? = 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: Float? = null,
|
||||
@SerialName("square_footage") val squareFootage: Int? = null,
|
||||
@SerialName("lot_size") val lotSize: Float? = 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 = false,
|
||||
@SerialName("created_at") val createdAt: String,
|
||||
@SerialName("updated_at") val updatedAt: String
|
||||
@@ -34,13 +34,13 @@ data class Residence(
|
||||
@Serializable
|
||||
data class ResidenceCreateRequest(
|
||||
val name: String,
|
||||
@SerialName("property_type") val propertyType: Int,
|
||||
@SerialName("street_address") val streetAddress: String,
|
||||
@SerialName("property_type") val propertyType: Int? = null,
|
||||
@SerialName("street_address") val streetAddress: String? = null,
|
||||
@SerialName("apartment_unit") val apartmentUnit: String? = null,
|
||||
val city: String,
|
||||
@SerialName("state_province") val stateProvince: String,
|
||||
@SerialName("postal_code") val postalCode: String,
|
||||
val country: String,
|
||||
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: Float? = null,
|
||||
@SerialName("square_footage") val squareFootage: Int? = null,
|
||||
@@ -80,13 +80,13 @@ data class ResidenceSummary(
|
||||
val owner: Int,
|
||||
@SerialName("owner_username") val ownerUsername: String,
|
||||
val name: String,
|
||||
@SerialName("property_type") val propertyType: String,
|
||||
@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,
|
||||
@SerialName("property_type") val propertyType: String? = 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,
|
||||
@SerialName("is_primary") val isPrimary: Boolean,
|
||||
@SerialName("task_summary") val taskSummary: TaskSummary,
|
||||
@SerialName("last_completed_task") val lastCompletedCustomTask: CustomTask?,
|
||||
@@ -119,21 +119,21 @@ data class ResidenceWithTasks(
|
||||
@SerialName("is_primary_owner") val isPrimaryOwner: Boolean = false,
|
||||
@SerialName("user_count") val userCount: Int = 1,
|
||||
val name: String,
|
||||
@SerialName("property_type") val propertyType: String,
|
||||
@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?,
|
||||
val bathrooms: Float?,
|
||||
@SerialName("square_footage") val squareFootage: Int?,
|
||||
@SerialName("lot_size") val lotSize: Float?,
|
||||
@SerialName("year_built") val yearBuilt: Int?,
|
||||
val description: String?,
|
||||
@SerialName("purchase_date") val purchaseDate: String?,
|
||||
@SerialName("purchase_price") val purchasePrice: Double?,
|
||||
@SerialName("property_type") val propertyType: String? = 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: Float? = null,
|
||||
@SerialName("square_footage") val squareFootage: Int? = null,
|
||||
@SerialName("lot_size") val lotSize: Float? = 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,
|
||||
@SerialName("task_summary") val taskSummary: TaskSummary,
|
||||
val tasks: List<TaskDetail>,
|
||||
|
||||
@@ -25,13 +25,13 @@ object AddResidenceRoute
|
||||
data class EditResidenceRoute(
|
||||
val residenceId: Int,
|
||||
val name: String,
|
||||
val propertyType: Int,
|
||||
val streetAddress: String,
|
||||
val propertyType: Int?,
|
||||
val streetAddress: String?,
|
||||
val apartmentUnit: String?,
|
||||
val city: String,
|
||||
val stateProvince: String,
|
||||
val postalCode: String,
|
||||
val country: String,
|
||||
val city: String?,
|
||||
val stateProvince: String?,
|
||||
val postalCode: String?,
|
||||
val country: String?,
|
||||
val bedrooms: Int?,
|
||||
val bathrooms: Float?,
|
||||
val squareFootage: Int?,
|
||||
|
||||
@@ -471,17 +471,27 @@ fun ResidenceDetailScreen(
|
||||
}
|
||||
|
||||
// Address Card
|
||||
item {
|
||||
InfoCard(
|
||||
icon = Icons.Default.LocationOn,
|
||||
title = "Address"
|
||||
) {
|
||||
Text(text = residence.streetAddress)
|
||||
if (residence.apartmentUnit != null) {
|
||||
Text(text = "Unit: ${residence.apartmentUnit}")
|
||||
if (residence.streetAddress != null || residence.city != null ||
|
||||
residence.stateProvince != null || residence.postalCode != null ||
|
||||
residence.country != null) {
|
||||
item {
|
||||
InfoCard(
|
||||
icon = Icons.Default.LocationOn,
|
||||
title = "Address"
|
||||
) {
|
||||
if (residence.streetAddress != null) {
|
||||
Text(text = residence.streetAddress)
|
||||
}
|
||||
if (residence.apartmentUnit != null) {
|
||||
Text(text = "Unit: ${residence.apartmentUnit}")
|
||||
}
|
||||
if (residence.city != null || residence.stateProvince != null || residence.postalCode != null) {
|
||||
Text(text = "${residence.city ?: ""}, ${residence.stateProvince ?: ""} ${residence.postalCode ?: ""}")
|
||||
}
|
||||
if (residence.country != null) {
|
||||
Text(text = residence.country)
|
||||
}
|
||||
}
|
||||
Text(text = "${residence.city}, ${residence.stateProvince} ${residence.postalCode}")
|
||||
Text(text = residence.country)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,10 +56,6 @@ fun ResidenceFormScreen(
|
||||
|
||||
// Validation errors
|
||||
var nameError by remember { mutableStateOf("") }
|
||||
var streetAddressError by remember { mutableStateOf("") }
|
||||
var cityError by remember { mutableStateOf("") }
|
||||
var stateProvinceError by remember { mutableStateOf("") }
|
||||
var postalCodeError by remember { mutableStateOf("") }
|
||||
|
||||
// Handle operation state changes
|
||||
LaunchedEffect(operationState) {
|
||||
@@ -79,10 +75,12 @@ fun ResidenceFormScreen(
|
||||
// Set default/existing property type when types are loaded
|
||||
LaunchedEffect(propertyTypes, existingResidence) {
|
||||
if (propertyTypes.isNotEmpty() && propertyType == null) {
|
||||
propertyType = if (isEditMode && existingResidence != null) {
|
||||
propertyType = if (isEditMode && existingResidence != null && existingResidence.propertyType != null) {
|
||||
propertyTypes.find { it.id == existingResidence.propertyType.toInt() }
|
||||
} else {
|
||||
} else if (!isEditMode && propertyTypes.isNotEmpty()) {
|
||||
propertyTypes.first()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,34 +95,6 @@ fun ResidenceFormScreen(
|
||||
nameError = ""
|
||||
}
|
||||
|
||||
if (streetAddress.isBlank()) {
|
||||
streetAddressError = "Street address is required"
|
||||
isValid = false
|
||||
} else {
|
||||
streetAddressError = ""
|
||||
}
|
||||
|
||||
if (city.isBlank()) {
|
||||
cityError = "City is required"
|
||||
isValid = false
|
||||
} else {
|
||||
cityError = ""
|
||||
}
|
||||
|
||||
if (stateProvince.isBlank()) {
|
||||
stateProvinceError = "State/Province is required"
|
||||
isValid = false
|
||||
} else {
|
||||
stateProvinceError = ""
|
||||
}
|
||||
|
||||
if (postalCode.isBlank()) {
|
||||
postalCodeError = "Postal code is required"
|
||||
isValid = false
|
||||
} else {
|
||||
postalCodeError = ""
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
@@ -148,9 +118,9 @@ fun ResidenceFormScreen(
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// Required fields section
|
||||
// Basic Information section
|
||||
Text(
|
||||
text = "Required Information",
|
||||
text = "Property Details",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
@@ -162,8 +132,10 @@ fun ResidenceFormScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = nameError.isNotEmpty(),
|
||||
supportingText = if (nameError.isNotEmpty()) {
|
||||
{ Text(nameError) }
|
||||
} else null
|
||||
{ Text(nameError, color = MaterialTheme.colorScheme.error) }
|
||||
} else {
|
||||
{ Text("Required", color = MaterialTheme.colorScheme.error) }
|
||||
}
|
||||
)
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
@@ -174,7 +146,7 @@ fun ResidenceFormScreen(
|
||||
value = propertyType?.name?.replaceFirstChar { it.uppercase() } ?: "",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text("Property Type *") },
|
||||
label = { Text("Property Type") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -197,15 +169,18 @@ fun ResidenceFormScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Address section
|
||||
Text(
|
||||
text = "Address",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = streetAddress,
|
||||
onValueChange = { streetAddress = it },
|
||||
label = { Text("Street Address *") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = streetAddressError.isNotEmpty(),
|
||||
supportingText = if (streetAddressError.isNotEmpty()) {
|
||||
{ Text(streetAddressError) }
|
||||
} else null
|
||||
label = { Text("Street Address") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
@@ -218,34 +193,22 @@ fun ResidenceFormScreen(
|
||||
OutlinedTextField(
|
||||
value = city,
|
||||
onValueChange = { city = it },
|
||||
label = { Text("City *") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = cityError.isNotEmpty(),
|
||||
supportingText = if (cityError.isNotEmpty()) {
|
||||
{ Text(cityError) }
|
||||
} else null
|
||||
label = { Text("City") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = stateProvince,
|
||||
onValueChange = { stateProvince = it },
|
||||
label = { Text("State/Province *") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = stateProvinceError.isNotEmpty(),
|
||||
supportingText = if (stateProvinceError.isNotEmpty()) {
|
||||
{ Text(stateProvinceError) }
|
||||
} else null
|
||||
label = { Text("State/Province") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = postalCode,
|
||||
onValueChange = { postalCode = it },
|
||||
label = { Text("Postal Code *") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = postalCodeError.isNotEmpty(),
|
||||
supportingText = if (postalCodeError.isNotEmpty()) {
|
||||
{ Text(postalCodeError) }
|
||||
} else null
|
||||
label = { Text("Postal Code") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
@@ -340,16 +303,16 @@ fun ResidenceFormScreen(
|
||||
// Submit button
|
||||
Button(
|
||||
onClick = {
|
||||
if (validateForm() && propertyType != null) {
|
||||
if (validateForm()) {
|
||||
val request = ResidenceCreateRequest(
|
||||
name = name,
|
||||
propertyType = propertyType!!.id,
|
||||
streetAddress = streetAddress,
|
||||
propertyType = propertyType?.id,
|
||||
streetAddress = streetAddress.ifBlank { null },
|
||||
apartmentUnit = apartmentUnit.ifBlank { null },
|
||||
city = city,
|
||||
stateProvince = stateProvince,
|
||||
postalCode = postalCode,
|
||||
country = country,
|
||||
city = city.ifBlank { null },
|
||||
stateProvince = stateProvince.ifBlank { null },
|
||||
postalCode = postalCode.ifBlank { null },
|
||||
country = country.ifBlank { null },
|
||||
bedrooms = bedrooms.toIntOrNull(),
|
||||
bathrooms = bathrooms.toFloatOrNull(),
|
||||
squareFootage = squareFootage.toIntOrNull(),
|
||||
@@ -367,7 +330,7 @@ fun ResidenceFormScreen(
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = validateForm() && propertyType != null
|
||||
enabled = validateForm()
|
||||
) {
|
||||
if (operationState is ApiResult.Loading) {
|
||||
CircularProgressIndicator(
|
||||
|
||||
Reference in New Issue
Block a user