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

@@ -7,6 +7,7 @@
import SwiftUI
import StoreKit
import TipKit
// MARK: - Customize Content View (for use in SettingsTabView)
struct CustomizeContentView: View {
@@ -17,6 +18,10 @@ struct CustomizeContentView: View {
var body: some View {
ScrollView {
VStack(spacing: 24) {
// Customize tip
TipView(CustomizeLayoutTip())
.tipBackground(Color(.secondarySystemBackground))
// APPEARANCE
SettingsSection(title: "Appearance") {
VStack(spacing: 16) {
@@ -640,16 +645,16 @@ struct PersonalityPackPickerCompact: View {
VStack(spacing: 8) {
ForEach(PersonalityPack.allCases, id: \.self) { aPack in
Button(action: {
if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW {
showOver18Alert = true
EventLogger.log(event: "show_over_18_alert")
} else {
// if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW {
// showOver18Alert = true
// EventLogger.log(event: "show_over_18_alert")
// } else {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
personalityPack = aPack
EventLogger.log(event: "change_personality_pack", withData: ["pack_title": aPack.title()])
LocalNotification.rescheduleNotifiations()
}
// }
}) {
HStack {
VStack(alignment: .leading, spacing: 4) {
@@ -681,7 +686,7 @@ struct PersonalityPackPickerCompact: View {
)
}
.buttonStyle(.plain)
.blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 4 : 0)
// .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 4 : 0)
}
}
.alert(isPresented: $showOver18Alert) {

View File

@@ -40,18 +40,18 @@ struct PersonalityPackPickerView: View {
.padding(5)
)
.onTapGesture {
if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW {
showOver18Alert = true
EventLogger.log(event: "show_over_18_alert")
} else {
// if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW {
// showOver18Alert = true
// EventLogger.log(event: "show_over_18_alert")
// } else {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
personalityPack = aPack
EventLogger.log(event: "change_personality_pack", withData: ["pack_title": aPack.title()])
LocalNotification.rescheduleNotifiations()
}
// }
}
.blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0)
// .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0)
.alert(isPresented: $showOver18Alert) {
let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) {
showNSFW = true