feat: My Tasks widget, event countdown redesign, completed section
- My Tasks widget: today's tasks with interactive check-off (ToggleTaskIntent) and a "+" that opens the app's add-task sheet; writes preserve all task fields and sync via the App Group. - Event Countdown: AZURE/AWS-style header (accent name + countdown label + percent); tap the label to cycle days → weeks → time remaining. - Today: collapsible "Completed" archive section below Upcoming. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,16 @@ struct TodayView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// ── Completed (all, collapsible) ──
|
||||
if !taskVM.completedTasks.isEmpty {
|
||||
CompletedSection(
|
||||
tasks: taskVM.completedTasks,
|
||||
onUndo: { taskVM.toggle($0) }
|
||||
)
|
||||
.padding(.horizontal, 18)
|
||||
.padding(.top, 10)
|
||||
}
|
||||
|
||||
let hasAnything = !taskVM.overdueTasks.isEmpty || !taskVM.todayTasks.isEmpty
|
||||
|| !taskVM.upcomingTasks.isEmpty || !taskVM.completedTodayTasks.isEmpty
|
||||
|| !taskVM.next3DaysTasks.isEmpty || !todayCalEvents.isEmpty
|
||||
@@ -1009,6 +1019,74 @@ struct CompletedTodaySection: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Completed Section (all completed, collapsible archive)
|
||||
struct CompletedSection: View {
|
||||
@Environment(\.colorScheme) private var cs
|
||||
let tasks: [TaskItem]
|
||||
let onUndo: (TaskItem) -> Void
|
||||
|
||||
@State private var expanded = false
|
||||
|
||||
private let dateFmt: DateFormatter = {
|
||||
let f = DateFormatter(); f.dateFormat = "MMM d"; return f
|
||||
}()
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// Header
|
||||
HStack {
|
||||
Text("Completed")
|
||||
.font(AppFonts.sans(15, weight: .bold))
|
||||
.foregroundColor(AppColors.text2(cs))
|
||||
Spacer()
|
||||
Text("\(tasks.count)")
|
||||
.font(AppFonts.mono(11))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 11, weight: .semibold))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
.rotationEffect(.degrees(expanded ? 0 : -90))
|
||||
.animation(KisaniSpring.micro, value: expanded)
|
||||
}
|
||||
.padding(.horizontal, 14).padding(.vertical, 12)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { withAnimation(KisaniSpring.snappy) { expanded.toggle() } }
|
||||
|
||||
if expanded {
|
||||
ForEach(tasks) { task in
|
||||
HStack(spacing: 12) {
|
||||
Button {
|
||||
withAnimation(KisaniSpring.snappy) { onUndo(task) }
|
||||
} label: {
|
||||
Image(systemName: "checkmark.square.fill")
|
||||
.font(.system(size: 22))
|
||||
.foregroundColor(AppColors.text3(cs).opacity(0.6))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text(task.title)
|
||||
.font(AppFonts.sans(14))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
.strikethrough(true, color: AppColors.text3(cs).opacity(0.7))
|
||||
.lineLimit(1)
|
||||
Spacer(minLength: 8)
|
||||
if let d = task.completedAt {
|
||||
Text(dateFmt.string(from: d))
|
||||
.font(AppFonts.sans(13))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 14).padding(.vertical, 10)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
}
|
||||
.background(AppColors.surface(cs))
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppRadius.large))
|
||||
.overlay(RoundedRectangle(cornerRadius: AppRadius.large).stroke(AppColors.border(cs), lineWidth: 1))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Task Row
|
||||
struct TaskRowView: View {
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
Reference in New Issue
Block a user