- 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>
260 lines
5.3 KiB
Swift
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))
|
|
}
|
|
}
|