feat: add widget extension — small, medium, large
- KisaniCalWidget: 3 sizes sharing the app's design language Small: date badge + kisaniCAL. wordmark + task count Medium: badge + task list (3 items) + workout row Large: badge + full task list (6 items) + workout row - WidgetDataStore (Shared/): lightweight Codable types for App Group UserDefaults cross-process data exchange - WidgetBridge: TaskViewModel/WorkoutViewModel extensions that sync data into App Group and call WidgetCenter.reloadAllTimelines() - ContentView: calls syncWidget() on appear and scene becoming active - project.yml + entitlements updated; project regenerated via xcodegen
This commit is contained in:
@@ -73,12 +73,19 @@ struct ContentView: View {
|
||||
OnboardingView(isPresented: $showOnboarding)
|
||||
.onDisappear { hasOnboarded = true }
|
||||
}
|
||||
.onAppear { if !hasOnboarded { showOnboarding = true } }
|
||||
.onAppear {
|
||||
if !hasOnboarded { showOnboarding = true }
|
||||
taskVM.syncWidget()
|
||||
workoutVM.syncWidget()
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
if phase == .active {
|
||||
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
|
||||
taskVM.syncWidget()
|
||||
workoutVM.syncWidget()
|
||||
}
|
||||
}
|
||||
.onChange(of: taskVM.tasks.count) { _ in taskVM.syncWidget() }
|
||||
.onChange(of: workoutVM.doneSets) { _ in
|
||||
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
|
||||
}
|
||||
|
||||
10
KisaniCal/KisaniCal.entitlements
Normal file
10
KisaniCal/KisaniCal.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.kutesir.KisaniCal</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
41
KisaniCal/WidgetBridge.swift
Normal file
41
KisaniCal/WidgetBridge.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import WidgetKit
|
||||
|
||||
// Syncs app ViewModels into App Group UserDefaults so the widget can read them,
|
||||
// then asks WidgetKit to reload all timelines.
|
||||
|
||||
extension TaskViewModel {
|
||||
func syncWidget() {
|
||||
let cal = Calendar.current
|
||||
let start = cal.startOfDay(for: Date())
|
||||
let end = cal.date(byAdding: .day, value: 1, to: start)!
|
||||
|
||||
let entries = tasks
|
||||
.filter { !$0.isComplete }
|
||||
.prefix(10)
|
||||
.map { t in
|
||||
WidgetDataStore.WidgetTask(
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
dueDate: t.dueDate,
|
||||
isToday: t.dueDate.map { $0 >= start && $0 < end } ?? false,
|
||||
isOverdue: t.dueDate.map { $0 < start } ?? false
|
||||
)
|
||||
}
|
||||
WidgetDataStore.saveTasks(Array(entries))
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
}
|
||||
}
|
||||
|
||||
extension WorkoutViewModel {
|
||||
func syncWidget() {
|
||||
let weekday = Calendar.current.component(.weekday, from: Date())
|
||||
let todayProgram = program(for: Date())
|
||||
let workout = WidgetDataStore.WidgetWorkout(
|
||||
name: activeProgram?.name ?? "Workout",
|
||||
sectionsCount: activeProgram?.sections.count ?? 0,
|
||||
hasWorkoutToday: todayProgram != nil
|
||||
)
|
||||
WidgetDataStore.saveWorkout(workout)
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user