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:
Trey t
2025-11-20 23:03:45 -06:00
parent 630e95e462
commit dd5e050025
8 changed files with 228 additions and 245 deletions

View File

@@ -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,

View File

@@ -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>,

View File

@@ -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?,

View File

@@ -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)
}
}

View File

@@ -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(

View File

@@ -30,10 +30,6 @@ struct ResidenceFormView: View {
// Validation errors
@State private var nameError: String = ""
@State private var streetAddressError: String = ""
@State private var cityError: String = ""
@State private var stateProvinceError: String = ""
@State private var postalCodeError: String = ""
enum Field {
case name, streetAddress, apartmentUnit, city, stateProvince, postalCode, country
@@ -44,12 +40,17 @@ struct ResidenceFormView: View {
existingResidence != nil
}
private var canSave: Bool {
!name.isEmpty
}
var body: some View {
NavigationView {
Form {
Section(header: Text("Property Details")) {
Section {
TextField("Property Name", text: $name)
.focused($focusedField, equals: .name)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.nameField)
if !nameError.isEmpty {
Text(nameError)
@@ -63,50 +64,41 @@ struct ResidenceFormView: View {
Text(type.name).tag(type as ResidenceType?)
}
}
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.propertyTypePicker)
} header: {
Text("Property Details")
} footer: {
Text("Required: Name")
.font(.caption)
.foregroundColor(.red)
}
Section(header: Text("Address")) {
Section {
TextField("Street Address", text: $streetAddress)
.focused($focusedField, equals: .streetAddress)
if !streetAddressError.isEmpty {
Text(streetAddressError)
.font(.caption)
.foregroundColor(.red)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.streetAddressField)
TextField("Apartment/Unit (optional)", text: $apartmentUnit)
.focused($focusedField, equals: .apartmentUnit)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.apartmentUnitField)
TextField("City", text: $city)
.focused($focusedField, equals: .city)
if !cityError.isEmpty {
Text(cityError)
.font(.caption)
.foregroundColor(.red)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.cityField)
TextField("State/Province", text: $stateProvince)
.focused($focusedField, equals: .stateProvince)
if !stateProvinceError.isEmpty {
Text(stateProvinceError)
.font(.caption)
.foregroundColor(.red)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.stateProvinceField)
TextField("Postal Code", text: $postalCode)
.focused($focusedField, equals: .postalCode)
if !postalCodeError.isEmpty {
Text(postalCodeError)
.font(.caption)
.foregroundColor(.red)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.postalCodeField)
TextField("Country", text: $country)
.focused($focusedField, equals: .country)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.countryField)
} header: {
Text("Address")
}
Section(header: Text("Property Features")) {
@@ -118,6 +110,7 @@ struct ResidenceFormView: View {
.multilineTextAlignment(.trailing)
.frame(width: 60)
.focused($focusedField, equals: .bedrooms)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.bedroomsField)
}
HStack {
@@ -128,26 +121,32 @@ struct ResidenceFormView: View {
.multilineTextAlignment(.trailing)
.frame(width: 60)
.focused($focusedField, equals: .bathrooms)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.bathroomsField)
}
TextField("Square Footage", text: $squareFootage)
.keyboardType(.numberPad)
.focused($focusedField, equals: .squareFootage)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.squareFootageField)
TextField("Lot Size (acres)", text: $lotSize)
.keyboardType(.decimalPad)
.focused($focusedField, equals: .lotSize)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.lotSizeField)
TextField("Year Built", text: $yearBuilt)
.keyboardType(.numberPad)
.focused($focusedField, equals: .yearBuilt)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.yearBuiltField)
}
Section(header: Text("Additional Details")) {
TextField("Description (optional)", text: $description, axis: .vertical)
.lineLimit(3...6)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.descriptionField)
Toggle("Primary Residence", isOn: $isPrimary)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.isPrimaryToggle)
}
if let errorMessage = viewModel.errorMessage {
@@ -165,13 +164,15 @@ struct ResidenceFormView: View {
Button("Cancel") {
isPresented = false
}
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.formCancelButton)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
submitForm()
}
.disabled(viewModel.isLoading)
.disabled(!canSave || viewModel.isLoading)
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.saveButton)
}
}
.onAppear {
@@ -197,7 +198,9 @@ struct ResidenceFormView: View {
} else {
// Fallback to DataCache directly
await MainActor.run {
self.residenceTypes = DataCache.shared.residenceTypes.value as! [ResidenceType]
if let cached = DataCache.shared.residenceTypes.value as? [ResidenceType] {
self.residenceTypes = cached
}
}
}
}
@@ -207,12 +210,12 @@ struct ResidenceFormView: View {
if let residence = existingResidence {
// Edit mode - populate fields from existing residence
name = residence.name
streetAddress = residence.streetAddress
streetAddress = residence.streetAddress ?? ""
apartmentUnit = residence.apartmentUnit ?? ""
city = residence.city
stateProvince = residence.stateProvince
postalCode = residence.postalCode
country = residence.country
city = residence.city ?? ""
stateProvince = residence.stateProvince ?? ""
postalCode = residence.postalCode ?? ""
country = residence.country ?? ""
bedrooms = residence.bedrooms != nil ? "\(residence.bedrooms!)" : ""
bathrooms = residence.bathrooms != nil ? "\(residence.bathrooms!)" : ""
squareFootage = residence.squareFootage != nil ? "\(residence.squareFootage!)" : ""
@@ -222,13 +225,11 @@ struct ResidenceFormView: View {
isPrimary = residence.isPrimary
// Set the selected property type
selectedPropertyType = residenceTypes.first { $0.id == Int(residence.propertyType) ?? 0 }
} else {
// Add mode - set default property type
if selectedPropertyType == nil && !residenceTypes.isEmpty {
selectedPropertyType = residenceTypes.first
if let propertyTypeStr = residence.propertyType, let propertyTypeId = Int(propertyTypeStr) {
selectedPropertyType = residenceTypes.first { $0.id == propertyTypeId }
}
}
// In add mode, leave selectedPropertyType as nil to force user to select
}
private func validateForm() -> Bool {
@@ -241,57 +242,54 @@ struct ResidenceFormView: View {
nameError = ""
}
if streetAddress.isEmpty {
streetAddressError = "Street address is required"
isValid = false
} else {
streetAddressError = ""
}
if city.isEmpty {
cityError = "City is required"
isValid = false
} else {
cityError = ""
}
if stateProvince.isEmpty {
stateProvinceError = "State/Province is required"
isValid = false
} else {
stateProvinceError = ""
}
if postalCode.isEmpty {
postalCodeError = "Postal code is required"
isValid = false
} else {
postalCodeError = ""
}
return isValid
}
private func submitForm() {
guard validateForm() else { return }
guard let propertyType = selectedPropertyType else {
return
}
// Convert optional numeric fields to Kotlin types
let bedroomsValue: KotlinInt? = {
guard !bedrooms.isEmpty, let value = Int32(bedrooms) else { return nil }
return KotlinInt(int: value)
}()
let bathroomsValue: KotlinFloat? = {
guard !bathrooms.isEmpty, let value = Float(bathrooms) else { return nil }
return KotlinFloat(float: value)
}()
let squareFootageValue: KotlinInt? = {
guard !squareFootage.isEmpty, let value = Int32(squareFootage) else { return nil }
return KotlinInt(int: value)
}()
let lotSizeValue: KotlinFloat? = {
guard !lotSize.isEmpty, let value = Float(lotSize) else { return nil }
return KotlinFloat(float: value)
}()
let yearBuiltValue: KotlinInt? = {
guard !yearBuilt.isEmpty, let value = Int32(yearBuilt) else { return nil }
return KotlinInt(int: value)
}()
// Convert propertyType to KotlinInt if it exists
let propertyTypeValue: KotlinInt? = {
guard let type = selectedPropertyType else { return nil }
return KotlinInt(int: Int32(type.id))
}()
let request = ResidenceCreateRequest(
name: name,
propertyType: Int32(propertyType.id),
streetAddress: streetAddress,
propertyType: propertyTypeValue,
streetAddress: streetAddress.isEmpty ? nil : streetAddress,
apartmentUnit: apartmentUnit.isEmpty ? nil : apartmentUnit,
city: city,
stateProvince: stateProvince,
postalCode: postalCode,
country: country,
bedrooms: Int32(bedrooms) as? KotlinInt,
bathrooms: Float(bathrooms) as? KotlinFloat,
squareFootage: Int32(squareFootage) as? KotlinInt,
lotSize: Float(lotSize) as? KotlinFloat,
yearBuilt: Int32(yearBuilt) as? KotlinInt,
city: city.isEmpty ? nil : city,
stateProvince: stateProvince.isEmpty ? nil : stateProvince,
postalCode: postalCode.isEmpty ? nil : postalCode,
country: country.isEmpty ? nil : country,
bedrooms: bedroomsValue,
bathrooms: bathroomsValue,
squareFootage: squareFootageValue,
lotSize: lotSizeValue,
yearBuilt: yearBuiltValue,
description: description.isEmpty ? nil : description,
purchaseDate: nil,
purchasePrice: nil,

View File

@@ -16,9 +16,11 @@ struct PropertyHeaderCard: View {
.font(.title2)
.fontWeight(.bold)
Text(residence.propertyType)
.font(.caption)
.foregroundColor(.secondary)
if let propertyType = residence.propertyType {
Text(propertyType)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
@@ -27,15 +29,19 @@ struct PropertyHeaderCard: View {
Divider()
VStack(alignment: .leading, spacing: 4) {
Label(residence.streetAddress, systemImage: "mappin.circle.fill")
.font(.subheadline)
if let streetAddress = residence.streetAddress {
Label(streetAddress, systemImage: "mappin.circle.fill")
.font(.subheadline)
}
Text("\(residence.city), \(residence.stateProvince) \(residence.postalCode)")
.font(.subheadline)
.foregroundColor(.secondary)
if residence.city != nil || residence.stateProvince != nil || residence.postalCode != nil {
Text("\(residence.city ?? ""), \(residence.stateProvince ?? "") \(residence.postalCode ?? "")")
.font(.subheadline)
.foregroundColor(.secondary)
}
if !residence.country.isEmpty {
Text(residence.country)
if let country = residence.country, !country.isEmpty {
Text(country)
.font(.caption)
.foregroundColor(.secondary)
}

View File

@@ -26,11 +26,13 @@ struct ResidenceCard: View {
.foregroundColor(Color(.label))
.lineLimit(1)
Text(residence.propertyType)
.font(.caption.weight(.medium))
.foregroundColor(Color(.tertiaryLabel))
.textCase(.uppercase)
.tracking(0.5)
if let propertyType = residence.propertyType {
Text(propertyType)
.font(.caption.weight(.medium))
.foregroundColor(Color(.tertiaryLabel))
.textCase(.uppercase)
.tracking(0.5)
}
}
Spacer()
@@ -49,22 +51,26 @@ struct ResidenceCard: View {
// Address
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "mappin.circle.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color(.tertiaryLabel))
Text(residence.streetAddress)
.font(.callout)
.foregroundColor(Color(.secondaryLabel))
if let streetAddress = residence.streetAddress {
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "mappin.circle.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color(.tertiaryLabel))
Text(streetAddress)
.font(.callout)
.foregroundColor(Color(.secondaryLabel))
}
}
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "location.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color(.tertiaryLabel))
Text("\(residence.city), \(residence.stateProvince)")
.font(.callout)
.foregroundColor(Color(.secondaryLabel))
if residence.city != nil || residence.stateProvince != nil {
HStack(spacing: AppSpacing.xxs) {
Image(systemName: "location.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color(.tertiaryLabel))
Text("\(residence.city ?? ""), \(residence.stateProvince ?? "")")
.font(.callout)
.foregroundColor(Color(.secondaryLabel))
}
}
}
.padding(.vertical, AppSpacing.xs)