Add premium features and reorganize Settings tab
Premium Features: - Journal notes and photo attachments for mood entries - Data export (CSV and PDF reports) - Privacy lock with Face ID/Touch ID - Apple Health integration for mood correlation - 4 new personality packs (Motivational Coach, Zen Master, Best Friend, Data Analyst) Settings Tab Reorganization: - Combined Customize and Settings into single tab with segmented control - Added upgrade banner with trial countdown above segment - "Why Upgrade?" sheet showing all premium benefits - Subscribe button opens improved StoreKit 2 subscription view UI Improvements: - Enhanced subscription store with feature highlights - Entry detail view for viewing/editing notes and photos - Removed duplicate subscription banners from tab content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,10 @@ final class MoodEntryModel {
|
||||
var canEdit: Bool
|
||||
var canDelete: Bool
|
||||
|
||||
// Journal & Media (NEW)
|
||||
var notes: String?
|
||||
var photoID: UUID?
|
||||
|
||||
// Computed properties
|
||||
var mood: Mood {
|
||||
Mood(rawValue: moodValue) ?? .missing
|
||||
@@ -49,7 +53,9 @@ final class MoodEntryModel {
|
||||
mood: Mood,
|
||||
entryType: EntryType,
|
||||
canEdit: Bool = true,
|
||||
canDelete: Bool = true
|
||||
canDelete: Bool = true,
|
||||
notes: String? = nil,
|
||||
photoID: UUID? = nil
|
||||
) {
|
||||
self.forDate = forDate
|
||||
self.moodValue = mood.rawValue
|
||||
@@ -58,6 +64,8 @@ final class MoodEntryModel {
|
||||
self.entryType = entryType.rawValue
|
||||
self.canEdit = canEdit
|
||||
self.canDelete = canDelete
|
||||
self.notes = notes
|
||||
self.photoID = photoID
|
||||
}
|
||||
|
||||
// Convenience initializer for raw values
|
||||
@@ -68,7 +76,9 @@ final class MoodEntryModel {
|
||||
timestamp: Date = Date(),
|
||||
weekDay: Int? = nil,
|
||||
canEdit: Bool = true,
|
||||
canDelete: Bool = true
|
||||
canDelete: Bool = true,
|
||||
notes: String? = nil,
|
||||
photoID: UUID? = nil
|
||||
) {
|
||||
self.forDate = forDate
|
||||
self.moodValue = moodValue
|
||||
@@ -77,5 +87,7 @@ final class MoodEntryModel {
|
||||
self.entryType = entryType
|
||||
self.canEdit = canEdit
|
||||
self.canDelete = canDelete
|
||||
self.notes = notes
|
||||
self.photoID = photoID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,16 @@ protocol PersonalityPackable {
|
||||
}
|
||||
|
||||
enum PersonalityPack: Int, CaseIterable {
|
||||
case Default
|
||||
case Rude
|
||||
|
||||
case Default = 0
|
||||
case Rude = 1
|
||||
case MotivationalCoach = 2
|
||||
case ZenMaster = 3
|
||||
case BestFriend = 4
|
||||
case DataAnalyst = 5
|
||||
|
||||
func randomPushNotificationStrings() -> (title: String, body: String) {
|
||||
let onboarding = UserDefaultsStore.getOnboarding()
|
||||
|
||||
|
||||
switch (self, onboarding.inputDay) {
|
||||
case (.Default, .Today):
|
||||
return (DefaultTitles.notificationTitles.randomElement()!,
|
||||
@@ -35,15 +39,69 @@ enum PersonalityPack: Int, CaseIterable {
|
||||
case (.Rude, .Previous):
|
||||
return (RudeTitles.notificationTitles.randomElement()!,
|
||||
RudeTitles.notificationBodyYesterday.randomElement()!)
|
||||
case (.MotivationalCoach, .Today):
|
||||
return (MotivationalCoachTitles.notificationTitles.randomElement()!,
|
||||
MotivationalCoachTitles.notificationBodyToday.randomElement()!)
|
||||
case (.MotivationalCoach, .Previous):
|
||||
return (MotivationalCoachTitles.notificationTitles.randomElement()!,
|
||||
MotivationalCoachTitles.notificationBodyYesterday.randomElement()!)
|
||||
case (.ZenMaster, .Today):
|
||||
return (ZenMasterTitles.notificationTitles.randomElement()!,
|
||||
ZenMasterTitles.notificationBodyToday.randomElement()!)
|
||||
case (.ZenMaster, .Previous):
|
||||
return (ZenMasterTitles.notificationTitles.randomElement()!,
|
||||
ZenMasterTitles.notificationBodyYesterday.randomElement()!)
|
||||
case (.BestFriend, .Today):
|
||||
return (BestFriendTitles.notificationTitles.randomElement()!,
|
||||
BestFriendTitles.notificationBodyToday.randomElement()!)
|
||||
case (.BestFriend, .Previous):
|
||||
return (BestFriendTitles.notificationTitles.randomElement()!,
|
||||
BestFriendTitles.notificationBodyYesterday.randomElement()!)
|
||||
case (.DataAnalyst, .Today):
|
||||
return (DataAnalystTitles.notificationTitles.randomElement()!,
|
||||
DataAnalystTitles.notificationBodyToday.randomElement()!)
|
||||
case (.DataAnalyst, .Previous):
|
||||
return (DataAnalystTitles.notificationTitles.randomElement()!,
|
||||
DataAnalystTitles.notificationBodyYesterday.randomElement()!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func title() -> String {
|
||||
switch self {
|
||||
case .Default:
|
||||
return DefaultTitles.title
|
||||
case .Rude:
|
||||
return RudeTitles.title
|
||||
case .MotivationalCoach:
|
||||
return MotivationalCoachTitles.title
|
||||
case .ZenMaster:
|
||||
return ZenMasterTitles.title
|
||||
case .BestFriend:
|
||||
return BestFriendTitles.title
|
||||
case .DataAnalyst:
|
||||
return DataAnalystTitles.title
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .Default: return "face.smiling"
|
||||
case .Rude: return "flame"
|
||||
case .MotivationalCoach: return "figure.run"
|
||||
case .ZenMaster: return "leaf"
|
||||
case .BestFriend: return "heart"
|
||||
case .DataAnalyst: return "chart.bar"
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .Default: return "Friendly and supportive"
|
||||
case .Rude: return "Snarky with attitude"
|
||||
case .MotivationalCoach: return "High energy pump-up vibes"
|
||||
case .ZenMaster: return "Calm and mindful"
|
||||
case .BestFriend: return "Casual and supportive"
|
||||
case .DataAnalyst: return "Stats-focused and objective"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +138,7 @@ final class DefaultTitles: PersonalityPackable {
|
||||
|
||||
final class RudeTitles: PersonalityPackable {
|
||||
static var title = String(localized: "rude")
|
||||
|
||||
|
||||
static var notificationTitles: [String] {
|
||||
[
|
||||
String(localized: "rude_notif_title_one"),
|
||||
@@ -89,7 +147,7 @@ final class RudeTitles: PersonalityPackable {
|
||||
String(localized: "rude_notif_title_four")
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
static var notificationBodyToday: [String] {
|
||||
[
|
||||
String(localized: "rude_notif_body_today_one"),
|
||||
@@ -97,7 +155,7 @@ final class RudeTitles: PersonalityPackable {
|
||||
String(localized: "rude_notif_body_today_three")
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
static var notificationBodyYesterday: [String] {
|
||||
[
|
||||
String(localized: "rude_notif_body_yesterday_one"),
|
||||
@@ -106,3 +164,135 @@ final class RudeTitles: PersonalityPackable {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Motivational Coach
|
||||
|
||||
final class MotivationalCoachTitles: PersonalityPackable {
|
||||
static var title = "Coach"
|
||||
|
||||
static var notificationTitles: [String] {
|
||||
[
|
||||
"LET'S GO!",
|
||||
"Champion Check-In",
|
||||
"Time to Shine!",
|
||||
"You've Got This!"
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyToday: [String] {
|
||||
[
|
||||
"Every day is a chance to be AMAZING! How are you feeling today?",
|
||||
"Winners track their progress! Log your mood and keep crushing it!",
|
||||
"Your mental game matters! Take 10 seconds to check in with yourself.",
|
||||
"Champions know their emotions! How's your energy right now?"
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyYesterday: [String] {
|
||||
[
|
||||
"Yesterday's reflection builds tomorrow's success! How did you feel?",
|
||||
"Great athletes review their game tape! Log yesterday's mood!",
|
||||
"No day is wasted when you learn from it! How was yesterday?",
|
||||
"Every experience counts! Tell me about yesterday!"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Zen Master
|
||||
|
||||
final class ZenMasterTitles: PersonalityPackable {
|
||||
static var title = "Zen"
|
||||
|
||||
static var notificationTitles: [String] {
|
||||
[
|
||||
"A Gentle Reminder",
|
||||
"Mindful Moment",
|
||||
"Inner Peace",
|
||||
"Present Awareness"
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyToday: [String] {
|
||||
[
|
||||
"Breathe. Notice. How does this moment find you?",
|
||||
"The river of feelings flows through us all. What flows through you now?",
|
||||
"Like clouds passing, emotions come and go. What passes through you today?",
|
||||
"In stillness, we find clarity. Take a moment to notice your inner weather."
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyYesterday: [String] {
|
||||
[
|
||||
"Yesterday has passed like a leaf on the stream. How did it feel?",
|
||||
"Reflecting on the past with compassion... How was yesterday's journey?",
|
||||
"Each day is a teacher. What did yesterday's emotions teach you?",
|
||||
"With gentle awareness, recall yesterday. What arose within you?"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Best Friend
|
||||
|
||||
final class BestFriendTitles: PersonalityPackable {
|
||||
static var title = "Bestie"
|
||||
|
||||
static var notificationTitles: [String] {
|
||||
[
|
||||
"Hey you!",
|
||||
"Quick check-in!",
|
||||
"Thinking of you!",
|
||||
"Got a sec?"
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyToday: [String] {
|
||||
[
|
||||
"Just checking in on my favorite person! How's it going today?",
|
||||
"Hey! Tell me everything - how are you feeling right now?",
|
||||
"You know I always want to hear about your day! What's the vibe?",
|
||||
"Sending good vibes your way! How are you doing today?"
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyYesterday: [String] {
|
||||
[
|
||||
"Wait, we didn't catch up yesterday! How did it go?",
|
||||
"I realized I didn't hear about yesterday - fill me in!",
|
||||
"Oops, missed you yesterday! How was your day?",
|
||||
"Tell me about yesterday! I want to hear all about it!"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Data Analyst
|
||||
|
||||
final class DataAnalystTitles: PersonalityPackable {
|
||||
static var title = "Analyst"
|
||||
|
||||
static var notificationTitles: [String] {
|
||||
[
|
||||
"Data Point Required",
|
||||
"Daily Metric Input",
|
||||
"Mood Tracking Alert",
|
||||
"Status Update Needed"
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyToday: [String] {
|
||||
[
|
||||
"Today's emotional data point is pending. Please log your current mood state.",
|
||||
"Incomplete dataset detected. Input today's mood to maintain tracking accuracy.",
|
||||
"Daily mood metric collection: What is your current emotional status (1-5)?",
|
||||
"Recording today's baseline. Please submit your mood data for analysis."
|
||||
]
|
||||
}
|
||||
|
||||
static var notificationBodyYesterday: [String] {
|
||||
[
|
||||
"Gap in historical data: Yesterday's mood entry is missing. Please backfill.",
|
||||
"Data integrity alert: Yesterday's emotional metric was not captured.",
|
||||
"Historical data request: Submit yesterday's mood for trend analysis.",
|
||||
"Missing data point from previous day. Please log for complete dataset."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ class UserDefaultsStore {
|
||||
case lastVotedDate
|
||||
case votingLayoutStyle
|
||||
case dayViewStyle
|
||||
case privacyLockEnabled
|
||||
case healthKitEnabled
|
||||
|
||||
case contentViewCurrentSelectedHeaderViewBackDays
|
||||
case contentViewHeaderTag
|
||||
|
||||
Reference in New Issue
Block a user