Fix 8 audit items: remove force-unwraps, improve accessibility and concurrency

- Replace force-unwrap HK types with modern HKQuantityType(_:) initializer
- Replace Calendar.date force-unwraps with guard/let in HealthService, HeaderPercView, MoodStreakActivity, DayViewViewModel, MonthTotalTemplate
- Extract DayViewViewModel.countEntries into testable static method with safe flatMap
- Replace DispatchQueue.main.asyncAfter with Task.sleep in CelebrationAnimations
- Add .minimumScaleFactor(0.5) to SmallRollUpHeaderView for Dynamic Type
- Add VoiceOver accessibility labels to HeaderPercView mood percentages
- Fix @testable import iFeel → Feels in Tests_iOS.swift
- Add 4 unit tests for countEntries (TDD)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-17 11:42:16 -06:00
parent 277e277750
commit 7f27446b94
11 changed files with 168 additions and 76 deletions

View File

@@ -31,21 +31,21 @@ class HealthService: ObservableObject {
// MARK: - Data Types
// Core activity metrics
private let stepCountType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
private let exerciseTimeType = HKQuantityType.quantityType(forIdentifier: .appleExerciseTime)!
private let activeEnergyType = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!
private let distanceType = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!
private let stepCountType = HKQuantityType(.stepCount)
private let exerciseTimeType = HKQuantityType(.appleExerciseTime)
private let activeEnergyType = HKQuantityType(.activeEnergyBurned)
private let distanceType = HKQuantityType(.distanceWalkingRunning)
// Heart & stress indicators
private let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
private let restingHeartRateType = HKQuantityType.quantityType(forIdentifier: .restingHeartRate)!
private let hrvType = HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!
private let heartRateType = HKQuantityType(.heartRate)
private let restingHeartRateType = HKQuantityType(.restingHeartRate)
private let hrvType = HKQuantityType(.heartRateVariabilitySDNN)
// Sleep & recovery
private let sleepAnalysisType = HKCategoryType.categoryType(forIdentifier: .sleepAnalysis)!
private let sleepAnalysisType = HKCategoryType(.sleepAnalysis)
// Mindfulness
private let mindfulSessionType = HKCategoryType.categoryType(forIdentifier: .mindfulSession)!
private let mindfulSessionType = HKCategoryType(.mindfulSession)
// State of Mind
private let stateOfMindType = HKSampleType.stateOfMindType()
@@ -117,7 +117,9 @@ class HealthService: ObservableObject {
func fetchHealthData(for date: Date) async -> DailyHealthData {
let calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: date)
let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
guard let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay) else {
return DailyHealthData(date: date, steps: nil, exerciseMinutes: nil, activeCalories: nil, distanceKm: nil, averageHeartRate: nil, restingHeartRate: nil, hrv: nil, sleepHours: nil, mindfulMinutes: nil)
}
// Fetch all metrics concurrently
async let steps = fetchSteps(start: startOfDay, end: endOfDay)
@@ -233,8 +235,10 @@ class HealthService: ObservableObject {
// Sleep data is typically recorded for the night before
// So for mood on date X, we look at sleep from evening of X-1 to morning of X
let calendar = Calendar.current
let endOfSleep = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: date)!
let startOfSleep = calendar.date(byAdding: .hour, value: -18, to: endOfSleep)!
guard let endOfSleep = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: date),
let startOfSleep = calendar.date(byAdding: .hour, value: -18, to: endOfSleep) else {
return nil
}
let predicate = HKQuery.predicateForSamples(withStart: startOfSleep, end: endOfSleep, options: .strictStartDate)