Files
Reflect/Shared/FeelsTips.swift
Trey t 440b04159e 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>
2025-12-19 17:21:55 -06:00

260 lines
5.3 KiB
Swift

//
// FeelsTips.swift
// Feels
//
// TipKit implementation for feature discovery and onboarding
//
import TipKit
import SwiftUI
// MARK: - Tip Definitions
/// Tip for customizing mood layouts
struct CustomizeLayoutTip: Tip {
var title: Text {
Text("Personalize Your Experience")
}
var message: Text? {
Text("Tap here to customize mood icons, colors, and layouts.")
}
var image: Image? {
Image(systemName: "paintbrush")
}
}
/// Tip for AI Insights feature
struct AIInsightsTip: Tip {
var title: Text {
Text("Discover AI Insights")
}
var message: Text? {
Text("Get personalized insights about your mood patterns powered by Apple Intelligence.")
}
var image: Image? {
Image(systemName: "brain")
}
var rules: [Rule] {
#Rule(Self.$hasLoggedMoods) { $0 >= 7 }
}
@Parameter
static var hasLoggedMoods: Int = 0
}
/// Tip for Siri shortcuts
struct SiriShortcutTip: Tip {
var title: Text {
Text("Use Siri to Log Moods")
}
var message: Text? {
Text("Say \"Hey Siri, log my mood as great in Feels\" for hands-free logging.")
}
var image: Image? {
Image(systemName: "mic.fill")
}
var rules: [Rule] {
#Rule(Self.$moodLogCount) { $0 >= 3 }
}
@Parameter
static var moodLogCount: Int = 0
}
/// Tip for HealthKit sync
struct HealthKitSyncTip: Tip {
var title: Text {
Text("Sync with Apple Health")
}
var message: Text? {
Text("Connect to Apple Health to see your mood data alongside sleep, exercise, and more.")
}
var image: Image? {
Image(systemName: "heart.fill")
}
var rules: [Rule] {
#Rule(Self.$hasSeenSettings) { $0 == true }
}
@Parameter
static var hasSeenSettings: Bool = false
}
/// Tip for widget voting
struct WidgetVotingTip: Tip {
var title: Text {
Text("Vote from Your Home Screen")
}
var message: Text? {
Text("Add the Mood Vote widget to quickly log your mood without opening the app.")
}
var image: Image? {
Image(systemName: "square.grid.2x2")
}
var rules: [Rule] {
#Rule(Self.$daysUsingApp) { $0 >= 2 }
}
@Parameter
static var daysUsingApp: Int = 0
}
/// Tip for viewing different time periods
struct TimeViewTip: Tip {
var title: Text {
Text("View Your History")
}
var message: Text? {
Text("Switch between Day, Month, and Year views to see your mood patterns over time.")
}
var image: Image? {
Image(systemName: "calendar")
}
}
/// Tip for mood streaks
struct MoodStreakTip: Tip {
var title: Text {
Text("Build Your Streak!")
}
var message: Text? {
Text("Log your mood daily to build a streak. Consistency helps you understand your patterns.")
}
var image: Image? {
Image(systemName: "flame.fill")
}
var rules: [Rule] {
#Rule(Self.$currentStreak) { $0 >= 3 }
}
@Parameter
static var currentStreak: Int = 0
}
/// Tip for Control Center widget
struct ControlCenterTip: Tip {
var title: Text {
Text("Quick Access from Control Center")
}
var message: Text? {
Text("Add Feels to Control Center for one-tap mood logging from anywhere.")
}
var image: Image? {
Image(systemName: "slider.horizontal.3")
}
var rules: [Rule] {
#Rule(Self.$daysUsingApp) { $0 >= 5 }
}
@Parameter
static var daysUsingApp: Int = 0
}
// MARK: - Tips Manager
@MainActor
class TipsManager {
static let shared = TipsManager()
private init() {}
func configure() {
try? Tips.configure([
.displayFrequency(.daily),
.datastoreLocation(.applicationDefault)
])
}
func resetAllTips() {
try? Tips.resetDatastore()
}
// Update tip parameters based on user actions
func onMoodLogged() {
SiriShortcutTip.moodLogCount += 1
AIInsightsTip.hasLoggedMoods += 1
}
func onSettingsViewed() {
HealthKitSyncTip.hasSeenSettings = true
}
func updateDaysUsingApp(_ days: Int) {
WidgetVotingTip.daysUsingApp = days
ControlCenterTip.daysUsingApp = days
}
func updateStreak(_ streak: Int) {
MoodStreakTip.currentStreak = streak
}
}
// MARK: - Tip View Modifiers
extension View {
func customizeLayoutTip() -> some View {
self.popoverTip(CustomizeLayoutTip())
}
func aiInsightsTip() -> some View {
self.popoverTip(AIInsightsTip())
}
func siriShortcutTip() -> some View {
self.popoverTip(SiriShortcutTip())
}
func healthKitSyncTip() -> some View {
self.popoverTip(HealthKitSyncTip())
}
func widgetVotingTip() -> some View {
self.popoverTip(WidgetVotingTip())
}
func timeViewTip() -> some View {
self.popoverTip(TimeViewTip())
}
func moodStreakTip() -> some View {
self.popoverTip(MoodStreakTip())
}
func controlCenterTip() -> some View {
self.popoverTip(ControlCenterTip())
}
}
// MARK: - Inline Tip View
struct InlineTipView: View {
let tip: any Tip
var body: some View {
TipView(tip)
.tipBackground(Color(.secondarySystemBackground))
}
}