diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png index ca9c554..4a4099c 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png index 8a9c51e..8944b24 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png index e502827..8fa1f33 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png index 812a32b..e454a3d 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png index a88dc57..a48852c 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png index 5a710d4..db63f40 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png index ac8d135..456bc34 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x.png index ac8d135..456bc34 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x.png index e0fde81..ebb7de9 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@1x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@1x.png index 6265f7b..7d0f9fb 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@1x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@1x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@2x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@2x.png index 7b13431..13173a2 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@2x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-76@2x.png differ diff --git a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png index 245e5c4..0789142 100644 Binary files a/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png and b/KisaniCal/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png differ diff --git a/KisaniCal/ContentView.swift b/KisaniCal/ContentView.swift index 5cbdda2..4c924b8 100644 --- a/KisaniCal/ContentView.swift +++ b/KisaniCal/ContentView.swift @@ -93,6 +93,7 @@ struct ContentView: View { } .onAppear { if !hasOnboarded { showOnboarding = true } + workoutVM.resetSetsForNewDayIfNeeded() CloudSyncManager.shared.restoreSettings() CloudSyncManager.shared.backupSettings() HealthKitManager.shared.bootstrap() @@ -108,6 +109,7 @@ struct ContentView: View { if phase == .active { taskVM.reload() consumeWidgetAddTask() + workoutVM.resetSetsForNewDayIfNeeded() workoutVM.checkPendingHealthConfirm() CloudSyncManager.shared.backupSettings() NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) diff --git a/KisaniCal/ISSUES.md b/KisaniCal/ISSUES.md new file mode 100644 index 0000000..bf6498e --- /dev/null +++ b/KisaniCal/ISSUES.md @@ -0,0 +1,58 @@ +# KisaniCal — Issue Tracker + +Status legend: `Open` · `In Progress` · `Resolved` + +--- + +## KC-1 — Daily workout log does not reset on a new day + +**Status:** In Progress (fix implemented & tested locally, pending release) +**Reported by:** User +**Area:** Workout + +### Description +When the same workout program is scheduled on back-to-back days (e.g. Shoulders +on Monday **and** Tuesday), the new day does not start fresh. Completed sets from +the previous day stay checked, so the workout appears already done. + +### Root cause +Set completion (`ExerciseSet.isDone`) was stored permanently inside the +`WorkoutProgram` and persisted via `save()`. There was no per-day reset, so a +program reused the next day carried over yesterday's checkmarks. + +### Fix +Set completion is now a per-day state: +- `WorkoutViewModel.resetSetsForNewDayIfNeeded()` clears every set's `isDone` + across all programs when the calendar day changes (skips first launch, no-ops + when the day is unchanged). +- `toggleSet(...)` stamps the active day so on-screen state always belongs to today. +- Called on cold launch (`init`), `.onAppear`, and `scenePhase == .active` + (handles crossing midnight while the app is alive). +- Streak history (`workoutDates`) is untouched, so completed days still count. + +Files: `KisaniCal/Models/ExerciseModels.swift`, `KisaniCal/ContentView.swift` + +### Verification +Builds cleanly (Xcode 26.2 SDK, simulator). Same program on consecutive days now +starts unchecked each day; streak preserved. + +--- + +## KC-2 — App logo update (new KisaniCal brand mark) + +**Status:** In Progress (assets installed & build-verified, pending release) +**Area:** Branding / App Icon + +### Description +Replace the app icon with the new KisaniCal logo (orange dot grid on dark) across +the App Store marketing icon and all home/lock screen / Spotlight / notification sizes. + +### Fix +All 12 sizes in `AppIcon.appiconset` regenerated from the new source. Alpha +flattened onto the brand dark background `RGB(26,29,34)` so the 1024 App Store icon +is fully opaque (App Store Connect compliant) and device icons are full-bleed. + +Files: `KisaniCal/Assets.xcassets/AppIcon.appiconset/` + +### Verification +Builds cleanly (app + widget extension); `hasAlpha: no` on all icons. diff --git a/KisaniCal/Models/ExerciseModels.swift b/KisaniCal/Models/ExerciseModels.swift index 8e52af7..9bdd888 100644 --- a/KisaniCal/Models/ExerciseModels.swift +++ b/KisaniCal/Models/ExerciseModels.swift @@ -208,6 +208,7 @@ class WorkoutViewModel: ObservableObject { private let kSchedule: String private let kActivePid: String private let kWorkoutDates: String + private let kLastActiveDay: String init() { let uid = AuthManager.shared.currentUser?.id ?? "shared" @@ -215,6 +216,7 @@ class WorkoutViewModel: ObservableObject { self.kSchedule = "kisani.workout.schedule.\(uid)" self.kActivePid = "kisani.workout.activePid.\(uid)" self.kWorkoutDates = "kisani.workout.completedDates.\(uid)" + self.kLastActiveDay = "kisani.workout.lastActiveDay.\(uid)" // Restore all workout keys from iCloud if missing locally (fresh install / new build) CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.programs.\(uid)") @@ -284,6 +286,7 @@ class WorkoutViewModel: ObservableObject { activeSections = blank.sections } workoutDates = UserDefaults.kisani.stringArray(forKey: kWorkoutDates) ?? [] + resetSetsForNewDayIfNeeded() } private func save() { @@ -300,6 +303,36 @@ class WorkoutViewModel: ObservableObject { CloudSyncManager.shared.push(value: workoutDates, forKey: kWorkoutDates) } + // MARK: - Daily Reset + /// A fresh calendar day starts a clean log. If the checkbox state currently on + /// screen belongs to an earlier day, wipe every set's `isDone` so re-running the + /// same program on back-to-back days (e.g. Shoulders on Mon AND Tue) starts + /// unchecked. Streak history (`workoutDates`) is never touched here. + /// Safe to call repeatedly — it only does work when the day actually changes. + func resetSetsForNewDayIfNeeded() { + let today = iso.string(from: Date()) + let last = UserDefaults.kisani.string(forKey: kLastActiveDay) + guard last != today else { return } + if last != nil { clearAllDoneSets() } // skip the wipe on the very first launch + UserDefaults.kisani.set(today, forKey: kLastActiveDay) + } + + /// Unchecks every set across all programs and clears the in-progress session. + private func clearAllDoneSets() { + for pi in programs.indices { + for si in programs[pi].sections.indices { + for ei in programs[pi].sections[si].exercises.indices { + for setIdx in programs[pi].sections[si].exercises[ei].sets.indices { + programs[pi].sections[si].exercises[ei].sets[setIdx].isDone = false + } + } + } + } + activeSections = programs.first { $0.id == activeProgramId }?.sections ?? activeSections + sessionStartDate = nil + save() + } + func logWorkoutCompleted(on date: Date = Date()) { let key = iso.string(from: date) guard !workoutDates.contains(key) else { return } @@ -452,6 +485,8 @@ class WorkoutViewModel: ObservableObject { else { return } let wasMarkedDone = !activeSections[si].exercises[ei].sets[setIdx].isDone activeSections[si].exercises[ei].sets[setIdx].isDone.toggle() + // The current checkbox state now belongs to today. + UserDefaults.kisani.set(iso.string(from: Date()), forKey: kLastActiveDay) if sessionStartDate == nil, activeSections[si].exercises[ei].sets[setIdx].isDone { sessionStartDate = Date() }