fix: reset workout log each new day + new app logo

KC-1: Set completion was stored permanently on the program, so the same
program scheduled on back-to-back days (e.g. Shoulders Mon + Tue) kept
yesterday's checkmarks. Add resetSetsForNewDayIfNeeded() to clear every
set's isDone when the calendar day changes (cold launch, onAppear, and
scenePhase active). Streak history is untouched.

KC-2: Replace all 12 AppIcon sizes with the new KisaniCal mark, alpha
flattened onto RGB(26,29,34) so the 1024 App Store icon is fully opaque.

Add KisaniCal/ISSUES.md tracker (KC-1, KC-2: In Progress).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kutesir
2026-06-06 19:29:25 +03:00
parent 324ddf9b0a
commit 800c7db76d
15 changed files with 95 additions and 0 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -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)

58
KisaniCal/ISSUES.md Normal file
View File

@@ -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.

View File

@@ -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()
}