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

@@ -8,6 +8,7 @@
import SwiftUI
import SwiftData
import Charts
import TipKit
struct DayViewConstants {
static let maxHeaderHeight = 200.0
@@ -30,7 +31,6 @@ struct DayView: View {
// MARK: edit row properties
@State private var showingSheet = false
@State private var selectedEntry: MoodEntryModel?
@State private var showEntryDetail = false
//
// MARK: ?? properties
@@ -53,25 +53,16 @@ struct DayView: View {
.sheet(isPresented: $showingSheet) {
SettingsView()
}
.onChange(of: selectedEntry) { _, newEntry in
if newEntry != nil {
showEntryDetail = true
}
}
.sheet(isPresented: $showEntryDetail, onDismiss: {
selectedEntry = nil
}) {
if let entry = selectedEntry {
EntryDetailView(
entry: entry,
onMoodUpdate: { newMood in
viewModel.update(entry: entry, toMood: newMood)
},
onDelete: {
viewModel.update(entry: entry, toMood: .missing)
}
)
}
.sheet(item: $selectedEntry) { entry in
EntryDetailView(
entry: entry,
onMoodUpdate: { newMood in
viewModel.update(entry: entry, toMood: newMood)
},
onDelete: {
viewModel.update(entry: entry, toMood: .missing)
}
)
}
}
.padding([.top])
@@ -107,6 +98,7 @@ struct DayView: View {
AddMoodHeaderView(addItemHeaderClosure: { (mood, date) in
viewModel.add(mood: mood, forDate: date, entryType: .header)
})
.widgetVotingTip()
}
}
}

View File

@@ -7,6 +7,7 @@
import SwiftUI
import SwiftData
import WidgetKit
@MainActor
class DayViewViewModel: ObservableObject {
@@ -60,12 +61,28 @@ class DayViewViewModel: ObservableObject {
}
public func add(mood: Mood, forDate date: Date, entryType: EntryType) {
DataController.shared.add(mood: mood, forDate: date, entryType: entryType)
MoodLogger.shared.logMood(mood, for: date, entryType: entryType)
}
public func update(entry: MoodEntryModel, toMood mood: Mood) {
if !DataController.shared.update(entryDate: entry.forDate, withMood: mood) {
print("Failed to update mood entry")
return
}
// Sync to HealthKit for past day updates
guard mood != .missing && mood != .placeholder else { return }
let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue)
if healthKitEnabled {
Task {
try? await HealthKitManager.shared.saveMood(mood, for: entry.forDate)
}
}
// Reload widgets asynchronously to avoid UI delay
Task { @MainActor in
WidgetCenter.shared.reloadAllTimelines()
}
}