- Remove TaskStatus model and status_id foreign key references - Add in_progress boolean field to task models and forms - Update TaskApi to use dedicated POST endpoints for task actions: - POST /tasks/:id/cancel/ instead of PATCH with is_cancelled - POST /tasks/:id/uncancel/ - POST /tasks/:id/archive/ - POST /tasks/:id/unarchive/ - Fix iOS TaskViewModel to use error-first pattern for Kotlin-Swift generic type bridging issues - Update iOS callback signatures to pass full TaskResponse instead of just taskId to avoid stale closure lookups - Add in_progress localization strings - Update widget preview data to use inProgress boolean 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
106 lines
4.0 KiB
Swift
106 lines
4.0 KiB
Swift
import Foundation
|
|
import ComposeApp
|
|
import WidgetKit
|
|
|
|
/// Processes pending actions queued by the widget extension
|
|
/// Call `processPendingActions()` when the app becomes active
|
|
@MainActor
|
|
final class WidgetActionProcessor {
|
|
static let shared = WidgetActionProcessor()
|
|
|
|
private init() {}
|
|
|
|
/// Check if there are pending widget actions to process
|
|
var hasPendingActions: Bool {
|
|
WidgetDataManager.shared.hasPendingActions
|
|
}
|
|
|
|
/// Process all pending widget actions
|
|
/// Should be called when app becomes active
|
|
func processPendingActions() {
|
|
guard DataManager.shared.isAuthenticated() else {
|
|
print("WidgetActionProcessor: Not authenticated, skipping action processing")
|
|
return
|
|
}
|
|
|
|
let actions = WidgetDataManager.shared.loadPendingActions()
|
|
guard !actions.isEmpty else {
|
|
print("WidgetActionProcessor: No pending actions")
|
|
return
|
|
}
|
|
|
|
print("WidgetActionProcessor: Processing \(actions.count) pending action(s)")
|
|
|
|
for action in actions {
|
|
Task {
|
|
await processAction(action)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Process a single widget action
|
|
private func processAction(_ action: WidgetDataManager.WidgetAction) async {
|
|
switch action {
|
|
case .completeTask(let taskId, let taskTitle):
|
|
await completeTask(taskId: taskId, taskTitle: taskTitle, action: action)
|
|
}
|
|
}
|
|
|
|
/// Complete a task via the API
|
|
private func completeTask(taskId: Int, taskTitle: String, action: WidgetDataManager.WidgetAction) async {
|
|
print("WidgetActionProcessor: Completing task \(taskId) - \(taskTitle)")
|
|
|
|
do {
|
|
// Create a task completion with default values (quick complete from widget)
|
|
let request = TaskCompletionCreateRequest(
|
|
taskId: Int32(taskId),
|
|
completedAt: nil, // Defaults to now on server
|
|
notes: "Completed from widget",
|
|
actualCost: nil,
|
|
rating: nil,
|
|
imageUrls: nil
|
|
)
|
|
|
|
let result = try await APILayer.shared.createTaskCompletion(request: request)
|
|
|
|
if result is ApiResultSuccess<TaskCompletionResponse> {
|
|
print("WidgetActionProcessor: Task \(taskId) completed successfully")
|
|
// Remove the processed action
|
|
WidgetDataManager.shared.removeAction(action)
|
|
// Clear pending state for this task
|
|
WidgetDataManager.shared.clearPendingState(forTaskId: taskId)
|
|
// Refresh tasks to update UI
|
|
await refreshTasks()
|
|
} else if let error = result as? ApiResultError {
|
|
print("WidgetActionProcessor: Failed to complete task \(taskId): \(error.message)")
|
|
// Remove action to avoid infinite retries
|
|
WidgetDataManager.shared.removeAction(action)
|
|
WidgetDataManager.shared.clearPendingState(forTaskId: taskId)
|
|
}
|
|
} catch {
|
|
print("WidgetActionProcessor: Error completing task \(taskId): \(error)")
|
|
// Remove action to avoid retries on error
|
|
WidgetDataManager.shared.removeAction(action)
|
|
WidgetDataManager.shared.clearPendingState(forTaskId: taskId)
|
|
}
|
|
}
|
|
|
|
/// Refresh tasks from the server to update UI and widget
|
|
private func refreshTasks() async {
|
|
do {
|
|
let result = try await APILayer.shared.getTasks(forceRefresh: true)
|
|
if let success = result as? ApiResultSuccess<TaskColumnsResponse>,
|
|
let data = success.data {
|
|
// Update widget with fresh data
|
|
WidgetDataManager.shared.saveTasks(from: data)
|
|
// Update summary from response (no extra API call needed)
|
|
if let summary = data.summary {
|
|
DataManager.shared.setTotalSummary(summary: summary)
|
|
}
|
|
}
|
|
} catch {
|
|
print("WidgetActionProcessor: Error refreshing tasks: \(error)")
|
|
}
|
|
}
|
|
}
|