This commit is contained in:
Trey t
2025-11-05 19:21:46 -06:00
parent 6bad8d6b5a
commit fe99f67f81
18 changed files with 1203 additions and 80 deletions

View File

@@ -0,0 +1,251 @@
import WidgetKit
import SwiftUI
import ComposeApp
// MARK: - Timeline Entry
struct TaskWidgetEntry: TimelineEntry {
let date: Date
let tasks: [CustomTask]
}
// MARK: - Provider
struct TaskWidgetProvider: TimelineProvider {
func placeholder(in context: Context) -> TaskWidgetEntry {
TaskWidgetEntry(date: Date(), tasks: [])
}
func getSnapshot(in context: Context, completion: @escaping (TaskWidgetEntry) -> ()) {
let tasks = LookupsManager.shared.allTasks
let entry = TaskWidgetEntry(
date: Date(),
tasks: Array(tasks.prefix(5))
)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<TaskWidgetEntry>) -> ()) {
let tasks = LookupsManager.shared.allTasks
let entry = TaskWidgetEntry(
date: Date(),
tasks: Array(tasks.prefix(5))
)
// Refresh every hour
let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: Date())!
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
// MARK: - Widget View
struct TaskWidgetEntryView: View {
var entry: TaskWidgetEntry
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall:
SmallWidgetView(tasks: entry.tasks)
case .systemMedium:
MediumWidgetView(tasks: entry.tasks)
default:
SmallWidgetView(tasks: entry.tasks)
}
}
}
// MARK: - Small Widget
struct SmallWidgetView: View {
let tasks: [CustomTask]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "house.fill")
.foregroundColor(.blue)
Text("Tasks")
.font(.headline)
.fontWeight(.bold)
}
if tasks.isEmpty {
Spacer()
Text("No tasks")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
} else {
if let firstTask = tasks.first {
VStack(alignment: .leading, spacing: 4) {
Text(firstTask.title)
.font(.subheadline)
.fontWeight(.medium)
.lineLimit(2)
HStack {
Text(firstTask.priority?.uppercased() ?? "")
.font(.caption2)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(priorityColor(firstTask.priority))
.foregroundColor(.white)
.cornerRadius(4)
Text(firstTask.dueDate ?? "")
.font(.caption2)
.foregroundColor(.secondary)
}
}
}
Spacer()
Text("\(tasks.count) total tasks")
.font(.caption2)
.foregroundColor(.secondary)
}
}
.padding()
}
func priorityColor(_ priority: String?) -> Color {
switch priority?.lowercased() {
case "urgent": return .red
case "high": return .orange
case "medium": return .blue
default: return .gray
}
}
}
// MARK: - Medium Widget
struct MediumWidgetView: View {
let tasks: [CustomTask]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "house.fill")
.foregroundColor(.blue)
Text("My Tasks")
.font(.headline)
.fontWeight(.bold)
Spacer()
Text("\(tasks.count)")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
if tasks.isEmpty {
Spacer()
HStack {
Spacer()
VStack {
Image(systemName: "checkmark.circle")
.font(.largeTitle)
.foregroundColor(.green)
Text("All done!")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
Spacer()
} else {
ForEach(tasks.prefix(3), id: \.id) { task in
HStack(spacing: 8) {
Circle()
.fill(priorityColor(task.priority))
.frame(width: 8, height: 8)
VStack(alignment: .leading, spacing: 2) {
Text(task.title)
.font(.caption)
.fontWeight(.medium)
.lineLimit(1)
Text(task.dueDate ?? "No due date")
.font(.caption2)
.foregroundColor(.secondary)
}
Spacer()
Text(task.priority?.uppercased() ?? "")
.font(.caption2)
.foregroundColor(.secondary)
}
}
}
}
.padding()
}
func priorityColor(_ priority: String?) -> Color {
switch priority?.lowercased() {
case "urgent": return .red
case "high": return .orange
case "medium": return .blue
default: return .gray
}
}
}
// MARK: - Widget Configuration
@main
struct TaskWidget: Widget {
let kind: String = "TaskWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: TaskWidgetProvider()) { entry in
if #available(iOS 17.0, *) {
TaskWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
TaskWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("My Tasks")
.description("View your upcoming tasks at a glance")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
// MARK: - Preview
struct TaskWidget_Previews: PreviewProvider {
static var previews: some View {
let sampleTasks = [
CustomTask(
id: 1,
residence: 1,
createdBy: 1,
createdByUsername: "user",
title: "Fix leaky faucet",
description: "Kitchen sink",
category: "plumbing",
priority: "high",
status: "pending",
dueDate: "2024-12-15",
estimatedCost: nil,
actualCost: nil,
notes: nil,
createdAt: "2024-01-01T00:00:00Z",
updatedAt: "2024-01-01T00:00:00Z",
showCompletedButton: false,
daysUntilDue: nil,
isOverdue: nil,
lastCompletion: nil
)
]
TaskWidgetEntryView(entry: TaskWidgetEntry(date: Date(), tasks: sampleTasks))
.previewContext(WidgetPreviewContext(family: .systemSmall))
TaskWidgetEntryView(entry: TaskWidgetEntry(date: Date(), tasks: sampleTasks))
.previewContext(WidgetPreviewContext(family: .systemMedium))
}
}