wip
This commit is contained in:
267
iosApp/iosApp/Task/AllTasksView.swift
Normal file
267
iosApp/iosApp/Task/AllTasksView.swift
Normal file
@@ -0,0 +1,267 @@
|
||||
import SwiftUI
|
||||
import ComposeApp
|
||||
|
||||
struct AllTasksView: View {
|
||||
@StateObject private var taskViewModel = TaskViewModel()
|
||||
@State private var tasksResponse: AllTasksResponse?
|
||||
@State private var isLoadingTasks = false
|
||||
@State private var tasksError: String?
|
||||
@State private var showAddTask = false
|
||||
@State private var showEditTask = false
|
||||
@State private var selectedTaskForEdit: TaskDetail?
|
||||
@State private var showInProgressTasks = false
|
||||
@State private var showDoneTasks = false
|
||||
@State private var showCompleteTask = false
|
||||
@State private var selectedTaskForComplete: TaskDetail?
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemGroupedBackground)
|
||||
.ignoresSafeArea()
|
||||
|
||||
if isLoadingTasks {
|
||||
ProgressView()
|
||||
} else if let error = tasksError {
|
||||
ErrorView(message: error) {
|
||||
loadAllTasks()
|
||||
}
|
||||
} else if let tasksResponse = tasksResponse {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
// Header Card
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "checklist")
|
||||
.font(.system(size: 48))
|
||||
.foregroundStyle(.blue.gradient)
|
||||
|
||||
Text("All Tasks")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("Tasks across all your properties")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
|
||||
.padding(.horizontal)
|
||||
.padding(.top)
|
||||
|
||||
// Tasks Section
|
||||
AllTasksSectionView(
|
||||
tasksResponse: tasksResponse,
|
||||
showInProgressTasks: $showInProgressTasks,
|
||||
showDoneTasks: $showDoneTasks,
|
||||
onEditTask: { task in
|
||||
selectedTaskForEdit = task
|
||||
showEditTask = true
|
||||
},
|
||||
onCancelTask: { task in
|
||||
taskViewModel.cancelTask(id: task.id) { _ in
|
||||
loadAllTasks()
|
||||
}
|
||||
},
|
||||
onUncancelTask: { task in
|
||||
taskViewModel.uncancelTask(id: task.id) { _ in
|
||||
loadAllTasks()
|
||||
}
|
||||
},
|
||||
onMarkInProgress: { task in
|
||||
taskViewModel.markInProgress(id: task.id) { success in
|
||||
if success {
|
||||
loadAllTasks()
|
||||
}
|
||||
}
|
||||
},
|
||||
onCompleteTask: { task in
|
||||
selectedTaskForComplete = task
|
||||
showCompleteTask = true
|
||||
}
|
||||
)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("All Tasks")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.sheet(isPresented: $showEditTask) {
|
||||
if let task = selectedTaskForEdit {
|
||||
EditTaskView(task: task, isPresented: $showEditTask)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showCompleteTask) {
|
||||
if let task = selectedTaskForComplete {
|
||||
CompleteTaskView(task: task, isPresented: $showCompleteTask) {
|
||||
loadAllTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: showEditTask) { isShowing in
|
||||
if !isShowing {
|
||||
loadAllTasks()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
loadAllTasks()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAllTasks() {
|
||||
guard let token = TokenStorage.shared.getToken() else { return }
|
||||
|
||||
isLoadingTasks = true
|
||||
tasksError = nil
|
||||
|
||||
let taskApi = TaskApi(client: ApiClient_iosKt.createHttpClient())
|
||||
taskApi.getTasks(token: token, days: 30) { result, error in
|
||||
if let successResult = result as? ApiResultSuccess<AllTasksResponse> {
|
||||
self.tasksResponse = successResult.data
|
||||
self.isLoadingTasks = false
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.tasksError = errorResult.message
|
||||
self.isLoadingTasks = false
|
||||
} else if let error = error {
|
||||
self.tasksError = error.localizedDescription
|
||||
self.isLoadingTasks = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AllTasksSectionView: View {
|
||||
let tasksResponse: AllTasksResponse
|
||||
@Binding var showInProgressTasks: Bool
|
||||
@Binding var showDoneTasks: Bool
|
||||
let onEditTask: (TaskDetail) -> Void
|
||||
let onCancelTask: (TaskDetail) -> Void
|
||||
let onUncancelTask: (TaskDetail) -> Void
|
||||
let onMarkInProgress: (TaskDetail) -> Void
|
||||
let onCompleteTask: (TaskDetail) -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// Task summary pills
|
||||
HStack(spacing: 8) {
|
||||
TaskPill(
|
||||
count: Int32(tasksResponse.summary.upcoming),
|
||||
label: "Upcoming",
|
||||
color: .blue
|
||||
)
|
||||
|
||||
TaskPill(
|
||||
count: Int32(tasksResponse.summary.inProgress),
|
||||
label: "In Progress",
|
||||
color: .orange
|
||||
)
|
||||
|
||||
TaskPill(
|
||||
count: Int32(tasksResponse.summary.done),
|
||||
label: "Done",
|
||||
color: .green
|
||||
)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
|
||||
// Upcoming tasks
|
||||
if !tasksResponse.upcomingTasks.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Upcoming (\(tasksResponse.upcomingTasks.count))", systemImage: "calendar")
|
||||
.font(.headline)
|
||||
.foregroundColor(.blue)
|
||||
|
||||
ForEach(tasksResponse.upcomingTasks, id: \.id) { task in
|
||||
TaskCard(
|
||||
task: task,
|
||||
onEdit: { onEditTask(task) },
|
||||
onCancel: { onCancelTask(task) },
|
||||
onUncancel: { onUncancelTask(task) },
|
||||
onMarkInProgress: { onMarkInProgress(task) },
|
||||
onComplete: { onCompleteTask(task) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In Progress section (collapsible)
|
||||
if !tasksResponse.inProgressTasks.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Label("In Progress (\(tasksResponse.inProgressTasks.count))", systemImage: "play.circle")
|
||||
.font(.headline)
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: showInProgressTasks ? "chevron.up" : "chevron.down")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
showInProgressTasks.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if showInProgressTasks {
|
||||
ForEach(tasksResponse.inProgressTasks, id: \.id) { task in
|
||||
TaskCard(
|
||||
task: task,
|
||||
onEdit: { onEditTask(task) },
|
||||
onCancel: { onCancelTask(task) },
|
||||
onUncancel: { onUncancelTask(task) },
|
||||
onMarkInProgress: nil,
|
||||
onComplete: { onCompleteTask(task) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Done section (collapsible)
|
||||
if !tasksResponse.doneTasks.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Label("Done (\(tasksResponse.doneTasks.count))", systemImage: "checkmark.circle")
|
||||
.font(.headline)
|
||||
.foregroundColor(.green)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: showDoneTasks ? "chevron.up" : "chevron.down")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
showDoneTasks.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if showDoneTasks {
|
||||
ForEach(tasksResponse.doneTasks, id: \.id) { task in
|
||||
TaskCard(
|
||||
task: task,
|
||||
onEdit: { onEditTask(task) },
|
||||
onCancel: nil,
|
||||
onUncancel: nil,
|
||||
onMarkInProgress: nil,
|
||||
onComplete: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
AllTasksView()
|
||||
}
|
||||
}
|
||||
300
iosApp/iosApp/Task/CompleteTaskView.swift
Normal file
300
iosApp/iosApp/Task/CompleteTaskView.swift
Normal file
@@ -0,0 +1,300 @@
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
import ComposeApp
|
||||
|
||||
struct CompleteTaskView: View {
|
||||
let task: TaskDetail
|
||||
@Binding var isPresented: Bool
|
||||
let onComplete: () -> Void
|
||||
|
||||
@StateObject private var taskViewModel = TaskViewModel()
|
||||
@State private var completedByName: String = ""
|
||||
@State private var actualCost: String = ""
|
||||
@State private var notes: String = ""
|
||||
@State private var rating: Int = 3
|
||||
@State private var selectedItems: [PhotosPickerItem] = []
|
||||
@State private var selectedImages: [UIImage] = []
|
||||
@State private var isSubmitting: Bool = false
|
||||
@State private var showError: Bool = false
|
||||
@State private var errorMessage: String = ""
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
// Task Info Header
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(task.title)
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text(task.category.name.capitalized)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
// Completed By
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Completed By (Optional)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
TextField("Enter name or leave blank", text: $completedByName)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
// Actual Cost
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Actual Cost (Optional)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
HStack {
|
||||
Text("$")
|
||||
.foregroundColor(.secondary)
|
||||
TextField("0.00", text: $actualCost)
|
||||
.keyboardType(.decimalPad)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
// Notes
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Notes (Optional)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
TextEditor(text: $notes)
|
||||
.frame(minHeight: 100)
|
||||
.padding(8)
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
// Rating
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Rating")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
ForEach(1...5, id: \.self) { star in
|
||||
Image(systemName: star <= rating ? "star.fill" : "star")
|
||||
.font(.title2)
|
||||
.foregroundColor(star <= rating ? .yellow : .gray)
|
||||
.onTapGesture {
|
||||
rating = star
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text("\(rating) out of 5")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
// Image Picker
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Add Images (up to 5)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
PhotosPicker(
|
||||
selection: $selectedItems,
|
||||
maxSelectionCount: 5,
|
||||
matching: .images
|
||||
) {
|
||||
HStack {
|
||||
Image(systemName: "photo.on.rectangle.angled")
|
||||
Text("Select Images")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.blue.opacity(0.1))
|
||||
.foregroundColor(.blue)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.onChange(of: selectedItems) { newItems in
|
||||
Task {
|
||||
selectedImages = []
|
||||
for item in newItems {
|
||||
if let data = try? await item.loadTransferable(type: Data.self),
|
||||
let image = UIImage(data: data) {
|
||||
selectedImages.append(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display selected images
|
||||
if !selectedImages.isEmpty {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 12) {
|
||||
ForEach(selectedImages.indices, id: \.self) { index in
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Image(uiImage: selectedImages[index])
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 100, height: 100)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
|
||||
Button(action: {
|
||||
selectedImages.remove(at: index)
|
||||
selectedItems.remove(at: index)
|
||||
}) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.background(Circle().fill(Color.black.opacity(0.6)))
|
||||
}
|
||||
.padding(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
// Complete Button
|
||||
Button(action: handleComplete) {
|
||||
HStack {
|
||||
if isSubmitting {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
} else {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
Text("Complete Task")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(isSubmitting ? Color.gray : Color.green)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.disabled(isSubmitting)
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.navigationTitle("Complete Task")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
isPresented = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Error", isPresented: $showError) {
|
||||
Button("OK") {
|
||||
showError = false
|
||||
}
|
||||
} message: {
|
||||
Text(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleComplete() {
|
||||
isSubmitting = true
|
||||
|
||||
guard let token = TokenStorage.shared.getToken() else {
|
||||
errorMessage = "Not authenticated"
|
||||
showError = true
|
||||
isSubmitting = false
|
||||
return
|
||||
}
|
||||
|
||||
// Get current date in ISO format
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
let currentDate = dateFormatter.string(from: Date())
|
||||
|
||||
// Create request
|
||||
let request = TaskCompletionCreateRequest(
|
||||
task: task.id,
|
||||
completedByUser: nil,
|
||||
completedByName: completedByName.isEmpty ? nil : completedByName,
|
||||
completionDate: currentDate,
|
||||
actualCost: actualCost.isEmpty ? nil : actualCost,
|
||||
notes: notes.isEmpty ? nil : notes,
|
||||
rating: KotlinInt(int: Int32(rating))
|
||||
)
|
||||
|
||||
let completionApi = TaskCompletionApi(client: ApiClient_iosKt.createHttpClient())
|
||||
|
||||
// If there are images, upload with images
|
||||
if !selectedImages.isEmpty {
|
||||
let imageDataArray = selectedImages.compactMap { $0.jpegData(compressionQuality: 0.8) }
|
||||
let imageByteArrays = imageDataArray.map { KotlinByteArray(data: $0) }
|
||||
let fileNames = (0..<imageDataArray.count).map { "image_\($0).jpg" }
|
||||
|
||||
completionApi.createCompletionWithImages(
|
||||
token: token,
|
||||
request: request,
|
||||
images: imageByteArrays,
|
||||
imageFileNames: fileNames
|
||||
) { result, error in
|
||||
handleCompletionResult(result: result, error: error)
|
||||
}
|
||||
} else {
|
||||
// Upload without images
|
||||
completionApi.createCompletion(token: token, request: request) { result, error in
|
||||
handleCompletionResult(result: result, error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCompletionResult(result: ApiResult<TaskCompletion>?, error: Error?) {
|
||||
if result is ApiResultSuccess<TaskCompletion> {
|
||||
isSubmitting = false
|
||||
isPresented = false
|
||||
onComplete()
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
errorMessage = errorResult.message
|
||||
showError = true
|
||||
isSubmitting = false
|
||||
} else if let error = error {
|
||||
errorMessage = error.localizedDescription
|
||||
showError = true
|
||||
isSubmitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper extension to convert Data to KotlinByteArray
|
||||
extension KotlinByteArray {
|
||||
convenience init(data: Data) {
|
||||
let array = [UInt8](data)
|
||||
self.init(size: Int32(array.count))
|
||||
for (index, byte) in array.enumerated() {
|
||||
self.set(index: Int32(index), value: Int8(bitPattern: byte))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ class TaskViewModel: ObservableObject {
|
||||
@Published var taskUpdated: Bool = false
|
||||
@Published var taskCancelled: Bool = false
|
||||
@Published var taskUncancelled: Bool = false
|
||||
@Published var taskMarkedInProgress: Bool = false
|
||||
|
||||
// MARK: - Private Properties
|
||||
private let taskApi: TaskApi
|
||||
@@ -19,8 +20,7 @@ class TaskViewModel: ObservableObject {
|
||||
// MARK: - Initialization
|
||||
init() {
|
||||
self.taskApi = TaskApi(client: ApiClient_iosKt.createHttpClient())
|
||||
self.tokenStorage = TokenStorage()
|
||||
self.tokenStorage.initialize(manager: TokenManager.init())
|
||||
self.tokenStorage = TokenStorage.shared
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
@@ -140,11 +140,81 @@ class TaskViewModel: ObservableObject {
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
func markInProgress(id: Int32, completion: @escaping (Bool) -> Void) {
|
||||
guard let token = tokenStorage.getToken() else {
|
||||
errorMessage = "Not authenticated"
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
taskMarkedInProgress = false
|
||||
|
||||
taskApi.markInProgress(token: token, id: id) { result, error in
|
||||
if result is ApiResultSuccess<TaskCancelResponse> {
|
||||
self.isLoading = false
|
||||
self.taskMarkedInProgress = true
|
||||
completion(true)
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.errorMessage = errorResult.message
|
||||
self.isLoading = false
|
||||
completion(false)
|
||||
} else if let error = error {
|
||||
self.errorMessage = error.localizedDescription
|
||||
self.isLoading = false
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func completeTask(taskId: Int32, completion: @escaping (Bool) -> Void) {
|
||||
guard let token = tokenStorage.getToken() else {
|
||||
errorMessage = "Not authenticated"
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
// Get current date in ISO format
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
let currentDate = dateFormatter.string(from: Date())
|
||||
|
||||
let request = TaskCompletionCreateRequest(
|
||||
task: taskId,
|
||||
completedByUser: nil,
|
||||
completedByName: nil,
|
||||
completionDate: currentDate,
|
||||
actualCost: nil,
|
||||
notes: nil,
|
||||
rating: nil
|
||||
)
|
||||
|
||||
let completionApi = TaskCompletionApi(client: ApiClient_iosKt.createHttpClient())
|
||||
completionApi.createCompletion(token: token, request: request) { result, error in
|
||||
if result is ApiResultSuccess<TaskCompletion> {
|
||||
self.isLoading = false
|
||||
completion(true)
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.errorMessage = errorResult.message
|
||||
self.isLoading = false
|
||||
completion(false)
|
||||
} else if let error = error {
|
||||
self.errorMessage = error.localizedDescription
|
||||
self.isLoading = false
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resetState() {
|
||||
taskCreated = false
|
||||
taskUpdated = false
|
||||
taskCancelled = false
|
||||
taskUncancelled = false
|
||||
taskMarkedInProgress = false
|
||||
errorMessage = nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user