Add Apple platform features and UX improvements

- Add HealthKit State of Mind sync for mood entries
- Add Live Activity with streak display and rating time window
- Add App Shortcuts/Siri integration for voice mood logging
- Add TipKit hints for feature discovery
- Add centralized MoodLogger for consistent side effects
- Add reminder time setting in Settings with time picker
- Fix duplicate notifications when changing reminder time
- Fix Live Activity streak showing 0 when not yet rated today
- Fix slow tap response in entry detail mood selection
- Update widget timeline to refresh at rating time
- Sync widgets when reminder time changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-19 17:21:55 -06:00
parent e123df1790
commit 440b04159e
27 changed files with 1577 additions and 81 deletions

View File

@@ -18,6 +18,9 @@ enum EntryType: Int, Codable {
case filledInMissing = 4
case notification = 5
case header = 6
case siri = 7
case controlCenter = 8
case liveActivity = 9
}
// MARK: - SwiftData Model

View File

@@ -6,15 +6,27 @@
//
import Foundation
import WidgetKit
final class OnboardingDataDataManager: ObservableObject {
static let shared = OnboardingDataDataManager()
@Published public private(set) var savedOnboardingData = UserDefaultsStore.getOnboarding()
public func updateOnboardingData(onboardingData: OnboardingData) {
let onboardingData = UserDefaultsStore.saveOnboarding(onboardingData: onboardingData)
savedOnboardingData = onboardingData
LocalNotification.scheduleReminder(atTime: onboardingData.date)
// Update Live Activity schedule when rating time changes
Task { @MainActor in
LiveActivityScheduler.shared.onRatingTimeUpdated()
}
// Force sync UserDefaults to app group before reloading widgets
GroupUserDefaults.groupDefaults.synchronize()
// Reload widgets so they show the correct view for new time
WidgetCenter.shared.reloadAllTimelines()
}
}

View File

@@ -17,11 +17,11 @@ protocol PersonalityPackable {
enum PersonalityPack: Int, CaseIterable {
case Default = 0
case Rude = 1
case MotivationalCoach = 2
case ZenMaster = 3
case BestFriend = 4
case DataAnalyst = 5
// case Rude = 1
case MotivationalCoach = 1
case ZenMaster = 2
case BestFriend = 3
case DataAnalyst = 4
func randomPushNotificationStrings() -> (title: String, body: String) {
let onboarding = UserDefaultsStore.getOnboarding()
@@ -33,12 +33,12 @@ enum PersonalityPack: Int, CaseIterable {
case (.Default, .Previous):
return (DefaultTitles.notificationTitles.randomElement()!,
DefaultTitles.notificationBodyYesterday.randomElement()!)
case (.Rude, .Today):
return (RudeTitles.notificationTitles.randomElement()!,
RudeTitles.notificationBodyToday.randomElement()!)
case (.Rude, .Previous):
return (RudeTitles.notificationTitles.randomElement()!,
RudeTitles.notificationBodyYesterday.randomElement()!)
// case (.Rude, .Today):
// return (RudeTitles.notificationTitles.randomElement()!,
// RudeTitles.notificationBodyToday.randomElement()!)
// case (.Rude, .Previous):
// return (RudeTitles.notificationTitles.randomElement()!,
// RudeTitles.notificationBodyYesterday.randomElement()!)
case (.MotivationalCoach, .Today):
return (MotivationalCoachTitles.notificationTitles.randomElement()!,
MotivationalCoachTitles.notificationBodyToday.randomElement()!)
@@ -70,8 +70,8 @@ enum PersonalityPack: Int, CaseIterable {
switch self {
case .Default:
return DefaultTitles.title
case .Rude:
return RudeTitles.title
// case .Rude:
// return RudeTitles.title
case .MotivationalCoach:
return MotivationalCoachTitles.title
case .ZenMaster:
@@ -86,7 +86,7 @@ enum PersonalityPack: Int, CaseIterable {
var icon: String {
switch self {
case .Default: return "face.smiling"
case .Rude: return "flame"
// case .Rude: return "flame"
case .MotivationalCoach: return "figure.run"
case .ZenMaster: return "leaf"
case .BestFriend: return "heart"
@@ -97,7 +97,7 @@ enum PersonalityPack: Int, CaseIterable {
var description: String {
switch self {
case .Default: return "Friendly and supportive"
case .Rude: return "Snarky with attitude"
// case .Rude: return "Snarky with attitude"
case .MotivationalCoach: return "High energy pump-up vibes"
case .ZenMaster: return "Calm and mindful"
case .BestFriend: return "Casual and supportive"

View File

@@ -96,6 +96,7 @@ class UserDefaultsStore {
case dayViewStyle
case privacyLockEnabled
case healthKitEnabled
case healthKitSyncEnabled
case contentViewCurrentSelectedHeaderViewBackDays
case contentViewHeaderTag