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:
259
Shared/FeelsTips.swift
Normal file
259
Shared/FeelsTips.swift
Normal file
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user