feat: home/lock widgets, Health-aware streaks, workout editor, and entitlement fixes

Widgets (new KisaniCalWidgets extension)
- Event Countdown widget: configurable via AppIntent, pick from your events
  or set a custom name/date; dot-grid progress toward the event.
- Day Progress and Tasks-Done-Today widgets; lock screen widgets.
- Shared dot grid sizes circles to fill the widget evenly at any count.
- Tasks/calendar prefs now persist to the App Group so widgets read live data.

Health & streaks
- Persist HealthKit authorization and re-establish on launch (fixes the
  Today dashboard and streak sync disappearing after relaunch).
- Add a Health permission toggle to onboarding; unify Settings/Profile on the
  manager's state.
- Day streak counts from a logged Health workout OR marking all exercises
  complete; nudge to mark exercises complete even when Health logged the workout.

Workout editor
- Drag to reorder exercises and move them across sections (drag-and-drop);
  swipe to delete.
- Add-exercise sheet: global search and keep-adding-multiple with a running count.

Fixes
- Restore app entitlements link (Sign in with Apple, HealthKit, App Group, iCloud).
- Add HealthKit usage strings; widget Info.plist NSExtension + nested bundle id.
- Calendar "Connect" routes to Settings when access was denied.
- Bake DEVELOPMENT_TEAM and shared versions into project.yml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Robin Kutesa
2026-06-03 12:27:45 +03:00
parent e5bd739499
commit 8b094691f5
23 changed files with 1272 additions and 115 deletions

View File

@@ -1,4 +1,5 @@
import SwiftUI
import WidgetKit
struct ContentView: View {
@StateObject private var taskVM = TaskViewModel()
@@ -86,7 +87,13 @@ struct ContentView: View {
if !hasOnboarded { showOnboarding = true }
CloudSyncManager.shared.restoreSettings()
CloudSyncManager.shared.backupSettings()
HealthKitManager.shared.bootstrap()
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
// Pull workouts from Health, then reschedule so a detected workout updates reminders.
Task {
await workoutVM.syncFromHealthKit()
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
}
}
.onChange(of: scenePhase) { phase in
if phase == .active {
@@ -94,11 +101,15 @@ struct ContentView: View {
workoutVM.checkPendingHealthConfirm()
CloudSyncManager.shared.backupSettings()
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
Task { await workoutVM.syncFromHealthKit() }
Task {
await workoutVM.syncFromHealthKit()
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
}
}
}
.onChange(of: taskVM.tasks) { _ in
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
WidgetCenter.shared.reloadAllTimelines()
}
.onChange(of: workoutVM.doneSets) { _ in
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)