Update Kotlin models and iOS Swift to align with new Go API format
- Update all Kotlin API models to match Go API response structures - Fix Swift type aliases (TaskDetail→TaskResponse, Residence→ResidenceResponse, etc.) - Update TaskCompletionCreateRequest to simplified Go API format (taskId, notes, actualCost, photoUrl) - Fix optional handling for frequency, priority, category, status in task models - Replace isPrimaryOwner with ownerId comparison against current user - Update ResidenceUsersResponse to use owner.id instead of ownerId - Fix non-optional String fields to use isEmpty checks instead of optional binding - Add type aliases for backwards compatibility in Kotlin models 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import ComposeApp
|
||||
|
||||
struct AddTaskWithResidenceView: View {
|
||||
@Binding var isPresented: Bool
|
||||
let residences: [Residence]
|
||||
let residences: [ResidenceResponse]
|
||||
|
||||
var body: some View {
|
||||
TaskFormView(residenceId: nil, residences: residences, isPresented: $isPresented)
|
||||
|
||||
@@ -11,13 +11,13 @@ struct AllTasksView: View {
|
||||
@State private var showAddTask = false
|
||||
@State private var showEditTask = false
|
||||
@State private var showingUpgradePrompt = false
|
||||
@State private var selectedTaskForEdit: TaskDetail?
|
||||
@State private var selectedTaskForComplete: TaskDetail?
|
||||
@State private var selectedTaskForEdit: TaskResponse?
|
||||
@State private var selectedTaskForComplete: TaskResponse?
|
||||
|
||||
@State private var selectedTaskForArchive: TaskDetail?
|
||||
@State private var selectedTaskForArchive: TaskResponse?
|
||||
@State private var showArchiveConfirmation = false
|
||||
|
||||
@State private var selectedTaskForCancel: TaskDetail?
|
||||
@State private var selectedTaskForCancel: TaskResponse?
|
||||
@State private var showCancelConfirmation = false
|
||||
|
||||
// Count total tasks across all columns
|
||||
@@ -334,37 +334,9 @@ struct RoundedCorner: Shape {
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == ResidenceWithTasks {
|
||||
/// Converts an array of ResidenceWithTasks into an array of Residence.
|
||||
/// Adjust the mapping inside as needed to match your model initializers.
|
||||
func toResidences() -> [Residence] {
|
||||
return self.map { item in
|
||||
return Residence(
|
||||
id: item.id,
|
||||
owner: KotlinInt(value: item.owner),
|
||||
ownerUsername: item.ownerUsername,
|
||||
isPrimaryOwner: item.isPrimaryOwner,
|
||||
userCount: item.userCount,
|
||||
name: item.name,
|
||||
propertyType: item.propertyType,
|
||||
streetAddress: item.streetAddress,
|
||||
apartmentUnit: item.apartmentUnit,
|
||||
city: item.city,
|
||||
stateProvince: item.stateProvince,
|
||||
postalCode: item.postalCode,
|
||||
country: item.country,
|
||||
bedrooms: item.bedrooms != nil ? KotlinInt(nonretainedObject: item.bedrooms!) : nil,
|
||||
bathrooms: item.bathrooms != nil ? KotlinFloat(float: Float(item.bathrooms!)) : nil,
|
||||
squareFootage: item.squareFootage != nil ? KotlinInt(nonretainedObject: item.squareFootage!) : nil,
|
||||
lotSize: item.lotSize != nil ? KotlinFloat(float: Float(item.lotSize!)) : nil,
|
||||
yearBuilt: item.yearBuilt != nil ? KotlinInt(nonretainedObject: item.yearBuilt!) : nil,
|
||||
description: item.description,
|
||||
purchaseDate: item.purchaseDate,
|
||||
purchasePrice: item.purchasePrice,
|
||||
isPrimary: item.isPrimary,
|
||||
createdAt: item.createdAt,
|
||||
updatedAt: item.updatedAt
|
||||
)
|
||||
}
|
||||
extension Array where Element == ResidenceResponse {
|
||||
/// Returns the array as-is (for API compatibility)
|
||||
func toResidences() -> [ResidenceResponse] {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ import PhotosUI
|
||||
import ComposeApp
|
||||
|
||||
struct CompleteTaskView: View {
|
||||
let task: TaskDetail
|
||||
let task: TaskResponse
|
||||
let onComplete: () -> Void
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@StateObject private var taskViewModel = TaskViewModel()
|
||||
@StateObject private var contractorViewModel = ContractorViewModel()
|
||||
private let completionViewModel = ComposeApp.TaskCompletionViewModel()
|
||||
@State private var completedByName: String = ""
|
||||
@State private var actualCost: String = ""
|
||||
@State private var notes: String = ""
|
||||
@@ -32,7 +33,7 @@ struct CompleteTaskView: View {
|
||||
.font(.headline)
|
||||
|
||||
HStack {
|
||||
Label(task.category.name.capitalized, systemImage: "folder")
|
||||
Label((task.category?.name ?? "").capitalized, systemImage: "folder")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
@@ -303,66 +304,53 @@ struct CompleteTaskView: View {
|
||||
|
||||
isSubmitting = true
|
||||
|
||||
// Get current date in ISO format
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
let currentDate = dateFormatter.string(from: Date())
|
||||
|
||||
// Create request
|
||||
// Create request with simplified Go API format
|
||||
// Note: completedAt defaults to now on server if not provided
|
||||
let request = TaskCompletionCreateRequest(
|
||||
task: task.id,
|
||||
completedByUser: nil,
|
||||
contractor: selectedContractor != nil ? KotlinInt(int: selectedContractor!.id) : nil,
|
||||
completedByName: completedByName.isEmpty ? nil : completedByName,
|
||||
completedByPhone: selectedContractor?.phone ?? "",
|
||||
completedByEmail: "",
|
||||
companyName: selectedContractor?.company ?? "",
|
||||
completionDate: currentDate,
|
||||
actualCost: actualCost.isEmpty ? nil : KotlinDouble(double: Double(actualCost) ?? 0.0),
|
||||
taskId: task.id,
|
||||
completedAt: nil,
|
||||
notes: notes.isEmpty ? nil : notes,
|
||||
rating: KotlinInt(int: Int32(rating))
|
||||
actualCost: actualCost.isEmpty ? nil : KotlinDouble(double: Double(actualCost) ?? 0.0),
|
||||
photoUrl: nil
|
||||
)
|
||||
|
||||
// Use TaskCompletionViewModel to create completion
|
||||
if !selectedImages.isEmpty {
|
||||
// Convert images to ImageData for Kotlin
|
||||
let imageDataList = selectedImages.compactMap { uiImage -> ComposeApp.ImageData? in
|
||||
guard let jpegData = uiImage.jpegData(compressionQuality: 0.8) else { return nil }
|
||||
let byteArray = KotlinByteArray(data: jpegData)
|
||||
return ComposeApp.ImageData(bytes: byteArray, fileName: "completion_image.jpg")
|
||||
}
|
||||
completionViewModel.createTaskCompletionWithImages(request: request, images: imageDataList)
|
||||
} else {
|
||||
completionViewModel.createTaskCompletion(request: request)
|
||||
}
|
||||
|
||||
// Observe the result
|
||||
Task {
|
||||
do {
|
||||
let result: ApiResult<TaskCompletion>
|
||||
|
||||
// If there are images, upload with images
|
||||
if !selectedImages.isEmpty {
|
||||
// Compress images to meet size requirements
|
||||
let imageDataArray = ImageCompression.compressImages(selectedImages)
|
||||
let imageByteArrays = imageDataArray.map { KotlinByteArray(data: $0) }
|
||||
let fileNames = (0..<imageDataArray.count).map { "image_\($0).jpg" }
|
||||
|
||||
result = try await APILayer.shared.createTaskCompletionWithImages(
|
||||
request: request,
|
||||
images: imageByteArrays,
|
||||
imageFileNames: fileNames
|
||||
)
|
||||
} else {
|
||||
// Upload without images
|
||||
result = try await APILayer.shared.createTaskCompletion(request: request)
|
||||
}
|
||||
|
||||
for await state in completionViewModel.createCompletionState {
|
||||
await MainActor.run {
|
||||
if result is ApiResultSuccess<TaskCompletion> {
|
||||
switch state {
|
||||
case is ApiResultSuccess<TaskCompletionResponse>:
|
||||
self.isSubmitting = false
|
||||
self.dismiss()
|
||||
self.onComplete()
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.errorMessage = errorResult.message
|
||||
self.showError = true
|
||||
self.isSubmitting = false
|
||||
} else {
|
||||
self.errorMessage = "Failed to complete task"
|
||||
case let error as ApiResultError:
|
||||
self.errorMessage = error.message
|
||||
self.showError = true
|
||||
self.isSubmitting = false
|
||||
case is ApiResultLoading:
|
||||
// Still loading, continue waiting
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.errorMessage = error.localizedDescription
|
||||
self.showError = true
|
||||
self.isSubmitting = false
|
||||
|
||||
// Break out of loop on terminal states
|
||||
if state is ApiResultSuccess<TaskCompletionResponse> || state is ApiResultError {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import ComposeApp
|
||||
/// Wrapper view for editing an existing task
|
||||
/// This is now just a convenience wrapper around TaskFormView in "edit" mode
|
||||
struct EditTaskView: View {
|
||||
let task: TaskDetail
|
||||
let task: TaskResponse
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -9,8 +9,8 @@ enum TaskFormField {
|
||||
// MARK: - Task Form View
|
||||
struct TaskFormView: View {
|
||||
let residenceId: Int32?
|
||||
let residences: [Residence]?
|
||||
let existingTask: TaskDetail? // nil for add mode, populated for edit mode
|
||||
let residences: [ResidenceResponse]?
|
||||
let existingTask: TaskResponse? // nil for add mode, populated for edit mode
|
||||
@Binding var isPresented: Bool
|
||||
@StateObject private var viewModel = TaskViewModel()
|
||||
@FocusState private var focusedField: TaskFormField?
|
||||
@@ -40,7 +40,7 @@ struct TaskFormView: View {
|
||||
@State private var isLoadingLookups: Bool = true
|
||||
|
||||
// Form fields
|
||||
@State private var selectedResidence: Residence?
|
||||
@State private var selectedResidence: ResidenceResponse?
|
||||
@State private var title: String
|
||||
@State private var description: String
|
||||
@State private var selectedCategory: TaskCategory?
|
||||
@@ -52,7 +52,7 @@ struct TaskFormView: View {
|
||||
@State private var estimatedCost: String
|
||||
|
||||
// Initialize form fields based on mode (add vs edit)
|
||||
init(residenceId: Int32? = nil, residences: [Residence]? = nil, existingTask: TaskDetail? = nil, isPresented: Binding<Bool>) {
|
||||
init(residenceId: Int32? = nil, residences: [ResidenceResponse]? = nil, existingTask: TaskResponse? = nil, isPresented: Binding<Bool>) {
|
||||
self.residenceId = residenceId
|
||||
self.residences = residences
|
||||
self.existingTask = existingTask
|
||||
@@ -72,7 +72,7 @@ struct TaskFormView: View {
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
_dueDate = State(initialValue: formatter.date(from: task.dueDate ?? "") ?? Date())
|
||||
|
||||
_intervalDays = State(initialValue: task.intervalDays != nil ? String(task.intervalDays!.intValue) : "")
|
||||
_intervalDays = State(initialValue: "") // No longer in API
|
||||
_estimatedCost = State(initialValue: task.estimatedCost != nil ? String(task.estimatedCost!.doubleValue) : "")
|
||||
} else {
|
||||
_title = State(initialValue: "")
|
||||
@@ -98,9 +98,9 @@ struct TaskFormView: View {
|
||||
if needsResidenceSelection, let residences = residences {
|
||||
Section {
|
||||
Picker("Property", selection: $selectedResidence) {
|
||||
Text("Select Property").tag(nil as Residence?)
|
||||
Text("Select Property").tag(nil as ResidenceResponse?)
|
||||
ForEach(residences, id: \.id) { residence in
|
||||
Text(residence.name).tag(residence as Residence?)
|
||||
Text(residence.name).tag(residence as ResidenceResponse?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,17 +396,17 @@ struct TaskFormView: View {
|
||||
if isEditMode, let task = existingTask {
|
||||
// UPDATE existing task
|
||||
let request = TaskCreateRequest(
|
||||
residence: task.residence,
|
||||
residenceId: task.residenceId,
|
||||
title: title,
|
||||
description: description.isEmpty ? nil : description,
|
||||
category: Int32(category.id),
|
||||
frequency: Int32(frequency.id),
|
||||
intervalDays: intervalDays.isEmpty ? nil : Int32(intervalDays) as? KotlinInt,
|
||||
priority: Int32(priority.id),
|
||||
status: KotlinInt(value: status.id) as? KotlinInt,
|
||||
categoryId: KotlinInt(int: Int32(category.id)),
|
||||
priorityId: KotlinInt(int: Int32(priority.id)),
|
||||
statusId: KotlinInt(int: Int32(status.id)),
|
||||
frequencyId: KotlinInt(int: Int32(frequency.id)),
|
||||
assignedToId: nil,
|
||||
dueDate: dueDateString,
|
||||
estimatedCost: estimatedCost.isEmpty ? nil : KotlinDouble(double: Double(estimatedCost) ?? 0.0),
|
||||
archived: task.archived
|
||||
contractorId: nil
|
||||
)
|
||||
|
||||
viewModel.updateTask(id: task.id, request: request) { success in
|
||||
@@ -427,17 +427,17 @@ struct TaskFormView: View {
|
||||
}
|
||||
|
||||
let request = TaskCreateRequest(
|
||||
residence: actualResidenceId,
|
||||
residenceId: actualResidenceId,
|
||||
title: title,
|
||||
description: description.isEmpty ? nil : description,
|
||||
category: Int32(category.id),
|
||||
frequency: Int32(frequency.id),
|
||||
intervalDays: intervalDays.isEmpty ? nil : Int32(intervalDays) as? KotlinInt,
|
||||
priority: Int32(priority.id),
|
||||
status: selectedStatus.map { KotlinInt(value: $0.id) },
|
||||
categoryId: KotlinInt(int: Int32(category.id)),
|
||||
priorityId: KotlinInt(int: Int32(priority.id)),
|
||||
statusId: selectedStatus.map { KotlinInt(int: Int32($0.id)) },
|
||||
frequencyId: KotlinInt(int: Int32(frequency.id)),
|
||||
assignedToId: nil,
|
||||
dueDate: dueDateString,
|
||||
estimatedCost: estimatedCost.isEmpty ? nil : KotlinDouble(double: Double(estimatedCost) ?? 0.0),
|
||||
archived: false
|
||||
contractorId: nil
|
||||
)
|
||||
|
||||
viewModel.createTask(request: request) { success in
|
||||
|
||||
@@ -45,7 +45,7 @@ class TaskViewModel: ObservableObject {
|
||||
self?.errorMessage = error
|
||||
}
|
||||
},
|
||||
onSuccess: { [weak self] (_: CustomTask) in
|
||||
onSuccess: { [weak self] (_: TaskResponse) in
|
||||
self?.actionState = .success(.create)
|
||||
},
|
||||
completion: completion,
|
||||
|
||||
Reference in New Issue
Block a user