diff --git a/Feels/Localizable.xcstrings b/Feels/Localizable.xcstrings index e4e5c40..412d9fb 100644 --- a/Feels/Localizable.xcstrings +++ b/Feels/Localizable.xcstrings @@ -6241,6 +6241,10 @@ } } }, + "Export Widget Screenshots" : { + "comment" : "A button label that prompts the user to download their light and dark mode widget screenshots.", + "isCommentAutoGenerated" : true + }, "Export your mood data as CSV or PDF" : { "comment" : "A hint that describes the functionality of the \"Export Data\" button in the Settings view.", "isCommentAutoGenerated" : true, @@ -8016,6 +8020,10 @@ } } }, + "Light & dark mode PNGs" : { + "comment" : "A description of what the \"Export Widget Screenshots\" button does.", + "isCommentAutoGenerated" : true + }, "Like ink on paper, each mood\nleaves its mark. Premium reveals the pattern." : { "comment" : "A description of how the premium version reveals patterns in user moods.", "isCommentAutoGenerated" : true, @@ -13742,6 +13750,10 @@ } } }, + "Saved to Documents/WidgetExports" : { + "comment" : "A description of where the exported widget screenshots are saved.", + "isCommentAutoGenerated" : true + }, "Say \"Hey Siri, log my mood as great in Feels\" for hands-free logging." : { "comment" : "A tip message for using Siri to log moods.", "extractionState" : "stale", @@ -14033,6 +14045,10 @@ } } }, + "Send 5 personality pack notifications" : { + "comment" : "A description of the action that can be performed when tapping the \"Test All Notifications\" button in the Settings app.", + "isCommentAutoGenerated" : true + }, "Set Trial Start Date" : { "comment" : "The title of a screen that lets a user set the start date of a free trial.", "isCommentAutoGenerated" : true, @@ -16177,6 +16193,10 @@ } } }, + "Test All Notifications" : { + "comment" : "A button label that tests sending notifications.", + "isCommentAutoGenerated" : true + }, "Test builds only" : { "comment" : "A section header that indicates that the settings view contains only test data.", "isCommentAutoGenerated" : true, diff --git a/Shared/LocalNotification.swift b/Shared/LocalNotification.swift index 6df211d..58dd76d 100644 --- a/Shared/LocalNotification.swift +++ b/Shared/LocalNotification.swift @@ -128,4 +128,46 @@ class LocalNotification { public class func removeNotificaiton() { UNUserNotificationCenter.current().removeAllPendingNotificationRequests() } + + // MARK: - Debug: Send All Personality Pack Notifications + + #if DEBUG + /// Sends one notification from each personality pack, staggered over 10 seconds for screenshot + public class func sendAllPersonalityNotificationsForScreenshot() { + let _ = createNotificationCategory() + + let packs: [(PersonalityPack, Double)] = [ + (.Default, 5), + (.MotivationalCoach, 6), + (.ZenMaster, 7), + (.BestFriend, 8), + (.DataAnalyst, 9) + ] + + for (pack, delay) in packs { + let content = UNMutableNotificationContent() + let strings = pack.randomPushNotificationStrings() + content.title = strings.title + content.body = strings.body + content.sound = .default + content.categoryIdentifier = LocalNotification.categoryName + content.interruptionLevel = .timeSensitive + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay, repeats: false) + let request = UNNotificationRequest( + identifier: "debug-\(pack.rawValue)-\(UUID().uuidString)", + content: content, + trigger: trigger + ) + + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("Failed to schedule \(pack) notification: \(error)") + } else { + print("Scheduled \(pack) notification in \(delay)s") + } + } + } + } + #endif } diff --git a/Shared/Services/WidgetExporter.swift b/Shared/Services/WidgetExporter.swift new file mode 100644 index 0000000..c029306 --- /dev/null +++ b/Shared/Services/WidgetExporter.swift @@ -0,0 +1,779 @@ +// +// WidgetExporter.swift +// Feels +// +// Debug utility to export all widget previews to PNG files +// + +#if DEBUG +import SwiftUI +import UIKit + +/// Exports widget previews to PNG files for App Store screenshots +@MainActor +class WidgetExporter { + + // MARK: - Widget Sizes (iPhone 15 Pro Max @ 3x) + + enum WidgetSize { + case small // 170x170 pt = 510x510 px + case medium // 364x170 pt = 1092x510 px + case large // 364x382 pt = 1092x1146 px + + var pointSize: CGSize { + switch self { + case .small: return CGSize(width: 170, height: 170) + case .medium: return CGSize(width: 364, height: 170) + case .large: return CGSize(width: 382, height: 382) + } + } + + var name: String { + switch self { + case .small: return "small" + case .medium: return "medium" + case .large: return "large" + } + } + } + + // MARK: - Export All Widgets + + static func exportAllWidgets() async -> URL? { + // Create export directory + let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + let exportPath = documentsPath.appendingPathComponent("WidgetExports", isDirectory: true) + + try? FileManager.default.removeItem(at: exportPath) + try? FileManager.default.createDirectory(at: exportPath, withIntermediateDirectories: true) + + // Export each widget type in both color schemes + for colorScheme in [ColorScheme.light, ColorScheme.dark] { + let schemeName = colorScheme == .light ? "light" : "dark" + + // Vote Widget - Not Voted + await exportVoteWidget(hasVoted: false, mood: nil, size: .small, colorScheme: colorScheme, to: exportPath, name: "vote_\(schemeName)_small_notvoted") + await exportVoteWidget(hasVoted: false, mood: nil, size: .medium, colorScheme: colorScheme, to: exportPath, name: "vote_\(schemeName)_medium_notvoted") + + // Vote Widget - Voted (all moods) + for mood in Mood.allValues { + await exportVoteWidget(hasVoted: true, mood: mood, size: .small, colorScheme: colorScheme, to: exportPath, name: "vote_\(schemeName)_small_\(mood.strValue.lowercased())") + await exportVoteWidget(hasVoted: true, mood: mood, size: .medium, colorScheme: colorScheme, to: exportPath, name: "vote_\(schemeName)_medium_\(mood.strValue.lowercased())") + } + + // Timeline Widget - Logged + await exportTimelineWidget(hasVoted: true, size: .small, colorScheme: colorScheme, to: exportPath, name: "timeline_\(schemeName)_small_logged") + await exportTimelineWidget(hasVoted: true, size: .medium, colorScheme: colorScheme, to: exportPath, name: "timeline_\(schemeName)_medium_logged") + await exportTimelineWidget(hasVoted: true, size: .large, colorScheme: colorScheme, to: exportPath, name: "timeline_\(schemeName)_large_logged") + + // Timeline Widget - Voting + await exportTimelineWidget(hasVoted: false, size: .small, colorScheme: colorScheme, to: exportPath, name: "timeline_\(schemeName)_small_voting") + await exportTimelineWidget(hasVoted: false, size: .medium, colorScheme: colorScheme, to: exportPath, name: "timeline_\(schemeName)_medium_voting") + await exportTimelineWidget(hasVoted: false, size: .large, colorScheme: colorScheme, to: exportPath, name: "timeline_\(schemeName)_large_voting") + + // Live Activity - Lock Screen (213 streak, all moods + not logged) + await exportLiveActivity(hasLogged: false, mood: nil, streak: 213, colorScheme: colorScheme, to: exportPath, name: "liveactivity_\(schemeName)_notlogged") + for mood in Mood.allValues { + await exportLiveActivity(hasLogged: true, mood: mood, streak: 213, colorScheme: colorScheme, to: exportPath, name: "liveactivity_\(schemeName)_\(mood.strValue.lowercased())") + } + } + + print("๐Ÿ“ธ Widgets exported to: \(exportPath.path)") + return exportPath + } + + // MARK: - Vote Widget Export + + private static func exportVoteWidget(hasVoted: Bool, mood: Mood?, size: WidgetSize, colorScheme: ColorScheme, to folder: URL, name: String) async { + let content: AnyView + if hasVoted, let mood = mood { + // Voted state + content = AnyView( + ExportVotedStatsView(mood: mood, totalEntries: 117, isSmall: size == .small) + ) + } else { + // Not voted state - show voting buttons + content = AnyView( + ExportVotingView(isSmall: size == .small) + ) + } + + let view = WidgetContainer(size: size, colorScheme: colorScheme, content: content) + await renderAndSave(view: view, size: size, to: folder, name: name) + } + + // MARK: - Timeline Widget Export + + private static func exportTimelineWidget(hasVoted: Bool, size: WidgetSize, colorScheme: ColorScheme, to folder: URL, name: String) async { + let timelineData = createSampleTimelineData(count: size == .large ? 10 : (size == .medium ? 5 : 1)) + + let content: AnyView + switch size { + case .small: + content = AnyView( + TimelineSmallExportView(timelineData: timelineData.first, hasVoted: hasVoted) + ) + case .medium: + content = AnyView( + TimelineMediumExportView(timelineData: Array(timelineData.prefix(5)), hasVoted: hasVoted) + ) + case .large: + content = AnyView( + TimelineLargeExportView(timelineData: timelineData, hasVoted: hasVoted) + ) + } + + let view = WidgetContainer(size: size, colorScheme: colorScheme, content: content, useSystemBackground: hasVoted) + await renderAndSave(view: view, size: size, to: folder, name: name) + } + + // MARK: - Live Activity Export + + /// Live Activity lock screen size (iPhone 15 Pro Max) + static let liveActivitySize = CGSize(width: 370, height: 100) + + private static func exportLiveActivity(hasLogged: Bool, mood: Mood?, streak: Int, colorScheme: ColorScheme, to folder: URL, name: String) async { + let content = ExportLiveActivityView( + streak: streak, + hasLoggedToday: hasLogged, + mood: mood + ) + + let view = content + .frame(width: liveActivitySize.width, height: liveActivitySize.height) + .background( + colorScheme == .dark + ? Color(UIColor.systemBackground).opacity(0.8) + : Color(UIColor.secondarySystemBackground) + ) + .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) + .environment(\.colorScheme, colorScheme) + + await renderAndSaveLiveActivity(view: view, to: folder, name: name) + } + + private static func renderAndSaveLiveActivity(view: V, to folder: URL, name: String) async { + let renderer = ImageRenderer(content: view.frame(width: liveActivitySize.width, height: liveActivitySize.height)) + renderer.scale = 3.0 + + if let image = renderer.uiImage { + let url = folder.appendingPathComponent("\(name).png") + if let data = image.pngData() { + try? data.write(to: url) + } + } + } + + // MARK: - Render and Save + + private static func renderAndSave(view: V, size: WidgetSize, to folder: URL, name: String) async { + let renderer = ImageRenderer(content: view.frame(width: size.pointSize.width, height: size.pointSize.height)) + renderer.scale = 3.0 // 3x for high res + + if let image = renderer.uiImage { + let url = folder.appendingPathComponent("\(name).png") + if let data = image.pngData() { + try? data.write(to: url) + } + } + } + + // MARK: - Sample Data + + struct TimelineDataItem: Identifiable { + let id = UUID() + let mood: Mood + let date: Date + let color: Color + let image: Image + } + + private static func createSampleTimelineData(count: Int) -> [TimelineDataItem] { + let moods: [Mood] = [.great, .good, .average, .good, .great, .average, .bad, .good, .great, .good] + let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable() + let moodImages: MoodImagable.Type = UserDefaultsStore.moodMoodImagable() + + return (0..: View { + let size: WidgetExporter.WidgetSize + let colorScheme: ColorScheme + let content: Content + var useSystemBackground: Bool = false + + var body: some View { + content + .frame(width: size.pointSize.width, height: size.pointSize.height) + .background(useSystemBackground ? Color(UIColor.systemBackground) : Color(UIColor.tertiarySystemFill)) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) + .environment(\.colorScheme, colorScheme) + } +} + +// MARK: - Export Voting View (static, no interactive intents) + +private struct ExportVotingView: View { + let isSmall: Bool + + private var moodTint: MoodTintable.Type { + UserDefaultsStore.moodTintable() + } + + private var moodImages: MoodImagable.Type { + UserDefaultsStore.moodMoodImagable() + } + + var body: some View { + if isSmall { + smallLayout + } else { + mediumLayout + } + } + + private var smallLayout: some View { + VStack(spacing: 8) { + // Top row: Great, Good, Average + HStack(spacing: 12) { + ForEach([Mood.great, .good, .average], id: \.rawValue) { mood in + moodIcon(for: mood, size: 40) + } + } + + // Bottom row: Bad, Horrible + HStack(spacing: 12) { + ForEach([Mood.bad, .horrible], id: \.rawValue) { mood in + moodIcon(for: mood, size: 40) + } + } + } + .padding(.horizontal, 8) + .padding(.vertical, 8) + } + + private var mediumLayout: some View { + VStack(spacing: 12) { + Text("How are you feeling?") + .font(.headline) + .foregroundStyle(.primary) + .multilineTextAlignment(.center) + + HStack(spacing: 0) { + ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in + moodImages.icon(forMood: mood) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 36, height: 36) + .foregroundColor(moodTint.color(forMood: mood)) + .frame(maxWidth: .infinity) + } + } + } + .padding(.horizontal, 12) + .padding(.vertical, 16) + } + + private func moodIcon(for mood: Mood, size: CGFloat) -> some View { + moodImages.icon(forMood: mood) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: size, height: size) + .foregroundColor(moodTint.color(forMood: mood)) + } +} + +// MARK: - Export Voted Stats View + +private struct ExportVotedStatsView: View { + let mood: Mood + let totalEntries: Int + let isSmall: Bool + + private var moodTint: MoodTintable.Type { + UserDefaultsStore.moodTintable() + } + + private var moodImages: MoodImagable.Type { + UserDefaultsStore.moodMoodImagable() + } + + private let moodCounts: [Mood: Int] = [.great: 45, .good: 42, .average: 18, .bad: 8, .horrible: 4] + + var body: some View { + if isSmall { + smallLayout + } else { + mediumLayout + } + } + + private var smallLayout: some View { + VStack(spacing: 8) { + ZStack(alignment: .bottomTrailing) { + moodImages.icon(forMood: mood) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 56, height: 56) + .foregroundColor(moodTint.color(forMood: mood)) + + Image(systemName: "checkmark.circle.fill") + .font(.headline) + .foregroundColor(.green) + .background(Circle().fill(.white).frame(width: 14, height: 14)) + .offset(x: 4, y: 4) + } + + Text("Today") + .font(.caption.weight(.semibold)) + .foregroundStyle(.secondary) + + Text("\(totalEntries) day streak") + .font(.caption2) + .foregroundStyle(.tertiary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(12) + } + + private var mediumLayout: some View { + HStack(alignment: .top, spacing: 20) { + VStack(spacing: 6) { + moodImages.icon(forMood: mood) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 48, height: 48) + .foregroundColor(moodTint.color(forMood: mood)) + + Text(mood.widgetDisplayName) + .font(.subheadline.weight(.semibold)) + .foregroundColor(moodTint.color(forMood: mood)) + + Text("Today") + .font(.caption2) + .foregroundStyle(.secondary) + } + + VStack(alignment: .leading, spacing: 10) { + Text("\(totalEntries) entries") + .font(.headline.weight(.semibold)) + .foregroundStyle(.primary) + + HStack(spacing: 6) { + ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { m in + let count = moodCounts[m, default: 0] + if count > 0 { + HStack(spacing: 2) { + Circle() + .fill(moodTint.color(forMood: m)) + .frame(width: 8, height: 8) + Text("\(count)") + .font(.caption2) + .foregroundStyle(.secondary) + } + } + } + } + + GeometryReader { geo in + HStack(spacing: 1) { + ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { m in + let percentage = Double(moodCounts[m, default: 0]) / Double(totalEntries) * 100 + if percentage > 0 { + RoundedRectangle(cornerRadius: 2) + .fill(moodTint.color(forMood: m)) + .frame(width: max(4, geo.size.width * CGFloat(percentage) / 100)) + } + } + } + } + .frame(height: 10) + .clipShape(RoundedRectangle(cornerRadius: 5)) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding() + } +} + +// MARK: - Timeline Export Views + +private struct TimelineSmallExportView: View { + let timelineData: WidgetExporter.TimelineDataItem? + let hasVoted: Bool + + private var dayFormatter: DateFormatter { + let f = DateFormatter() + f.dateFormat = "EEEE" + return f + } + + private var dateFormatter: DateFormatter { + let f = DateFormatter() + f.dateFormat = "MMM d" + return f + } + + var body: some View { + if !hasVoted { + ExportVotingView(isSmall: true) + } else if let today = timelineData { + VStack(spacing: 0) { + Spacer() + + today.image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 70, height: 70) + .foregroundColor(today.color) + + Spacer().frame(height: 12) + + VStack(spacing: 2) { + Text(dayFormatter.string(from: today.date)) + .font(.caption2.weight(.medium)) + .foregroundStyle(.secondary) + .textCase(.uppercase) + + Text(dateFormatter.string(from: today.date)) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.primary) + } + + Spacer() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } +} + +private struct TimelineMediumExportView: View { + let timelineData: [WidgetExporter.TimelineDataItem] + let hasVoted: Bool + + private var dayFormatter: DateFormatter { + let f = DateFormatter() + f.dateFormat = "EEE" + return f + } + + private var dateFormatter: DateFormatter { + let f = DateFormatter() + f.dateFormat = "d" + return f + } + + private var headerDateRange: String { + guard let first = timelineData.first, let last = timelineData.last else { return "" } + let formatter = DateFormatter() + formatter.dateFormat = "MMM d" + return "\(formatter.string(from: last.date)) - \(formatter.string(from: first.date))" + } + + var body: some View { + if !hasVoted { + ExportVotingView(isSmall: false) + } else { + GeometryReader { geo in + let cellHeight = geo.size.height - 36 + + VStack(spacing: 4) { + HStack { + Text("Last 5 Days") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.primary) + Text("ยท") + .foregroundStyle(.secondary) + Text(headerDateRange) + .font(.caption) + .foregroundStyle(.secondary) + Spacer() + } + .padding(.horizontal, 14) + .padding(.top, 10) + + HStack(spacing: 8) { + ForEach(Array(timelineData.enumerated()), id: \.element.id) { index, item in + ExportMediumDayCell( + dayLabel: dayFormatter.string(from: item.date), + dateLabel: dateFormatter.string(from: item.date), + image: item.image, + color: item.color, + isToday: index == 0, + height: cellHeight + ) + } + } + .padding(.horizontal, 10) + .padding(.bottom, 10) + } + } + } + } +} + +private struct ExportMediumDayCell: View { + let dayLabel: String + let dateLabel: String + let image: Image + let color: Color + let isToday: Bool + let height: CGFloat + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 14) + .fill(color.opacity(isToday ? 0.25 : 0.12)) + .frame(height: height) + + VStack(spacing: 4) { + Text(dayLabel) + .font(.caption2.weight(isToday ? .bold : .medium)) + .foregroundStyle(isToday ? .primary : .secondary) + .textCase(.uppercase) + + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 36, height: 36) + .foregroundColor(color) + + Text(dateLabel) + .font(.caption.weight(isToday ? .bold : .semibold)) + .foregroundStyle(isToday ? color : .secondary) + } + } + .frame(maxWidth: .infinity) + } +} + +private struct TimelineLargeExportView: View { + let timelineData: [WidgetExporter.TimelineDataItem] + let hasVoted: Bool + + private var dayFormatter: DateFormatter { + let f = DateFormatter() + f.dateFormat = "EEE" + return f + } + + private var dateFormatter: DateFormatter { + let f = DateFormatter() + f.dateFormat = "d" + return f + } + + private var headerDateRange: String { + guard let first = timelineData.first, let last = timelineData.last else { return "" } + let formatter = DateFormatter() + formatter.dateFormat = "MMM d" + return "\(formatter.string(from: last.date)) - \(formatter.string(from: first.date))" + } + + var body: some View { + if !hasVoted { + ExportLargeVotingView() + } else { + GeometryReader { geo in + let cellHeight = (geo.size.height - 70) / 2 + + VStack(spacing: 6) { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text("Last 10 Days") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.primary) + Text(headerDateRange) + .font(.caption2) + .foregroundStyle(.secondary) + } + Spacer() + } + .padding(.horizontal, 12) + .padding(.top, 8) + + VStack(spacing: 6) { + HStack(spacing: 6) { + ForEach(Array(timelineData.prefix(5).enumerated()), id: \.element.id) { index, item in + ExportDayCell( + dayLabel: dayFormatter.string(from: item.date), + dateLabel: dateFormatter.string(from: item.date), + image: item.image, + color: item.color, + isToday: index == 0, + height: cellHeight + ) + } + } + + HStack(spacing: 6) { + ForEach(Array(timelineData.suffix(5).enumerated()), id: \.element.id) { _, item in + ExportDayCell( + dayLabel: dayFormatter.string(from: item.date), + dateLabel: dateFormatter.string(from: item.date), + image: item.image, + color: item.color, + isToday: false, + height: cellHeight + ) + } + } + } + .padding(.horizontal, 10) + .padding(.bottom, 8) + } + } + } + } +} + +private struct ExportLargeVotingView: View { + private var moodTint: MoodTintable.Type { + UserDefaultsStore.moodTintable() + } + + private var moodImages: MoodImagable.Type { + UserDefaultsStore.moodMoodImagable() + } + + var body: some View { + VStack(spacing: 16) { + Spacer() + + Text("How are you feeling?") + .font(.title3.weight(.semibold)) + .foregroundStyle(.primary) + .multilineTextAlignment(.center) + + HStack(spacing: 0) { + ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in + moodImages.icon(forMood: mood) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 44, height: 44) + .foregroundColor(moodTint.color(forMood: mood)) + .padding(10) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(moodTint.color(forMood: mood).opacity(0.15)) + ) + .frame(maxWidth: .infinity) + } + } + + Spacer() + } + .padding(.horizontal, 12) + .padding(.vertical, 16) + } +} + +private struct ExportDayCell: View { + let dayLabel: String + let dateLabel: String + let image: Image + let color: Color + let isToday: Bool + let height: CGFloat + + var body: some View { + VStack(spacing: 2) { + Text(dayLabel) + .font(.caption2.weight(isToday ? .bold : .medium)) + .foregroundStyle(isToday ? .primary : .secondary) + .textCase(.uppercase) + + ZStack { + RoundedRectangle(cornerRadius: 14) + .fill(color.opacity(isToday ? 0.25 : 0.12)) + .frame(height: height - 16) + + VStack(spacing: 6) { + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 38, height: 38) + .foregroundColor(color) + + Text(dateLabel) + .font(.caption.weight(isToday ? .bold : .semibold)) + .foregroundStyle(isToday ? color : .secondary) + } + } + } + .frame(maxWidth: .infinity) + } +} + +// MARK: - Export Live Activity View (Lock Screen) + +private struct ExportLiveActivityView: View { + let streak: Int + let hasLoggedToday: Bool + let mood: Mood? + + private var moodTint: MoodTintable.Type { + UserDefaultsStore.moodTintable() + } + + var body: some View { + HStack(spacing: 16) { + // Streak indicator + VStack(spacing: 4) { + Image(systemName: "flame.fill") + .font(.title) + .foregroundColor(.orange) + Text("\(streak)") + .font(.title.bold()) + Text("day streak") + .font(.caption) + .foregroundColor(.secondary) + } + + Divider() + .frame(height: 50) + + // Status + VStack(alignment: .leading, spacing: 8) { + if hasLoggedToday, let mood = mood { + HStack(spacing: 8) { + Circle() + .fill(moodTint.color(forMood: mood)) + .frame(width: 24, height: 24) + VStack(alignment: .leading) { + Text("Today's mood") + .font(.caption) + .foregroundColor(.secondary) + Text(mood.widgetDisplayName) + .font(.headline) + } + } + } else { + VStack(alignment: .leading) { + Text("Don't break your streak!") + .font(.headline) + Text("Tap to log your mood") + .font(.caption) + .foregroundColor(.secondary) + } + } + } + + Spacer() + } + .padding() + } +} +#endif diff --git a/Shared/Views/SettingsView/SettingsView.swift b/Shared/Views/SettingsView/SettingsView.swift index 1ffdced..ec750d7 100644 --- a/Shared/Views/SettingsView/SettingsView.swift +++ b/Shared/Views/SettingsView/SettingsView.swift @@ -19,6 +19,8 @@ struct SettingsContentView: View { @State private var showReminderTimePicker = false @State private var showSubscriptionStore = false @State private var showTrialDatePicker = false + @State private var isExportingWidgets = false + @State private var widgetExportPath: URL? @StateObject private var healthService = HealthService.shared @AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) @@ -58,7 +60,9 @@ struct SettingsContentView: View { animationLabButton paywallPreviewButton tipsPreviewButton - + testNotificationsButton + exportWidgetsButton + clearDataButton #endif @@ -382,6 +386,95 @@ struct SettingsContentView: View { } } + private var testNotificationsButton: some View { + ZStack { + theme.currentTheme.secondaryBGColor + Button { + LocalNotification.sendAllPersonalityNotificationsForScreenshot() + } label: { + HStack(spacing: 12) { + Image(systemName: "bell.badge.fill") + .font(.title2) + .foregroundColor(.red) + .frame(width: 32) + + VStack(alignment: .leading, spacing: 2) { + Text("Test All Notifications") + .foregroundColor(textColor) + + Text("Send 5 personality pack notifications") + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + + Image(systemName: "arrow.up.right") + .font(.caption) + .foregroundStyle(.tertiary) + } + .padding() + } + } + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) + } + + private var exportWidgetsButton: some View { + ZStack { + theme.currentTheme.secondaryBGColor + Button { + isExportingWidgets = true + Task { + widgetExportPath = await WidgetExporter.exportAllWidgets() + isExportingWidgets = false + // Open Files app to the export location + if let path = widgetExportPath { + // Show share sheet or alert with path + print("๐Ÿ“ธ Widgets exported to: \(path.path)") + } + } + } label: { + HStack(spacing: 12) { + if isExportingWidgets { + ProgressView() + .frame(width: 32) + } else { + Image(systemName: "square.grid.2x2.fill") + .font(.title2) + .foregroundColor(.purple) + .frame(width: 32) + } + + VStack(alignment: .leading, spacing: 2) { + Text("Export Widget Screenshots") + .foregroundColor(textColor) + + if let path = widgetExportPath { + Text("Saved to Documents/WidgetExports") + .font(.caption) + .foregroundColor(.green) + } else { + Text("Light & dark mode PNGs") + .font(.caption) + .foregroundStyle(.secondary) + } + } + + Spacer() + + Image(systemName: "arrow.down.doc.fill") + .font(.caption) + .foregroundStyle(.tertiary) + } + .padding() + } + .disabled(isExportingWidgets) + } + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) + } + private var clearDataButton: some View { ZStack { theme.currentTheme.secondaryBGColor diff --git a/docs/AppStoreScreens.pxd b/docs/AppStoreScreens.pxd new file mode 100644 index 0000000..3d1a62b Binary files /dev/null and b/docs/AppStoreScreens.pxd differ diff --git a/docs/competitors/README.md b/docs/competitors/README.md new file mode 100644 index 0000000..c332a32 --- /dev/null +++ b/docs/competitors/README.md @@ -0,0 +1,94 @@ +# Competitor Analysis - Mood Tracking Apps + +Analysis of top 5 mood tracking app competitors on the iOS App Store. + +--- + +## Overview Comparison + +| App | Rating | Reviews | Price | Differentiator | +|-----|--------|---------|-------|----------------| +| [Daylio](./daylio/) | 4.8 | 57K | Free + IAP | No-typing micro-diary | +| [Reflectly](./reflectly/) | 4.6 | 82K | Free + IAP | AI-powered journaling | +| [Moodnotes](./moodnotes/) | 4.7 | 11K | Free + IAP | CBT-based, clinical psychology | +| [Moodistory](./moodistory/) | 4.8 | 846 | Free + IAP | Privacy-first, lightweight | +| [Bearable](./bearable/) | 4.8 | 4.7K | Free + IAP | Comprehensive health/symptom tracking | + +--- + +## App Store URLs + +1. **Daylio:** https://apps.apple.com/us/app/daylio-journal-daily-diary/id1194023242 +2. **Reflectly:** https://apps.apple.com/us/app/reflectly-journal-ai-diary/id1241229134 +3. **Moodnotes:** https://apps.apple.com/us/app/moodnotes-mood-tracker/id1019230398 +4. **Moodistory:** https://apps.apple.com/us/app/emotion-tracker-moodistory/id1335347860 +5. **Bearable:** https://apps.apple.com/us/app/bearable-symptom-tracker/id1482581097 + +--- + +## Market Positioning + +### By Review Count (Market Size Indicator) +1. Reflectly - 82K reviews (largest user base) +2. Daylio - 57K reviews +3. Moodnotes - 11K reviews +4. Bearable - 4.7K reviews +5. Moodistory - 846 reviews (indie) + +### By Approach +- **Quick Logging:** Daylio, Moodistory (tap-based, no typing) +- **AI/Smart:** Reflectly (AI-driven prompts) +- **Clinical/CBT:** Moodnotes (psychology-backed) +- **Health Focused:** Bearable (chronic illness, symptoms) + +### By Price Point (Monthly) +- Moodnotes: $14.99/mo (highest) +- Reflectly: $9.99/mo +- Bearable: $6.99/mo +- Daylio: Variable tiers +- Moodistory: Variable tiers (lowest) + +--- + +## Feature Comparison + +| Feature | Daylio | Reflectly | Moodnotes | Moodistory | Bearable | +|---------|--------|-----------|-----------|------------|----------| +| Quick tap logging | Yes | No | No | Yes | Yes | +| AI features | No | Yes | No | No | No | +| CBT-based | No | Yes | Yes | No | No | +| Apple Watch | Yes | No | Yes | Yes | No | +| Apple Health | Yes | No | No | Yes | Yes | +| Year in Pixels | Yes | No | No | Yes | No | +| Privacy focus | Medium | Low | Medium | High | Medium | +| Widgets | Yes | Yes | No | Yes | No | +| Voice input | Yes | Yes | No | No | No | +| Export data | Yes | Yes | Yes | Yes | Yes | + +--- + +## Downloaded Assets + +### Screenshots Available +- Moodnotes: 7 screenshots +- Moodistory: 10 screenshots +- Reflectly: 8 screenshots + +### App Icons Only (Screenshots not via API) +- Daylio +- Bearable + +--- + +## Key Takeaways for Feels + +1. **Quick logging is valued** - Daylio and Moodistory emphasize speed +2. **Privacy is a differentiator** - Moodistory's privacy-first approach stands out +3. **AI is trending** - Reflectly's AI positioning attracts users +4. **Visual insights matter** - Year in Pixels and calendars are common +5. **Apple ecosystem integration** - Watch, Health, Widgets are expected +6. **CBT/psychology backing** - Adds credibility for mental health positioning + +--- + +*Analysis conducted January 2026* diff --git a/docs/competitors/bearable/README.md b/docs/competitors/bearable/README.md new file mode 100644 index 0000000..30e571b --- /dev/null +++ b/docs/competitors/bearable/README.md @@ -0,0 +1,114 @@ +# Bearable - Symptom Tracker + +## App Store Information + +**App Store URL:** https://apps.apple.com/us/app/bearable-symptom-tracker/id1482581097 + +**App ID:** 1482581097 + +--- + +## Basic Details + +| Field | Value | +|-------|-------| +| **Title** | Bearable - Symptom Tracker | +| **Subtitle** | Mood, Health, Migraine, Period | +| **Developer** | Bearable Ltd | +| **Category** | Health & Fitness | +| **Age Rating** | 4+ | +| **Price** | Free (with In-App Purchases) | + +--- + +## Ratings & Reviews + +| Metric | Value | +|--------|-------| +| **Average Rating** | 4.8 / 5 | +| **Total Reviews** | 4,679 | + +--- + +## Technical Details + +| Spec | Value | +|------|-------| +| **Version** | 1.0.579 | +| **Size** | 102.6 MB | +| **iOS Requirement** | iOS 15.1 or later | +| **Languages** | English only | + +--- + +## Description + +Feeling overwhelmed by your health? Bearable can help you feel more in control of your symptoms, flare-ups, and overall well-being. + +Join over 900,000 people securely managing their Chronic Illnesses, Migraine, Pain, Headaches, Fatigue, IBS, PoTS, PCOS, EDS, Period, Mood, Mental Health and more - with Bearable. + +Bearable helps you discover and manage triggers for changes in your health. Our simple, customizable tracking tools make it easy to understand the correlation between anything you do and how you feel. + +--- + +## Key Features + +- Symptom and mood tracking +- Medication and treatment logging +- Pain and headache monitoring +- Period and PCOS cycle tracking +- PoTS and EDS flare-up documentation +- Apple Health and Fitbit integration +- Data export functionality +- Customizable reminders +- Dark mode support +- Encrypted, secure data storage +- Correlation analysis between activities and symptoms + +--- + +## Pricing Options + +| Plan | Price | +|------|-------| +| Monthly | $6.99 | +| Yearly | $34.99 | +| Various other tiers | $4.49 - $49.99 | + +--- + +## What's New (v1.0.579 - Dec 2025) + +Bug fixes and quality-of-life design improvements. + +--- + +## Assets + +- `app_icon.jpg` - App icon (512x512) +- Screenshots not available via API + +--- + +## Competitive Analysis Notes + +**Strengths:** +- Comprehensive health tracking beyond just mood +- 900,000+ user base +- Strong chronic illness community focus +- Correlation analysis between factors +- Created by people with chronic conditions + +**Target Audience:** +- Users with chronic illnesses (migraine, IBS, fibromyalgia, etc.) +- People tracking medication effectiveness +- Users needing symptom-to-activity correlation +- Healthcare provider communication needs + +**Differentiators:** +- Broader health scope (not just mood) +- Chronic illness specific features +- Medical conversation support +- Symptom correlation algorithms + +**Note:** This is more of a health/symptom tracker than pure mood tracker - different market positioning. diff --git a/docs/competitors/bearable/app_icon.jpg b/docs/competitors/bearable/app_icon.jpg new file mode 100644 index 0000000..2e13984 Binary files /dev/null and b/docs/competitors/bearable/app_icon.jpg differ diff --git a/docs/competitors/daylio/README.md b/docs/competitors/daylio/README.md new file mode 100644 index 0000000..ea71d06 --- /dev/null +++ b/docs/competitors/daylio/README.md @@ -0,0 +1,105 @@ +# Daylio Journal - Daily Diary + +## App Store Information + +**App Store URL:** https://apps.apple.com/us/app/daylio-journal-daily-diary/id1194023242 + +**App ID:** 1194023242 + +--- + +## Basic Details + +| Field | Value | +|-------|-------| +| **Title** | Daylio Journal - Daily Diary | +| **Subtitle** | Mood Tracker, Health, Habits | +| **Developer** | Relaxio s.r.o. | +| **Category** | Lifestyle | +| **Age Rating** | 4+ | +| **Price** | Free (with In-App Purchases) | + +--- + +## Ratings & Reviews + +| Metric | Value | +|--------|-------| +| **Average Rating** | 4.8 / 5 | +| **Total Reviews** | 57,048 | + +--- + +## Technical Details + +| Spec | Value | +|------|-------| +| **Version** | 1.69.2 | +| **Size** | 284.6 MB | +| **iOS Requirement** | iOS 14.0 or later | +| **Languages** | 30 languages | + +--- + +## Description + +Self-Care Bullet Journal with Goals - Mood Diary & Happiness Tracker + +Daylio enables you to keep a private journal without having to type a single line. Try this beautifully designed & stunningly simple micro-diary app right now for FREE! + +### What is Daylio + +Daylio is a very versatile app, and you can turn it in whatever you need to track. Your fitness goal pal. Your mental health coach. Your food log. Your gratitude diary. Mood tracker. Exercise, meditate, eat, and be grateful. Take care of yourself. + +--- + +## Key Features + +- Multiple daily mood entries +- Customizable activities and moods with emoji support +- Weekly, monthly, yearly analytics +- "Year in Pixels" visualization +- Dark mode and color theme customization +- Goal and habit tracking +- Voice memo recording +- Activity widgets +- PDF/CSV export capability +- PIN lock security +- iCloud backup integration +- Apple Health data support + +--- + +## Pricing Options + +| Plan | Price | +|------|-------| +| Various Premium Tiers | $4.99, $17.99, $23.99, $35.99, $59.99 | + +--- + +## What's New (v1.69.2 - Dec 2025) + +"Fresh new look" with new themes and colorful emojis that match your vibe. + +--- + +## Assets + +- `app_icon.jpg` - App icon (512x512) +- Screenshots not available via API + +--- + +## Competitive Analysis Notes + +**Strengths:** +- Highest review count in category (57K+) +- No typing required - tap-based logging +- Extensive customization options +- Year in Pixels visualization is unique + +**Target Audience:** +- Users who want quick, low-effort mood logging +- People tracking habits alongside moods +- Users who prefer visual over text-based journaling diff --git a/docs/competitors/daylio/app_icon.jpg b/docs/competitors/daylio/app_icon.jpg new file mode 100644 index 0000000..c18ac81 Binary files /dev/null and b/docs/competitors/daylio/app_icon.jpg differ diff --git a/docs/competitors/index.html b/docs/competitors/index.html new file mode 100644 index 0000000..c0b6604 --- /dev/null +++ b/docs/competitors/index.html @@ -0,0 +1,1236 @@ + + + + + + Competitor Analysis - Mood Tracking Apps + + + +
+
+

Competitor Analysis

+

Top 5 Mood Tracking Apps on the iOS App Store

+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AppRatingReviewsCategoryPriceDifferentiator
+
+ Daylio + Daylio +
+
โ˜… 4.857KLifestyleFree + IAPNo-typing micro-diary
+
+ Reflectly + Reflectly +
+
โ˜… 4.682KHealth & FitnessFree + IAPAI-powered journaling
+
+ Moodnotes + Moodnotes +
+
โ˜… 4.711KHealth & FitnessFree + IAPCBT-based, clinical
+
+ Moodistory + Moodistory +
+
โ˜… 4.8846Health & FitnessFree + IAPPrivacy-first, lightweight
+
+ Bearable + Bearable +
+
โ˜… 4.84.7KHealth & FitnessFree + IAPHealth/symptom tracking
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureDaylioReflectlyMoodnotesMoodistoryBearable
Quick tap loggingโœ“โ€”โ€”โœ“โœ“
AI featuresโ€”โœ“โ€”โ€”โ€”
CBT-basedโ€”โœ“โœ“โ€”โ€”
Apple Watchโœ“โ€”โœ“โœ“โ€”
Apple Healthโœ“โ€”โ€”โœ“โœ“
Year in Pixelsโœ“โ€”โ€”โœ“โ€”
Widgetsโœ“โœ“โ€”โœ“โ€”
Voice inputโœ“โœ“โ€”โ€”โ€”
Export dataโœ“โœ“โœ“โœ“โœ“
+
+
+ + +
+
+ Daylio +
+

Daylio Journal - Daily Diary

+

Mood Tracker, Health, Habits

+
+
+ Rating + โ˜… 4.8 +
+
+ Reviews + 57,048 +
+
+ Developer + Relaxio s.r.o. +
+
+ Category + Lifestyle +
+
+ Size + 284.6 MB +
+
+ Version + 1.69.2 +
+
+ + View on App Store โ†’ + +
+
+ +
+
+

Description

+

Self-Care Bullet Journal with Goals - Mood Diary & Happiness Tracker. Daylio enables you to keep a private journal without having to type a single line. It's a versatile app that can be your fitness goal pal, mental health coach, food log, or gratitude diary.

+
+
+

Key Features

+
    +
  • โœ“ Multiple daily mood entries
  • +
  • โœ“ Customizable activities & moods
  • +
  • โœ“ Year in Pixels visualization
  • +
  • โœ“ Goal and habit tracking
  • +
  • โœ“ Voice memo recording
  • +
  • โœ“ iCloud backup & Apple Health
  • +
  • โœ“ PDF/CSV export
  • +
+
+
+

Pricing

+ + + +
BaseFree
Premium (various tiers)$4.99 - $59.99
+
+
+

Technical Details

+ + + + +
iOS RequirementiOS 14.0+
Languages30 languages
Age Rating4+
+
+
+ +
+

Competitive Analysis

+
+
+

Strengths

+
    +
  • Highest review count in category (57K+)
  • +
  • No typing required - tap-based logging
  • +
  • Extensive customization options
  • +
  • Year in Pixels visualization is unique
  • +
+
+
+

Target Audience

+
    +
  • Users who want quick, low-effort mood logging
  • +
  • People tracking habits alongside moods
  • +
  • Users who prefer visual over text-based journaling
  • +
+
+
+
+ +
+

Screenshots

+

Screenshots not available via API - icon only

+
+
+ + +
+
+ Reflectly +
+

Reflectly - Journal & AI Diary

+

Mood Tracker & Daily Quotes

+
+
+ Rating + โ˜… 4.6 +
+
+ Reviews + 81,727 +
+
+ Developer + Kodeon, Inc. +
+
+ Category + Health & Fitness +
+
+ Size + 72.3 MB +
+
+ Version + 4.15.0 +
+
+ + View on App Store โ†’ + +
+
+ +
+
+

Description

+

Reflectly is the #1 journaling app that's like your best friend. It's the world's first intelligent journal app & mood tracker that gives you personalized motivation and prompts the more you use it. Uses AI, positive psychology, mindfulness, and CBT to help you thrive.

+
+
+

Key Features

+
    +
  • โœ“ AI-driven personalized prompts
  • +
  • โœ“ Daily mood tracking with graphs
  • +
  • โœ“ Voice-to-text journaling
  • +
  • โœ“ Habit tracker with achievements
  • +
  • โœ“ Lock screen widgets (iOS 16+)
  • +
  • โœ“ Morning quotes & evening reflections
  • +
+
+
+

Pricing

+ + + + +
BaseFree
Monthly$9.99/mo
Yearly$59.99/yr
+
+
+

Technical Details

+ + + + +
iOS RequirementiOS 15.0+
LanguagesEnglish
Age Rating4+
+
+
+ +
+

Competitive Analysis

+
+
+

Strengths

+
    +
  • Highest review count (82K+) - massive user base
  • +
  • AI-powered personalization
  • +
  • "World's first intelligent journal" positioning
  • +
  • Strong marketing/branding
  • +
+
+
+

Target Audience

+
    +
  • Users who prefer guided journaling
  • +
  • People wanting AI-driven insights
  • +
  • Mindfulness/self-care focused users
  • +
+
+
+
+ +
+

Screenshots

+
+ Screenshot 1 + Screenshot 2 + Screenshot 3 + Screenshot 4 + Screenshot 5 + Screenshot 6 + Screenshot 7 + Screenshot 8 +
+
+
+ + +
+
+ Moodnotes +
+

Moodnotes - Mood Tracker

+

Daily Self Care Journal & CBT

+
+
+ Rating + โ˜… 4.7 +
+
+ Reviews + 10,942 +
+
+ Developer + Mosaic S.r.l. +
+
+ Category + Health & Fitness +
+
+ Size + 153.5 MB +
+
+ Version + 3.14.8 +
+
+ + View on App Store โ†’ + +
+
+ +
+
+

Description

+

Don't let your mood affect your life. Take control over it! Created by design experts and clinical psychologists, Moodnotes is grounded in cognitive behavior therapy (CBT) and positive psychology. Helps avoid common thinking traps and develop healthier perspectives.

+
+
+

Key Features

+
    +
  • โœ“ CBT-based approach
  • +
  • โœ“ Face scanning for mood detection
  • +
  • โœ“ Thinking pattern recognition
  • +
  • โœ“ Expert-written mental health articles
  • +
  • โœ“ Apple Watch compatibility
  • +
  • โœ“ iCloud sync/backup
  • +
+
+
+

Pricing

+ + + + +
BaseFree
Monthly$14.99/mo
Yearly$29.99 - $89.99/yr
+
+
+

Technical Details

+ + + + +
iOS RequirementiOS 13.0+
Languages8 languages
Age Rating13+
+
+
+ +
+

Competitive Analysis

+
+
+

Strengths

+
    +
  • CBT-based approach (clinical foundation)
  • +
  • Face scanning for automatic mood detection
  • +
  • Created by clinical psychologists
  • +
  • Strong mental health article content
  • +
+
+
+

Target Audience

+
    +
  • Users interested in CBT/therapy approaches
  • +
  • People dealing with anxiety
  • +
  • Those who want science-backed tracking
  • +
+
+
+
+ +
+

Screenshots

+
+ Screenshot 1 + Screenshot 2 + Screenshot 3 + Screenshot 4 + Screenshot 5 + Screenshot 6 + Screenshot 7 +
+
+
+ + +
+
+ Moodistory +
+

Emotion Tracker: Moodistory

+

Mood Journal & Mental Health

+
+
+ Rating + โ˜… 4.8 +
+
+ Reviews + 846 +
+
+ Developer + Christoph Matzka +
+
+ Category + Health & Fitness +
+
+ Size + 35.8 MB +
+
+ Version + 3.5 +
+
+ + View on App Store โ†’ + +
+
+ +
+
+

Description

+

A low-effort mood tracker with unique, beautiful design that respects 100% your privacy. Create journal entries in less than 5 seconds, without writing a single word. All data stays on your device - they never store mood data on external servers.

+
+
+

Key Features

+
    +
  • โœ“ 180+ customizable events
  • +
  • โœ“ Year in Pixels visualization
  • +
  • โœ“ Apple Health integration
  • +
  • โœ“ 100% privacy focused
  • +
  • โœ“ 5 home screen widgets
  • +
  • โœ“ Apple Watch app
  • +
  • โœ“ Custom mood scales (2-11 points)
  • +
+
+
+

Pricing

+ + + +
BaseFree
Premium tiers$2.99 - $39.99
+
+
+

Technical Details

+ + + + +
iOS RequirementiOS 16+
Languages8 languages
Age Rating9+
+
+
+ +
+

Competitive Analysis

+
+
+

Strengths

+
    +
  • Smallest app size (35.8 MB) - lightweight
  • +
  • Strong privacy focus (data on device only)
  • +
  • Beautiful, unique design
  • +
  • Indie developer (personal touch)
  • +
+
+
+

Target Audience

+
    +
  • Privacy-conscious users
  • +
  • Users who want quick, no-typing logging
  • +
  • Apple ecosystem users
  • +
+
+
+
+ +
+

Screenshots

+
+ Screenshot 1 + Screenshot 2 + Screenshot 3 + Screenshot 4 + Screenshot 5 + Screenshot 6 + Screenshot 7 + Screenshot 8 + Screenshot 9 + Screenshot 10 +
+
+
+ + +
+
+ Bearable +
+

Bearable - Symptom Tracker

+

Mood, Health, Migraine, Period

+
+
+ Rating + โ˜… 4.8 +
+
+ Reviews + 4,679 +
+
+ Developer + Bearable Ltd +
+
+ Category + Health & Fitness +
+
+ Size + 102.6 MB +
+
+ Version + 1.0.579 +
+
+ + View on App Store โ†’ + +
+
+ +
+
+

Description

+

Feeling overwhelmed by your health? Join over 900,000 people managing Chronic Illnesses, Migraine, Pain, Fatigue, IBS, PoTS, PCOS, EDS, Period, Mood, Mental Health and more. Discover and manage triggers for changes in your health with correlation analysis.

+
+
+

Key Features

+
    +
  • โœ“ Symptom and mood tracking
  • +
  • โœ“ Medication and treatment logging
  • +
  • โœ“ Pain and headache monitoring
  • +
  • โœ“ Period and PCOS tracking
  • +
  • โœ“ Apple Health & Fitbit integration
  • +
  • โœ“ Correlation analysis
  • +
  • โœ“ Encrypted data storage
  • +
+
+
+

Pricing

+ + + + +
BaseFree
Monthly$6.99/mo
Yearly$34.99/yr
+
+
+

Technical Details

+ + + + +
iOS RequirementiOS 15.1+
LanguagesEnglish only
Age Rating4+
+
+
+ +
+

Competitive Analysis

+
+
+

Strengths

+
    +
  • Comprehensive health tracking beyond mood
  • +
  • 900,000+ user base
  • +
  • Strong chronic illness community
  • +
  • Created by people with chronic conditions
  • +
+
+
+

Target Audience

+
    +
  • Users with chronic illnesses
  • +
  • People tracking medication effectiveness
  • +
  • Healthcare provider communication needs
  • +
+
+
+

Note

+
    +
  • More health/symptom tracker than pure mood tracker
  • +
  • Different market positioning - broader health focus
  • +
+
+
+
+ +
+

Screenshots

+

Screenshots not available via API - icon only

+
+
+
+ + + + + + + diff --git a/docs/competitors/moodistory/README.md b/docs/competitors/moodistory/README.md new file mode 100644 index 0000000..838157b --- /dev/null +++ b/docs/competitors/moodistory/README.md @@ -0,0 +1,106 @@ +# Emotion Tracker: Moodistory + +## App Store Information + +**App Store URL:** https://apps.apple.com/us/app/emotion-tracker-moodistory/id1335347860 + +**App ID:** 1335347860 + +--- + +## Basic Details + +| Field | Value | +|-------|-------| +| **Title** | Emotion Tracker: Moodistory | +| **Subtitle** | Mood Journal & Mental Health | +| **Developer** | Christoph Matzka | +| **Category** | Health & Fitness | +| **Age Rating** | 9+ | +| **Price** | Free (with In-App Purchases) | + +--- + +## Ratings & Reviews + +| Metric | Value | +|--------|-------| +| **Average Rating** | 4.8 / 5 | +| **Total Reviews** | 846 | + +--- + +## Technical Details + +| Spec | Value | +|------|-------| +| **Version** | 3.5 | +| **Size** | 35.8 MB | +| **iOS Requirement** | iOS 16+ | +| **Languages** | 8 languages (EN, FR, DE, JA, KO, RU, ZH, ES) | + +--- + +## Description + +Moodistory is a low-effort mood tracker with a unique and beautiful design that respects 100% your privacy. Create journal entries in less than 5 seconds, without writing a single word. Use the integrated mood calendar (or year in pixels view) to easily find mood patterns. Become aware of your mood highs and lows and analyze the cause of mood swings. Better manage anxiety, discover triggers for a positive mood, and ultimately establish awareness about your mental health. + +--- + +## Key Features + +- 180+ customizable events/activities across 10 categories +- Interactive mood calendars (yearly, monthly, daily views) +- Apple Health integration for sleep, daylight, exercise tracking +- Face ID/Touch ID/Passcode security +- PDF and CSV export functionality +- 5 home screen widgets +- Apple Watch app support +- Custom mood scales (2-11 point range) +- iCloud and manual backup options +- Customizable color themes +- **100% privacy focused - data stays on device** + +--- + +## Pricing Options + +| Plan | Price | +|------|-------| +| Various Premium Tiers | $2.99, $6.99, $14.99, $19.99, $39.99 | + +--- + +## What's New (v3.5 - Oct 2025) + +Added Apple Health connectivity for analyzing mood influences from sleep, daylight, exercise, steps, and active energy. + +--- + +## Assets + +- `app_icon.jpg` - App icon (512x512) +- `screenshot_1.png` through `screenshot_10.png` - App Store screenshots + +--- + +## Competitive Analysis Notes + +**Strengths:** +- Smallest app size (35.8 MB) - very lightweight +- Strong privacy focus (data stays on device) +- Beautiful, unique design +- Indie developer (personal touch) +- Extensive Apple Health integration + +**Target Audience:** +- Privacy-conscious users +- Users who want quick, no-typing logging +- Apple ecosystem users (Watch, Health integration) +- Users who prefer lightweight apps + +**Differentiators:** +- Privacy-first approach (no external servers) +- 5-second logging claim +- Year in pixels visualization +- Custom mood scales (2-11 points) diff --git a/docs/competitors/moodistory/app_icon.jpg b/docs/competitors/moodistory/app_icon.jpg new file mode 100644 index 0000000..2b5d32f Binary files /dev/null and b/docs/competitors/moodistory/app_icon.jpg differ diff --git a/docs/competitors/moodistory/screenshot_1.png b/docs/competitors/moodistory/screenshot_1.png new file mode 100644 index 0000000..1f61d30 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_1.png differ diff --git a/docs/competitors/moodistory/screenshot_10.png b/docs/competitors/moodistory/screenshot_10.png new file mode 100644 index 0000000..ff1d4b1 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_10.png differ diff --git a/docs/competitors/moodistory/screenshot_2.png b/docs/competitors/moodistory/screenshot_2.png new file mode 100644 index 0000000..ab85258 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_2.png differ diff --git a/docs/competitors/moodistory/screenshot_3.png b/docs/competitors/moodistory/screenshot_3.png new file mode 100644 index 0000000..17a2ff9 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_3.png differ diff --git a/docs/competitors/moodistory/screenshot_4.png b/docs/competitors/moodistory/screenshot_4.png new file mode 100644 index 0000000..281be62 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_4.png differ diff --git a/docs/competitors/moodistory/screenshot_5.png b/docs/competitors/moodistory/screenshot_5.png new file mode 100644 index 0000000..1d98bf7 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_5.png differ diff --git a/docs/competitors/moodistory/screenshot_6.png b/docs/competitors/moodistory/screenshot_6.png new file mode 100644 index 0000000..1d93682 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_6.png differ diff --git a/docs/competitors/moodistory/screenshot_7.png b/docs/competitors/moodistory/screenshot_7.png new file mode 100644 index 0000000..7c312af Binary files /dev/null and b/docs/competitors/moodistory/screenshot_7.png differ diff --git a/docs/competitors/moodistory/screenshot_8.png b/docs/competitors/moodistory/screenshot_8.png new file mode 100644 index 0000000..6c21998 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_8.png differ diff --git a/docs/competitors/moodistory/screenshot_9.png b/docs/competitors/moodistory/screenshot_9.png new file mode 100644 index 0000000..9030202 Binary files /dev/null and b/docs/competitors/moodistory/screenshot_9.png differ diff --git a/docs/competitors/moodnotes/README.md b/docs/competitors/moodnotes/README.md new file mode 100644 index 0000000..69c4eca --- /dev/null +++ b/docs/competitors/moodnotes/README.md @@ -0,0 +1,107 @@ +# Moodnotes - Mood Tracker + +## App Store Information + +**App Store URL:** https://apps.apple.com/us/app/moodnotes-mood-tracker/id1019230398 + +**App ID:** 1019230398 + +--- + +## Basic Details + +| Field | Value | +|-------|-------| +| **Title** | Moodnotes - Mood Tracker | +| **Subtitle** | Daily Self Care Journal & CBT | +| **Developer** | Mosaic S.r.l. | +| **Category** | Health & Fitness | +| **Age Rating** | 13+ | +| **Price** | Free (with In-App Purchases) | + +--- + +## Ratings & Reviews + +| Metric | Value | +|--------|-------| +| **Average Rating** | 4.7 / 5 | +| **Total Reviews** | 10,942 | + +--- + +## Technical Details + +| Spec | Value | +|------|-------| +| **Version** | 3.14.8 | +| **Size** | 153.5 MB | +| **iOS Requirement** | iOS 13.0 or later | +| **Languages** | 8 languages (EN, FR, DE, IT, RU, ZH, ES, SV) | + +--- + +## Description + +Don't let your mood affect your life. Take control over it! + +Meet Moodnotes - a super easy mood tracker & journaling app to capture your mood and help you improve your thinking habits. Moodnotes empowers you to track your mood over time, avoid common thinking traps, and develop perspectives associated with increased happiness and well-being. + +Created by design experts and clinical psychologists (creators of MoodKit), Moodnotes is grounded in the scientifically-supported content of cognitive behavior therapy and positive psychology. + +--- + +## Key Features + +- Mood tracking with emotion identification +- Automatic face scanning for mood detection +- Photo/memory attachment capability +- Expert-written mental health articles +- Thinking pattern recognition ("traps") +- Anxiety reduction tools +- Apple Watch compatibility +- iCloud sync/backup +- Premium: unlimited entries, statistics, insights, enhanced articles + +--- + +## Pricing Options + +| Plan | Price | +|------|-------| +| Monthly | $14.99 | +| Yearly | $29.99 - $89.99 | + +--- + +## What's New (v3.14.8 - Oct 2024) + +Bug fixes and performance improvements. + +--- + +## Assets + +- `app_icon.jpg` - App icon (512x512) +- `screenshot_1.jpg` through `screenshot_7.jpg` - App Store screenshots + +--- + +## Competitive Analysis Notes + +**Strengths:** +- CBT-based approach (clinical psychology foundation) +- Face scanning for automatic mood detection +- Created by clinical psychologists +- Strong mental health article content + +**Target Audience:** +- Users interested in CBT/therapy approaches +- People wanting to understand thinking patterns +- Users dealing with anxiety +- Those who want science-backed mood tracking + +**Differentiators:** +- "Thinking traps" identification +- Clinical psychology pedigree +- Face scanning technology diff --git a/docs/competitors/moodnotes/app_icon.jpg b/docs/competitors/moodnotes/app_icon.jpg new file mode 100644 index 0000000..7113a00 Binary files /dev/null and b/docs/competitors/moodnotes/app_icon.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_1.jpg b/docs/competitors/moodnotes/screenshot_1.jpg new file mode 100644 index 0000000..67653dd Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_1.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_2.jpg b/docs/competitors/moodnotes/screenshot_2.jpg new file mode 100644 index 0000000..577d4eb Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_2.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_3.jpg b/docs/competitors/moodnotes/screenshot_3.jpg new file mode 100644 index 0000000..eb70454 Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_3.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_4.jpg b/docs/competitors/moodnotes/screenshot_4.jpg new file mode 100644 index 0000000..e74a51d Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_4.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_5.jpg b/docs/competitors/moodnotes/screenshot_5.jpg new file mode 100644 index 0000000..97c6ff1 Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_5.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_6.jpg b/docs/competitors/moodnotes/screenshot_6.jpg new file mode 100644 index 0000000..33df57c Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_6.jpg differ diff --git a/docs/competitors/moodnotes/screenshot_7.jpg b/docs/competitors/moodnotes/screenshot_7.jpg new file mode 100644 index 0000000..edd732c Binary files /dev/null and b/docs/competitors/moodnotes/screenshot_7.jpg differ diff --git a/docs/competitors/reflectly/README.md b/docs/competitors/reflectly/README.md new file mode 100644 index 0000000..7002237 --- /dev/null +++ b/docs/competitors/reflectly/README.md @@ -0,0 +1,113 @@ +# Reflectly - Journal & AI Diary + +## App Store Information + +**App Store URL:** https://apps.apple.com/us/app/reflectly-journal-ai-diary/id1241229134 + +**App ID:** 1241229134 + +--- + +## Basic Details + +| Field | Value | +|-------|-------| +| **Title** | Reflectly - Journal & AI Diary | +| **Subtitle** | Mood Tracker & Daily Quotes | +| **Developer** | Kodeon, Inc. | +| **Category** | Health & Fitness | +| **Age Rating** | 4+ | +| **Price** | Free (with In-App Purchases) | + +--- + +## Ratings & Reviews + +| Metric | Value | +|--------|-------| +| **Average Rating** | 4.6 / 5 | +| **Total Reviews** | 81,727 | + +--- + +## Technical Details + +| Spec | Value | +|------|-------| +| **Version** | 4.15.0 | +| **Size** | 72.3 MB | +| **iOS Requirement** | iOS 15.0 or later | +| **Languages** | English | + +--- + +## Description + +Reflectly is the #1 journaling app that's like your best friend. Vent your thoughts & feelings to improve your mood and practice mindfulness. Write down how you feel each day in your own mood diary. It's the world's first intelligent journal app & mood tracker that gives you personalized motivation and prompts the more you use it. + +### The Best Journal App for Self-Care and Mindfulness + +How you're feeling on a daily basis matters. Reflectly is a personal journal driven by AI to help you deal with negative thoughts and increase positivity. It uses positive psychology, mindfulness, and cognitive behavioral therapy (CBT) to help you thrive. + +--- + +## Key Features + +- Daily mood tracking with correlations and graphs +- Personalized journaling prompts and reminders +- Mood diary entries with voice-to-text capability +- Daily, weekly, and monthly insights +- Habit tracker with achievement system +- Advanced statistics (premium feature) +- Morning motivation quotes and evening reflections +- Previous entry editing and review +- Lock screen widgets (iOS 16+) +- AI-driven personalized prompts + +--- + +## Pricing Options + +| Plan | Price | +|------|-------| +| Monthly | $9.99 | +| Yearly | $59.99 | + +--- + +## What's New (v4.15.0 - Nov 2025) + +Bug fixes and performance improvements. + +--- + +## Assets + +- `app_icon.jpg` - App icon (512x512) +- `screenshot_1.jpg` through `screenshot_8.jpg` - App Store screenshots + +--- + +## Competitive Analysis Notes + +**Strengths:** +- Highest review count (81K+) - massive user base +- AI-powered personalization +- "World's first intelligent journal" positioning +- Strong marketing/branding ("like your best friend") +- Part of Growth Bundle (cross-app ecosystem) + +**Target Audience:** +- Users who prefer guided journaling +- People wanting AI-driven insights +- Mindfulness/self-care focused users +- Those who like motivational content + +**Differentiators:** +- AI-powered prompts and personalization +- "Best friend" emotional positioning +- Voice-to-text journaling +- Growth Bundle ecosystem integration +- Lock screen widgets + +**Pricing Note:** Most expensive monthly option ($9.99/mo) but positions as premium AI experience. diff --git a/docs/competitors/reflectly/app_icon.jpg b/docs/competitors/reflectly/app_icon.jpg new file mode 100644 index 0000000..17170a4 Binary files /dev/null and b/docs/competitors/reflectly/app_icon.jpg differ diff --git a/docs/competitors/reflectly/screenshot_1.jpg b/docs/competitors/reflectly/screenshot_1.jpg new file mode 100644 index 0000000..c24cc4c Binary files /dev/null and b/docs/competitors/reflectly/screenshot_1.jpg differ diff --git a/docs/competitors/reflectly/screenshot_2.jpg b/docs/competitors/reflectly/screenshot_2.jpg new file mode 100644 index 0000000..6c5c8ef Binary files /dev/null and b/docs/competitors/reflectly/screenshot_2.jpg differ diff --git a/docs/competitors/reflectly/screenshot_3.jpg b/docs/competitors/reflectly/screenshot_3.jpg new file mode 100644 index 0000000..16d86b0 Binary files /dev/null and b/docs/competitors/reflectly/screenshot_3.jpg differ diff --git a/docs/competitors/reflectly/screenshot_4.jpg b/docs/competitors/reflectly/screenshot_4.jpg new file mode 100644 index 0000000..893c45b Binary files /dev/null and b/docs/competitors/reflectly/screenshot_4.jpg differ diff --git a/docs/competitors/reflectly/screenshot_5.jpg b/docs/competitors/reflectly/screenshot_5.jpg new file mode 100644 index 0000000..c758d56 Binary files /dev/null and b/docs/competitors/reflectly/screenshot_5.jpg differ diff --git a/docs/competitors/reflectly/screenshot_6.jpg b/docs/competitors/reflectly/screenshot_6.jpg new file mode 100644 index 0000000..3fd83b4 Binary files /dev/null and b/docs/competitors/reflectly/screenshot_6.jpg differ diff --git a/docs/competitors/reflectly/screenshot_7.jpg b/docs/competitors/reflectly/screenshot_7.jpg new file mode 100644 index 0000000..850dd47 Binary files /dev/null and b/docs/competitors/reflectly/screenshot_7.jpg differ diff --git a/docs/competitors/reflectly/screenshot_8.jpg b/docs/competitors/reflectly/screenshot_8.jpg new file mode 100644 index 0000000..c0072fc Binary files /dev/null and b/docs/competitors/reflectly/screenshot_8.jpg differ