Refactor ZStack layouts to .background(), add Year View accessibility IDs, triage QA test plan

Replace ZStack-with-gradient patterns with idiomatic .background() modifier
across onboarding, customize, and settings views. Add accessibility identifiers
to Year View charts for UI test automation. Mark 67 impossible-to-automate
tests RED in QA plan and scaffold initial Year View and Settings onboarding tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-20 09:17:52 -06:00
parent ffc74f1a27
commit 5895b387be
22 changed files with 1469 additions and 1378 deletions

View File

@@ -8,9 +8,9 @@
/* Begin PBXBuildFile section */
06E4767B5977FAC8B644FC92 /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */; };
A1B2C3D4E5F607080910ABCD /* DayViewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */; };
1C0DAB51279DB0FB003B1F21 /* Feels/Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */; };
1C0DAB52279DB0FB003B1F22 /* Feels/Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */; };
1AB245144C89927264D16645 /* InsightsEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */; };
1C0DAB51279DB0FB003B1F21 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */; };
1C0DAB52279DB0FB003B1F22 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */; };
1C9566442EF8F5F70032E68F /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 1C9566432EF8F5F70032E68F /* Algorithms */; };
1CB4D0A028787D8A00902A56 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB4D09F28787D8A00902A56 /* StoreKit.framework */; };
1CD90B07278C7DE0001C4FEA /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B06278C7DE0001C4FEA /* Tests_iOS.swift */; };
@@ -25,50 +25,52 @@
1CDE000F2F3BBD26006AE6A1 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 1CA00002300000000000002A /* PostHog */; };
1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */; };
1CDEFBC02F3B8736006AE6A1 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */; };
2EE4D94530F6BF39B26FB4D4 /* DayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427CD9C91D43AB6A0302B4DD /* DayScreen.swift */; };
46F07FA9D330456697C9AC29 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */; };
4F1C717B7747918A459322CB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D304CD05CC7C662CCD7DCB /* Foundation.framework */; };
54259F7B3F4E959B3F4055E4 /* StreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2A2FC314F88244CA946BF /* StreakTests.swift */; };
9559409B5AEEAB40EBCB6AF9 /* VoteLogicsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */; };
EEB21B1CAA8EAEB497BD9FB3 /* DataControllerCRUDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */; };
A018FE95582C04ED0F1806DC /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */; };
E0579E66FFBBF124AC625ACD /* WaitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */; };
C26D40397E1AA24816FB3751 /* TabBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */; };
2EE4D94530F6BF39B26FB4D4 /* DayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427CD9C91D43AB6A0302B4DD /* DayScreen.swift */; };
A371ED1B0784315F96FFC6BD /* EntryDetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */; };
92C1523E0398F866DB4CA027 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA8B21231D67DED575502 /* SettingsScreen.swift */; };
AA11110011111100AAAAAAAA /* AppLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11111111111111AAAAAAAA /* AppLaunchTests.swift */; };
BB22220022222200BBBBBBBB /* MoodLoggingEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */; };
CC33330033333300CCCCCCCC /* MoodLoggingWithDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */; };
DD44440044444400DDDDDDDD /* EntryDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD44444444444444DDDDDDDD /* EntryDetailTests.swift */; };
EE55550055555500EEEEEEEE /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE55555555555555EEEEEEEE /* SettingsTests.swift */; };
FF66660066666600FFFFFFFF /* SecondaryTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */; };
A1B2C3D400000000C9D0E1F2 /* NoteEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */; };
B2C3D4E500000000D0E1F2A3 /* CustomizeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */; };
C3D4E500000000E1F2A3B4C5 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */; };
D4E5F6A700000000F2A3B4C5 /* MoodReplacementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */; };
E5F6A7B800000000A3B4C5D6 /* EmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */; };
F6A7B8C900000000B4C5D6E7 /* EntryDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */; };
A7B8C9D000000000C5D6E7F8 /* NotesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */; };
F75470AA2BA1E9EFF8F5265A /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC4C498A1185DC831F4593 /* LocalizationTests.swift */; };
E3482DB0421C12E11517BDC8 /* TrialBannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CD463209E0909393545D62 /* TrialBannerTests.swift */; };
A4B459F8CE7F5534DE4FADCA /* DarkModeStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */; };
1AB245144C89927264D16645 /* InsightsEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */; };
756B9857B0657D2DB2D6D4E2 /* AppResumeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */; };
6F9C9C4B50CF8C1769171FF9 /* NoteEditTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469470483072085BE9E04E12 /* NoteEditTests.swift */; };
B8C9D0E100000000D6E7F8A9 /* MonthViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */; };
C9D0E1F200000000E7F8A9B0 /* SettingsActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */; };
D0E1F2A300000000F8A9B0C1 /* CustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */; };
E1F2A3B400000000A9B0C1D2 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */; };
F2A3B400000000B0C1D2E3F4 /* StabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */; };
756B9857B0657D2DB2D6D4E2 /* AppResumeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */; };
92C1523E0398F866DB4CA027 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881CA8B21231D67DED575502 /* SettingsScreen.swift */; };
9559409B5AEEAB40EBCB6AF9 /* VoteLogicsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */; };
A018FE95582C04ED0F1806DC /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */; };
A1B2C3D400000000C9D0E1F2 /* NoteEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */; };
A1B2C3D4E5F607080910ABCD /* DayViewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */; };
A371ED1B0784315F96FFC6BD /* EntryDetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */; };
A3B4C5D600000000C1D2E3F4 /* DataPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */; };
B4C5D6E700000000D2E3F4A5 /* PaywallGateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */; };
C5D6E7F800000000E3F4A5B6 /* AppThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */; };
D6E7F8A900000000F4A5B6C7 /* IconPackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */; };
E7F8A9B000000000A5B6C7D8 /* PremiumCustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */; };
F8A9B0C100000000B6C7D8E9 /* HeaderMoodLoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */; };
A4B459F8CE7F5534DE4FADCA /* DarkModeStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */; };
A7B8C9D000000000C5D6E7F8 /* NotesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */; };
A9B0C1D200000000C7D8E9FA /* DayViewGroupingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */; };
AA11110011111100AAAAAAAA /* AppLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11111111111111AAAAAAAA /* AppLaunchTests.swift */; };
B0C1D2E300000000D8E9FA0B /* AllDayViewStylesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */; };
B2C3D4E500000000D0E1F2A3 /* CustomizeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */; };
B4C5D6E700000000D2E3F4A5 /* PaywallGateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */; };
B8C9D0E100000000D6E7F8A9 /* MonthViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */; };
BB22220022222200BBBBBBBB /* MoodLoggingEmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */; };
C1D2E3F400000000E9FA0B1C /* MonthViewInteractionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */; };
C26D40397E1AA24816FB3751 /* TabBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */; };
C3D4E500000000E1F2A3B4C5 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */; };
C5D6E7F800000000E3F4A5B6 /* AppThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */; };
C9D0E1F200000000E7F8A9B0 /* SettingsActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */; };
CC33330033333300CCCCCCCC /* MoodLoggingWithDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */; };
D0E1F2A300000000F8A9B0C1 /* CustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */; };
D1AD0A0469EADFB1446E9B09 /* YearViewDisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0246E9F406F872E5DEEB7269 /* YearViewDisplayTests.swift */; };
D4E5F6A700000000F2A3B4C5 /* MoodReplacementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */; };
D6E7F8A900000000F4A5B6C7 /* IconPackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */; };
DD44440044444400DDDDDDDD /* EntryDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD44444444444444DDDDDDDD /* EntryDetailTests.swift */; };
E0579E66FFBBF124AC625ACD /* WaitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */; };
E1F2A3B400000000A9B0C1D2 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */; };
E3482DB0421C12E11517BDC8 /* TrialBannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CD463209E0909393545D62 /* TrialBannerTests.swift */; };
E5F6A7B800000000A3B4C5D6 /* EmptyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */; };
E7F8A9B000000000A5B6C7D8 /* PremiumCustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */; };
EE55550055555500EEEEEEEE /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE55555555555555EEEEEEEE /* SettingsTests.swift */; };
EEB21B1CAA8EAEB497BD9FB3 /* DataControllerCRUDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */; };
F2A3B400000000B0C1D2E3F4 /* StabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */; };
F6A7B8C900000000B4C5D6E7 /* EntryDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */; };
F75470AA2BA1E9EFF8F5265A /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC4C498A1185DC831F4593 /* LocalizationTests.swift */; };
F8A9B0C100000000B6C7D8E9 /* HeaderMoodLoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */; };
FD30D4508D4C61AB10AC1E71 /* SettingsOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFDAD20AE6C6914EDD87DCBC /* SettingsOnboardingTests.swift */; };
FF66660066666600FFFFFFFF /* SecondaryTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -117,7 +119,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Feels/Localizable.xcstrings; sourceTree = "<group>"; };
0246E9F406F872E5DEEB7269 /* YearViewDisplayTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YearViewDisplayTests.swift; sourceTree = "<group>"; };
0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppResumeTests.swift; sourceTree = "<group>"; };
17DC4C498A1185DC831F4593 /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Feels/Localizable.xcstrings; sourceTree = "<group>"; };
1CB4D09F28787D8A00902A56 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.5.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
1CD90AF5278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; };
1CD90AFB278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -139,58 +144,57 @@
1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS)Dev.entitlements"; sourceTree = "<group>"; };
1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; };
1E594AEAB5F046E3B3ED7C47 /* Feels Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Feels Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
29E2A2FC314F88244CA946BF /* StreakTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StreakTests.swift; sourceTree = "<group>"; };
5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataControllerCRUDTests.swift; sourceTree = "<group>"; };
9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DayViewViewModelTests.swift; sourceTree = "<group>"; };
B60015D02A064FF582E232FD /* Feels Watch App/Feels Watch AppDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch AppDebug.entitlements"; sourceTree = "<group>"; };
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App/Feels Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch App.entitlements"; sourceTree = "<group>"; };
DA0D74ACDD741CFA1F14F50F /* FeelsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeelsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VoteLogicsTests.swift; sourceTree = "<group>"; };
F4D304CD05CC7C662CCD7DCB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitHelpers.swift; sourceTree = "<group>"; };
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScreen.swift; sourceTree = "<group>"; };
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayScreen.swift; sourceTree = "<group>"; };
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailScreen.swift; sourceTree = "<group>"; };
881CA8B21231D67DED575502 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
AA11111111111111AAAAAAAA /* AppLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchTests.swift; sourceTree = "<group>"; };
BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingEmptyStateTests.swift; sourceTree = "<group>"; };
CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingWithDataTests.swift; sourceTree = "<group>"; };
DD44444444444444DDDDDDDD /* EntryDetailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailTests.swift; sourceTree = "<group>"; };
EE55555555555555EEEEEEEE /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryTabTests.swift; sourceTree = "<group>"; };
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditorScreen.swift; sourceTree = "<group>"; };
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeScreen.swift; sourceTree = "<group>"; };
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodReplacementTests.swift; sourceTree = "<group>"; };
E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateTests.swift; sourceTree = "<group>"; };
F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDeleteTests.swift; sourceTree = "<group>"; };
A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTests.swift; sourceTree = "<group>"; };
17DC4C498A1185DC831F4593 /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
21CD463209E0909393545D62 /* TrialBannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrialBannerTests.swift; sourceTree = "<group>"; };
8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeStylesTests.swift; sourceTree = "<group>"; };
A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsEmptyStateTests.swift; sourceTree = "<group>"; };
0359E1D32D936859E5A0C9F3 /* AppResumeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppResumeTests.swift; sourceTree = "<group>"; };
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
29E2A2FC314F88244CA946BF /* StreakTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StreakTests.swift; sourceTree = "<group>"; };
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayScreen.swift; sourceTree = "<group>"; };
469470483072085BE9E04E12 /* NoteEditTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditTests.swift; sourceTree = "<group>"; };
B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewTests.swift; sourceTree = "<group>"; };
C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsActionTests.swift; sourceTree = "<group>"; };
D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizationTests.swift; sourceTree = "<group>"; };
E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; };
F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StabilityTests.swift; sourceTree = "<group>"; };
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitHelpers.swift; sourceTree = "<group>"; };
5566271983AEDF1D33C34FE6 /* DataControllerCRUDTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataControllerCRUDTests.swift; sourceTree = "<group>"; };
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailScreen.swift; sourceTree = "<group>"; };
8114D2CE12EC5392371BB415 /* DarkModeStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeStylesTests.swift; sourceTree = "<group>"; };
881CA8B21231D67DED575502 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
9CFAE86F485C853DB3239DD9 /* IntegrationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteEditorScreen.swift; sourceTree = "<group>"; };
A3B4C5D6E7F8A9B0C1D2E3F4 /* DataPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPersistenceTests.swift; sourceTree = "<group>"; };
B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallGateTests.swift; sourceTree = "<group>"; };
C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppThemeTests.swift; sourceTree = "<group>"; };
D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPackTests.swift; sourceTree = "<group>"; };
E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumCustomizationTests.swift; sourceTree = "<group>"; };
F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderMoodLoggingTests.swift; sourceTree = "<group>"; };
A6988985985DE9C29CFDFA96 /* InsightsEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsEmptyStateTests.swift; sourceTree = "<group>"; };
A7B8C9D0E1F2A3B4C5D6E7F8 /* NotesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTests.swift; sourceTree = "<group>"; };
A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayViewGroupingTests.swift; sourceTree = "<group>"; };
AA11111111111111AAAAAAAA /* AppLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchTests.swift; sourceTree = "<group>"; };
B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDayViewStylesTests.swift; sourceTree = "<group>"; };
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeScreen.swift; sourceTree = "<group>"; };
B4C5D6E7F8A9B0C1D2E3F4A5 /* PaywallGateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallGateTests.swift; sourceTree = "<group>"; };
B60015D02A064FF582E232FD /* Feels Watch AppDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch AppDebug.entitlements"; sourceTree = "<group>"; };
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels Watch App/Feels Watch App.entitlements"; sourceTree = "<group>"; };
B8C9D0E1F2A3B4C5D6E7F8A9 /* MonthViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewTests.swift; sourceTree = "<group>"; };
BB22222222222222BBBBBBBB /* MoodLoggingEmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingEmptyStateTests.swift; sourceTree = "<group>"; };
C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthViewInteractionTests.swift; sourceTree = "<group>"; };
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
C5D6E7F8A9B0C1D2E3F4A5B6 /* AppThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppThemeTests.swift; sourceTree = "<group>"; };
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScreen.swift; sourceTree = "<group>"; };
C9D0E1F2A3B4C5D6E7F8A9B0 /* SettingsActionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsActionTests.swift; sourceTree = "<group>"; };
CC33333333333333CCCCCCCC /* MoodLoggingWithDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodLoggingWithDataTests.swift; sourceTree = "<group>"; };
D0E1F2A3B4C5D6E7F8A9B0C1 /* CustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizationTests.swift; sourceTree = "<group>"; };
D4E5F60708091011ABCDE001 /* DayViewViewModelTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DayViewViewModelTests.swift; sourceTree = "<group>"; };
D4E5F6A7B8C9D0E1F2A3B4C5 /* MoodReplacementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoodReplacementTests.swift; sourceTree = "<group>"; };
D6E7F8A9B0C1D2E3F4A5B6C7 /* IconPackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPackTests.swift; sourceTree = "<group>"; };
DA0D74ACDD741CFA1F14F50F /* FeelsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeelsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DD44444444444444DDDDDDDD /* EntryDetailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDetailTests.swift; sourceTree = "<group>"; };
DD717F91BD65382B7DDFE3C4 /* VoteLogicsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VoteLogicsTests.swift; sourceTree = "<group>"; };
DFDAD20AE6C6914EDD87DCBC /* SettingsOnboardingTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsOnboardingTests.swift; sourceTree = "<group>"; };
E1F2A3B4C5D6E7F8A9B0C1D2 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; };
E5F6A7B8C9D0E1F2A3B4C5D6 /* EmptyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateTests.swift; sourceTree = "<group>"; };
E7F8A9B0C1D2E3F4A5B6C7D8 /* PremiumCustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumCustomizationTests.swift; sourceTree = "<group>"; };
EE55555555555555EEEEEEEE /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
F2A3B4C5D6E7F8A9B0C1D2E3 /* StabilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StabilityTests.swift; sourceTree = "<group>"; };
F4D304CD05CC7C662CCD7DCB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
F6A7B8C9D0E1F2A3B4C5D6E7 /* EntryDeleteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryDeleteTests.swift; sourceTree = "<group>"; };
F8A9B0C1D2E3F4A5B6C7D8E9 /* HeaderMoodLoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderMoodLoggingTests.swift; sourceTree = "<group>"; };
FF66666666666666FFFFFFFF /* SecondaryTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryTabTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
1C000C162EE93AE3009C9ED5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
1C000C162EE93AE3009C9ED5 /* Exceptions for "Shared" folder in "FeelsWidgetExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
AccessibilityIdentifiers.swift,
@@ -221,7 +225,7 @@
);
target = 1CD90B44278C7E7A001C4FEA /* FeelsWidgetExtension */;
};
2166CE8AA7264FC2B4BFAAAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
2166CE8AA7264FC2B4BFAAAC /* Exceptions for "Shared" folder in "Feels Watch App" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Models/Mood.swift,
@@ -236,9 +240,41 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
1C00073D2EE9388A009C9ED5 /* Shared */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2166CE8AA7264FC2B4BFAAAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 1C000C162EE93AE3009C9ED5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Shared; sourceTree = "<group>"; };
1C0009922EE938FC009C9ED5 /* FeelsWidget2 */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = FeelsWidget2; sourceTree = "<group>"; };
579031D619ED4B989145EEB1 /* Feels Watch App */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Feels Watch App"; sourceTree = "<group>"; };
1C00073D2EE9388A009C9ED5 /* Shared */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
2166CE8AA7264FC2B4BFAAAC /* Exceptions for "Shared" folder in "Feels Watch App" target */,
1C000C162EE93AE3009C9ED5 /* Exceptions for "Shared" folder in "FeelsWidgetExtension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = Shared;
sourceTree = "<group>";
};
1C0009922EE938FC009C9ED5 /* FeelsWidget2 */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = FeelsWidget2;
sourceTree = "<group>";
};
579031D619ED4B989145EEB1 /* Feels Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = "Feels Watch App";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -306,9 +342,9 @@
1CD90AE5278C7DDF001C4FEA = {
isa = PBXGroup;
children = (
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App/Feels Watch App.entitlements */,
B60015D02A064FF582E232FD /* Feels Watch App/Feels Watch AppDebug.entitlements */,
1C0DAB50279DB0FB003B1F21 /* Feels/Localizable.xcstrings */,
B8AB4CD73C2B4DC89C6FE84D /* Feels Watch App.entitlements */,
B60015D02A064FF582E232FD /* Feels Watch AppDebug.entitlements */,
1C0DAB50279DB0FB003B1F21 /* Localizable.xcstrings */,
1CDEFBBE2F3B8736006AE6A1 /* Configuration.storekit */,
1CD90B6A278C7F75001C4FEA /* Feels (iOS).entitlements */,
1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */,
@@ -386,33 +422,12 @@
A9B0C1D2E3F4A5B6C7D8E9FA /* DayViewGroupingTests.swift */,
B0C1D2E3F4A5B6C7D8E9FA0B /* AllDayViewStylesTests.swift */,
C1D2E3F4A5B6C7D8E9FA0B1C /* MonthViewInteractionTests.swift */,
0246E9F406F872E5DEEB7269 /* YearViewDisplayTests.swift */,
DFDAD20AE6C6914EDD87DCBC /* SettingsOnboardingTests.swift */,
);
path = "Tests iOS";
sourceTree = "<group>";
};
3A62ED77167DA212DE1CCB7D /* Helpers */ = {
isa = PBXGroup;
children = (
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */,
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
B697A2092711045D69109EA1 /* Screens */ = {
isa = PBXGroup;
children = (
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */,
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */,
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */,
881CA8B21231D67DED575502 /* SettingsScreen.swift */,
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */,
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */,
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */,
);
path = Screens;
sourceTree = "<group>";
};
1CD90B11278C7DE0001C4FEA /* Tests macOS */ = {
isa = PBXGroup;
children = (
@@ -446,6 +461,15 @@
path = FeelsTests;
sourceTree = "<group>";
};
3A62ED77167DA212DE1CCB7D /* Helpers */ = {
isa = PBXGroup;
children = (
29CE4110A0D8FBBAD7F92BDF /* BaseUITestCase.swift */,
5354C23DD5FC67C1C97482F2 /* WaitHelpers.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
88F4C25CA0D11FB136B0B8A6 /* iOS */ = {
isa = PBXGroup;
children = (
@@ -454,6 +478,20 @@
name = iOS;
sourceTree = "<group>";
};
B697A2092711045D69109EA1 /* Screens */ = {
isa = PBXGroup;
children = (
C7CDDCB9C85BAE71C679C0BF /* TabBarScreen.swift */,
427CD9C91D43AB6A0302B4DD /* DayScreen.swift */,
7E35564DEA72EB6F8447CDAA /* EntryDetailScreen.swift */,
881CA8B21231D67DED575502 /* SettingsScreen.swift */,
A1B2C3D4E5F6A7B8C9D0E1F2 /* NoteEditorScreen.swift */,
B2C3D4E5F6A7B8C9D0E1F2A3 /* CustomizeScreen.swift */,
C3D4E5F6A7B8C9D0E1F2A3B4 /* OnboardingScreen.swift */,
);
path = Screens;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -676,7 +714,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1C0DAB51279DB0FB003B1F21 /* Feels/Localizable.xcstrings in Resources */,
1C0DAB51279DB0FB003B1F21 /* Localizable.xcstrings in Resources */,
1CDEFBBF2F3B8736006AE6A1 /* Configuration.storekit in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -707,7 +745,7 @@
buildActionMask = 2147483647;
files = (
1CDEFBC02F3B8736006AE6A1 /* Configuration.storekit in Resources */,
1C0DAB52279DB0FB003B1F22 /* Feels/Localizable.xcstrings in Resources */,
1C0DAB52279DB0FB003B1F22 /* Localizable.xcstrings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -787,6 +825,8 @@
A9B0C1D200000000C7D8E9FA /* DayViewGroupingTests.swift in Sources */,
B0C1D2E300000000D8E9FA0B /* AllDayViewStylesTests.swift in Sources */,
C1D2E3F400000000E9FA0B1C /* MonthViewInteractionTests.swift in Sources */,
D1AD0A0469EADFB1446E9B09 /* YearViewDisplayTests.swift in Sources */,
FD30D4508D4C61AB10AC1E71 /* SettingsOnboardingTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -125,6 +125,11 @@ enum AccessibilityID {
// MARK: - Year View
enum YearView {
static let heatmap = "year_heatmap"
static let donutChart = "year_donut_chart"
static let barChart = "year_bar_chart"
static let statsSection = "year_stats_section"
static func cardHeader(year: Int) -> String { "year_card_header_\(year)" }
static let shareButton = "year_share_button"
}
// MARK: - Onboarding

View File

@@ -25,16 +25,7 @@ struct OnboardingDay: View {
@ObservedObject var onboardingData: OnboardingData
var body: some View {
ZStack {
// Gradient background
LinearGradient(
colors: [Color(hex: "4facfe"), Color(hex: "00f2fe")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 0) {
VStack(spacing: 0) {
Spacer()
// Icon
@@ -103,8 +94,15 @@ struct OnboardingDay: View {
}
.padding(.horizontal, 30)
.padding(.bottom, 80)
}
}
.background(
LinearGradient(
colors: [Color(hex: "4facfe"), Color(hex: "00f2fe")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
)
.accessibilityIdentifier(AccessibilityID.Onboarding.dayScreen)
}
}

View File

@@ -12,17 +12,7 @@ struct OnboardingStyle: View {
@State private var selectedTheme: AppTheme = .celestial
var body: some View {
ZStack {
// Gradient background that updates with theme
LinearGradient(
colors: selectedTheme.previewColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 0.4), value: selectedTheme)
ScrollView(showsIndicators: false) {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
// Icon
ZStack {
@@ -85,8 +75,16 @@ struct OnboardingStyle: View {
.padding(.top, 24)
.padding(.bottom, 80)
}
}
}
.background(
LinearGradient(
colors: selectedTheme.previewColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 0.4), value: selectedTheme)
)
.onAppear {
// Apply default theme on appear
selectedTheme.apply()

View File

@@ -15,16 +15,7 @@ struct OnboardingSubscription: View {
let completionClosure: ((OnboardingData) -> Void)
var body: some View {
ZStack {
// Gradient background
LinearGradient(
colors: [Color(hex: "11998e"), Color(hex: "38ef7d")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 0) {
VStack(spacing: 0) {
Spacer()
// Crown icon
@@ -137,8 +128,15 @@ struct OnboardingSubscription: View {
}
.padding(.horizontal, 24)
.padding(.bottom, 50)
}
}
.background(
LinearGradient(
colors: [Color(hex: "11998e"), Color(hex: "38ef7d")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
)
.accessibilityIdentifier(AccessibilityID.Onboarding.subscriptionScreen)
.sheet(isPresented: $showSubscriptionStore, onDismiss: {
// After subscription store closes, complete onboarding

View File

@@ -16,44 +16,35 @@ struct OnboardingTitle: View {
@ObservedObject var onboardingData: OnboardingData
var body: some View {
ZStack {
Image("average", bundle: .main)
.foregroundColor(Color(UIColor.darkText))
.opacity(0.04)
.scaleEffect(1.2)
.padding(.bottom, 55)
.accessibilityHidden(true)
ScrollView {
VStack{
Text(String(localized: "onboarding_title_title"))
.font(.title)
.foregroundColor(Color(UIColor.white))
.padding([.trailing, .leading], 55)
.padding([.top], 25)
ForEach(OnboardingTitle.titleOptions, id: \.self) { option in
Button(action: {
ScrollView {
VStack{
Text(String(localized: "onboarding_title_title"))
.font(.title)
.foregroundColor(Color(UIColor.white))
.padding([.trailing, .leading], 55)
.padding([.top], 25)
ForEach(OnboardingTitle.titleOptions, id: \.self) { option in
Button(action: {
// onboardingData.title = option
}, label: {
Text(option)
.font(.subheadline.weight(.bold))
.foregroundColor(.white)
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.white))
.cornerRadius(10)
})
.buttonStyle(PlainButtonStyle())
.padding([.top], 10)
}
Text(String(localized: "onboarding_title_type_your_own"))
.font(.body)
.foregroundColor(Color(UIColor.white))
.padding([.top], 25)
.padding([.trailing, .leading], 55)
}, label: {
Text(option)
.font(.subheadline.weight(.bold))
.foregroundColor(.white)
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.white))
.cornerRadius(10)
})
.buttonStyle(PlainButtonStyle())
.padding([.top], 10)
}
Text(String(localized: "onboarding_title_type_your_own"))
.font(.body)
.foregroundColor(Color(UIColor.white))
.padding([.top], 25)
.padding([.trailing, .leading], 55)
// TextField("Notification", text: $onboardingData.title)
// .frame(height: 44)
// .foregroundColor(Color(UIColor.white))
@@ -63,12 +54,21 @@ struct OnboardingTitle: View {
// .overlay(RoundedRectangle(cornerRadius: 16).stroke(Color.white))
// .padding([.leading, .trailing], 75)
// .padding([.top], 45)
Spacer()
}
Spacer()
}
}
.background {
ZStack {
Color.orange
Image("average", bundle: .main)
.foregroundColor(Color(UIColor.darkText))
.opacity(0.04)
.scaleEffect(1.2)
.padding(.bottom, 55)
.accessibilityHidden(true)
}
}
.background(.orange)
}
}

View File

@@ -19,8 +19,56 @@ struct OnboardingWrapup: View {
}
var body: some View {
ZStack {
GeometryReader { geometry in
GeometryReader { geometry in
VStack {
ScrollView {
Spacer()
Text(String(localized: "onboarding_wrap_up_1"))
.padding(.top)
.padding()
.font(.title)
.foregroundColor(Color(UIColor.white))
Text(formatter.string(from: onboardingData.date))
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Text(String(localized: "onboarding_wrap_up_3"))
.font(.title)
.padding()
.foregroundColor(Color(UIColor.white))
Text(onboardingData.inputDay.localizedValue)
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Button(action: {
AnalyticsManager.shared.track(.onboardingCompleted(dayId: String(onboardingData.inputDay.rawValue)))
completionClosure(onboardingData)
}, label: {
Text(String(localized: "onboarding_wrap_up_complete_button"))
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color(hex: "31d158"))
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(Color.white))
.cornerRadius(10)
})
.padding([.top], 65)
}
.multilineTextAlignment(.center)
}
.frame(maxWidth: geometry.size.width)
}
.background {
ZStack {
Color(hex: "31d158")
VStack {
Spacer()
Image("great", bundle: .main)
@@ -30,55 +78,8 @@ struct OnboardingWrapup: View {
.accessibilityHidden(true)
Spacer()
}
VStack {
ScrollView {
Spacer()
Text(String(localized: "onboarding_wrap_up_1"))
.padding(.top)
.padding()
.font(.title)
.foregroundColor(Color(UIColor.white))
Text(formatter.string(from: onboardingData.date))
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Text(String(localized: "onboarding_wrap_up_3"))
.font(.title)
.padding()
.foregroundColor(Color(UIColor.white))
Text(onboardingData.inputDay.localizedValue)
.font(.title)
.fontWeight(.bold)
.padding()
.foregroundColor(Color(UIColor.white))
Button(action: {
AnalyticsManager.shared.track(.onboardingCompleted(dayId: String(onboardingData.inputDay.rawValue)))
completionClosure(onboardingData)
}, label: {
Text(String(localized: "onboarding_wrap_up_complete_button"))
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color(hex: "31d158"))
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(Color.white))
.cornerRadius(10)
})
.padding([.top], 65)
}
.multilineTextAlignment(.center)
}
.frame(maxWidth: geometry.size.width)
}
}
.background(Color(hex: "31d158"))
}
}

View File

@@ -41,8 +41,6 @@ struct AddMoodHeaderView: View {
Text(String(imagePack.rawValue))
.hidden()
theme.currentTheme.secondaryBGColor
VStack(spacing: 16) {
Text(ShowBasedOnVoteLogics.getVotingTitle(onboardingData: onboardingData))
.font(.title2.bold())
@@ -66,6 +64,7 @@ struct AddMoodHeaderView: View {
}
}
}
.background(theme.currentTheme.secondaryBGColor)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
.fixedSize(horizontal: false, vertical: true)
.accessibilityIdentifier(AccessibilityID.DayView.moodHeader)

View File

@@ -24,17 +24,17 @@ struct CustomizeContentView: View {
Button(action: { showThemePicker = true }) {
HStack(spacing: 16) {
// Emoji preview
ZStack {
LinearGradient(
colors: [.purple.opacity(0.8), .blue.opacity(0.8), .cyan.opacity(0.8)],
startPoint: .topLeading,
endPoint: .bottomTrailing
Text("🎨")
.font(.title)
.frame(width: 56, height: 56)
.background(
LinearGradient(
colors: [.purple.opacity(0.8), .blue.opacity(0.8), .cyan.opacity(0.8)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
Text("🎨")
.font(.title)
}
.frame(width: 56, height: 56)
.clipShape(RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: 12))
VStack(alignment: .leading, spacing: 4) {
Text("Browse Themes")

View File

@@ -259,26 +259,25 @@ struct AppThemePreviewSheet: View {
}
private var heroSection: some View {
ZStack {
// Gradient background
VStack(spacing: 16) {
Text(theme.emoji)
.font(.system(size: 72))
.shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
Text(theme.tagline)
.font(.title3.weight(.medium))
.foregroundColor(.white)
.shadow(color: .black.opacity(0.3), radius: 4, x: 0, y: 2)
}
.frame(maxWidth: .infinity)
.frame(height: 200)
.background(
LinearGradient(
colors: theme.previewColors + [theme.previewColors[0].opacity(0.5)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
VStack(spacing: 16) {
Text(theme.emoji)
.font(.system(size: 72))
.shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
Text(theme.tagline)
.font(.title3.weight(.medium))
.foregroundColor(.white)
.shadow(color: .black.opacity(0.3), radius: 4, x: 0, y: 2)
}
}
.frame(height: 200)
)
.clipShape(RoundedRectangle(cornerRadius: 20))
.padding(.horizontal, 20)
.padding(.top, 16)

View File

@@ -22,40 +22,37 @@ struct DayFilterPickerView: View {
(Calendar.current.shortWeekdaySymbols[6], 7)]
var body: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
HStack {
ForEach(weekdays.indices, id: \.self) { dayIdx in
let day = String(weekdays[dayIdx].0)
let value = weekdays[dayIdx].1
let isSelected = filteredDays.currentFilters.contains(value)
Button(action: {
if isSelected {
filteredDays.removeFilter(filter: value)
} else {
filteredDays.addFilter(newFilter: value)
}
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
}) {
Text(day.capitalized)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color(uiColor: .tertiarySystemBackground))
.foregroundColor(isSelected ? .green : .red)
.cornerRadius(8)
VStack {
HStack {
ForEach(weekdays.indices, id: \.self) { dayIdx in
let day = String(weekdays[dayIdx].0)
let value = weekdays[dayIdx].1
let isSelected = filteredDays.currentFilters.contains(value)
Button(action: {
if isSelected {
filteredDays.removeFilter(filter: value)
} else {
filteredDays.addFilter(newFilter: value)
}
.buttonStyle(.plain)
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
}) {
Text(day.capitalized)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color(uiColor: .tertiarySystemBackground))
.foregroundColor(isSelected ? .green : .red)
.cornerRadius(8)
}
.buttonStyle(.plain)
}
Text(String(localized: "day_picker_view_text"))
.padding(.top)
.foregroundColor(textColor)
}
.padding()
Text(String(localized: "day_picker_view_text"))
.padding(.top)
.foregroundColor(textColor)
}
.padding()
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}

View File

@@ -50,47 +50,45 @@ struct IconPickerView: View {
]
var body: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
ScrollView(.horizontal) {
HStack {
VStack {
ScrollView(.horizontal) {
HStack {
Button(action: {
UIApplication.shared.setAlternateIconName(nil)
AnalyticsManager.shared.track(.appIconChanged(iconTitle: "default"))
}, label: {
Image("AppIconImage", bundle: .main)
.resizable()
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "Default app icon"))
.accessibilityHint(String(localized: "Double tap to select"))
ForEach(iconSets, id: \.self.0){ iconSet in
Button(action: {
UIApplication.shared.setAlternateIconName(nil)
AnalyticsManager.shared.track(.appIconChanged(iconTitle: "default"))
UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
// FIXME: Handle error
}
AnalyticsManager.shared.track(.appIconChanged(iconTitle: iconSet.1))
}, label: {
Image("AppIconImage", bundle: .main)
Image(iconSet.0, bundle: .main)
.resizable()
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "Default app icon"))
.accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))"))
.accessibilityHint(String(localized: "Double tap to select"))
ForEach(iconSets, id: \.self.0){ iconSet in
Button(action: {
UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
// FIXME: Handle error
}
AnalyticsManager.shared.track(.appIconChanged(iconTitle: iconSet.1))
}, label: {
Image(iconSet.0, bundle: .main)
.resizable()
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))"))
.accessibilityHint(String(localized: "Double tap to select"))
}
}
.padding()
}
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.bgColor))
.padding()
.cornerRadius(10)
}
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.bgColor))
.padding()
.cornerRadius(10)
}
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}

View File

@@ -16,58 +16,56 @@ struct PersonalityPackPickerView: View {
private var textColor: Color { theme.currentTheme.labelColor }
var body: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
ForEach(PersonalityPack.allCases, id: \.self) { aPack in
VStack(spacing: 10) {
Text(String(aPack.title()))
.font(.body)
.foregroundColor(textColor)
Text(aPack.randomPushNotificationStrings().title)
.font(.body)
.foregroundColor(Color(UIColor.systemGray))
Text(aPack.randomPushNotificationStrings().body)
.font(.body)
.foregroundColor(Color(UIColor.systemGray))
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.contentShape(Rectangle())
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear)
.padding(5)
)
.onTapGesture {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
personalityPack = aPack
AnalyticsManager.shared.track(.personalityPackChanged(packTitle: aPack.title()))
LocalNotification.rescheduleNotifiations()
// }
}
// .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0)
.alert(isPresented: $showOver18Alert) {
let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) {
showNSFW = true
}
let secondaryButton = Alert.Button.cancel(Text(String(localized: "customize_view_over18alert_no"))) {
showNSFW = false
}
return Alert(title: Text(String(localized: "customize_view_over18alert_title")),
message: Text(String(localized: "customize_view_over18alert_body")),
primaryButton: primaryButton,
secondaryButton: secondaryButton)
}
if aPack.rawValue != (PersonalityPack.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 {
Divider()
}
VStack {
ForEach(PersonalityPack.allCases, id: \.self) { aPack in
VStack(spacing: 10) {
Text(String(aPack.title()))
.font(.body)
.foregroundColor(textColor)
Text(aPack.randomPushNotificationStrings().title)
.font(.body)
.foregroundColor(Color(UIColor.systemGray))
Text(aPack.randomPushNotificationStrings().body)
.font(.body)
.foregroundColor(Color(UIColor.systemGray))
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.contentShape(Rectangle())
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear)
.padding(5)
)
.onTapGesture {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
personalityPack = aPack
AnalyticsManager.shared.track(.personalityPackChanged(packTitle: aPack.title()))
LocalNotification.rescheduleNotifiations()
// }
}
// .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0)
.alert(isPresented: $showOver18Alert) {
let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) {
showNSFW = true
}
let secondaryButton = Alert.Button.cancel(Text(String(localized: "customize_view_over18alert_no"))) {
showNSFW = false
}
return Alert(title: Text(String(localized: "customize_view_over18alert_title")),
message: Text(String(localized: "customize_view_over18alert_body")),
primaryButton: primaryButton,
secondaryButton: secondaryButton)
}
if aPack.rawValue != (PersonalityPack.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 {
Divider()
}
}
}
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}

View File

@@ -15,19 +15,17 @@ struct ThemePickerView: View {
private var textColor: Color { selectedTheme.currentTheme.labelColor }
var body: some View {
ZStack {
selectedTheme.currentTheme.secondaryBGColor
VStack {
HStack(spacing: 0) {
themeButton(for: .system)
themeButton(for: .iFeel)
themeButton(for: .dark)
themeButton(for: .light)
}
.padding(.top)
VStack {
HStack(spacing: 0) {
themeButton(for: .system)
themeButton(for: .iFeel)
themeButton(for: .dark)
themeButton(for: .light)
}
.padding()
.padding(.top)
}
.padding()
.background(selectedTheme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
.onAppear {

View File

@@ -18,57 +18,54 @@ struct VotingLayoutPickerView: View {
}
var body: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack(alignment: .leading, spacing: 12) {
Text("Voting Layout")
.font(.headline)
.foregroundColor(textColor)
.padding(.horizontal)
.padding(.top)
VStack(alignment: .leading, spacing: 12) {
Text("Voting Layout")
.font(.headline)
.foregroundColor(textColor)
.padding(.horizontal)
.padding(.top)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: {
if UIAccessibility.isReduceMotionEnabled {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: {
if UIAccessibility.isReduceMotionEnabled {
votingLayoutStyle = layout.rawValue
} else {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
} else {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
}
}
AnalyticsManager.shared.track(.votingLayoutChanged(layout: layout.displayName))
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)
.frame(width: 44, height: 44)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6))
Text(layout.displayName)
.font(.caption)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8))
}
.frame(width: 70)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2)
)
}
.buttonStyle(.plain)
AnalyticsManager.shared.track(.votingLayoutChanged(layout: layout.displayName))
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)
.frame(width: 44, height: 44)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6))
Text(layout.displayName)
.font(.caption)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8))
}
.frame(width: 70)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2)
)
}
.buttonStyle(.plain)
}
.padding(.horizontal)
}
.padding(.bottom)
.padding(.horizontal)
}
.padding(.bottom)
}
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}

File diff suppressed because it is too large Load Diff

View File

@@ -522,6 +522,7 @@ struct YearCard: View, Equatable {
}
}
.buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.YearView.cardHeader(year: year))
Spacer()
@@ -533,6 +534,7 @@ struct YearCard: View, Equatable {
.foregroundColor(textColor.opacity(0.6))
}
.buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.YearView.shareButton)
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
@@ -543,6 +545,7 @@ struct YearCard: View, Equatable {
// Donut Chart
MoodDonutChart(metrics: animatedMetrics, moodTint: moodTint)
.frame(width: 100, height: 100)
.accessibilityIdentifier(AccessibilityID.YearView.donutChart)
// Bar Chart
VStack(spacing: 6) {
@@ -574,10 +577,12 @@ struct YearCard: View, Equatable {
}
}
.frame(maxWidth: .infinity)
.accessibilityIdentifier(AccessibilityID.YearView.barChart)
}
.padding(.horizontal, 16)
.padding(.bottom, 12)
.transition(.opacity.combined(with: .move(edge: .top)))
.accessibilityIdentifier(AccessibilityID.YearView.statsSection)
}
Divider()

View File

@@ -82,6 +82,14 @@ enum UITestID {
static let header = "insights_header"
}
enum Year {
static let donutChart = "year_donut_chart"
static let barChart = "year_bar_chart"
static let statsSection = "year_stats_section"
static func cardHeader(year: Int) -> String { "year_card_header_\(year)" }
static let shareButton = "year_share_button"
}
enum Month {
static let grid = "month_grid"
}

View File

@@ -0,0 +1,48 @@
//
// SettingsOnboardingTests.swift
// Tests iOS
//
// TC-124: Show onboarding from Settings.
//
import XCTest
final class SettingsOnboardingTests: BaseUITestCase {
override var seedFixture: String? { "empty" }
override var bypassSubscription: Bool { true }
/// TC-124: Tapping Show Onboarding in Settings replays the onboarding flow.
func testSettings_ShowOnboarding_OpensOnboardingFlow() {
let tabBar = TabBarScreen(app: app)
let settingsScreen = tabBar.tapSettings()
settingsScreen.assertVisible()
// Switch to the Settings sub-tab (not Customize)
settingsScreen.tapSettingsTab()
captureScreenshot(name: "settings_before_show_onboarding")
// Scroll to and tap "Show Onboarding" button
let showOnboardingBtn = app.element("settings_show_onboarding")
guard showOnboardingBtn.waitForExistence(timeout: 2) ||
app.swipeUntilExists(showOnboardingBtn, direction: .up, maxSwipes: 8) else {
captureScreenshot(name: "settings_show_onboarding_not_found")
XCTFail("Show Onboarding button not found in Settings")
return
}
showOnboardingBtn.tapWhenReady()
// The sheet may take a moment to animate in.
// Look for the onboarding welcome screen or any text unique to onboarding.
let welcomeScreen = app.element(UITestID.Onboarding.welcome)
let welcomeText = app.staticTexts["Welcome to Feels"]
let found = welcomeScreen.waitForExistence(timeout: 8) ||
welcomeText.waitForExistence(timeout: 3)
captureScreenshot(name: "settings_show_onboarding_result")
XCTAssertTrue(found,
"Onboarding should appear after tapping Show Onboarding"
)
}
}

View File

@@ -0,0 +1,51 @@
//
// YearViewDisplayTests.swift
// Tests iOS
//
// Year View display tests: donut chart, bar chart.
// TC-035, TC-036
//
import XCTest
final class YearViewDisplayTests: BaseUITestCase {
override var seedFixture: String? { "week_of_moods" }
override var bypassSubscription: Bool { true }
/// TC-035: Year View shows donut chart with mood distribution.
func testYearView_DonutChartVisible() {
let tabBar = TabBarScreen(app: app)
tabBar.tapYear()
// Wait for Year tab to be selected and content to load
XCTAssertTrue(tabBar.yearTab.isSelected, "Year tab should be selected")
captureScreenshot(name: "year_view_loaded")
// The donut chart is inside the stats section of the first YearCard.
// Try finding by accessibility identifier first, then fall back to presence of "days" text.
let donutChart = app.element(UITestID.Year.donutChart)
let found = donutChart.waitForExistence(timeout: 8) ||
app.swipeUntilExists(donutChart, direction: .up, maxSwipes: 3, timeoutPerTry: 1.0)
captureScreenshot(name: "year_donut_chart")
XCTAssertTrue(found, "Donut chart should be visible in Year View")
}
/// TC-036: Year View shows bar chart with mood percentages.
func testYearView_BarChartVisible() {
let tabBar = TabBarScreen(app: app)
tabBar.tapYear()
XCTAssertTrue(tabBar.yearTab.isSelected, "Year tab should be selected")
let barChart = app.element(UITestID.Year.barChart)
let found = barChart.waitForExistence(timeout: 8) ||
app.swipeUntilExists(barChart, direction: .up, maxSwipes: 3, timeoutPerTry: 1.0)
captureScreenshot(name: "year_bar_chart")
XCTAssertTrue(found, "Bar chart should be visible in Year View")
}
}

Binary file not shown.

View File

@@ -1,53 +1,71 @@
# UI Test Prompt Template
# UI Test Prompt Template (QA Plan Driven)
Copy/paste this prompt into Codex or Claude and replace the placeholders.
Copy/paste this into Claude (or Codex), then replace placeholders.
```md
Create an iOS UI test for this behavior:
Task:
Create 3 solid iOS UI tests from the QA plan that compile and run reliably using the existing test architecture.
<DESCRIBE USER FLOW / EXPECTED BEHAVIOR>
Project:
- Root: /Users/treyt/Desktop/code/Feels
- QA source: /Users/treyt/Desktop/code/Feels/docs/Feels_QA_Test_Plan.xlsx
Repository context:
- Project root: /Users/treyt/Desktop/code/Feels
- Follow these files strictly:
- /Users/treyt/Desktop/code/Feels/docs/XCUITest-Authoring.md
- /Users/treyt/Desktop/code/Feels/AGENTS.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/README.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/BaseUITestCase.swift
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/WaitHelpers.swift
- /Users/treyt/Desktop/code/Feels/Shared/AccessibilityIdentifiers.swift
Optional explicit test IDs/names from me (if provided, prioritize these):
<PASTE_TEST_IDS_AND_NAMES_HERE_OR_LEAVE_EMPTY>
Implementation requirements:
1. Use the established pattern:
Mandatory references (read before coding):
- /Users/treyt/Desktop/code/Feels/docs/XCUITest-Authoring.md
- /Users/treyt/Desktop/code/Feels/AGENTS.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/README.md
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/BaseUITestCase.swift
- /Users/treyt/Desktop/code/Feels/Tests iOS/Helpers/WaitHelpers.swift
- /Users/treyt/Desktop/code/Feels/Shared/AccessibilityIdentifiers.swift
- Existing suites in /Users/treyt/Desktop/code/Feels/Tests iOS/ (use them for style/patterns)
Selection rules:
1. If I pasted specific QA IDs/names, use those first.
2. If fewer than 3 are provided, choose remaining from the spreadsheet.
3. Pick the easiest automatable tests (low setup complexity, deterministic UI state, no external dependencies).
4. Skip cases likely to be flaky or blocked (network dependency, unstable animation-only behavior, uncertain app hooks).
5. Briefly justify why each selected test is “easy + stable”.
Implementation rules (do not reinvent):
1. Reuse existing architecture only:
- `BaseUITestCase`
- `UITestID` / accessibility identifier selectors first
- screen objects in `Tests iOS/Screens/`
- wait helpers (`tapWhenReady`, `waitForExistence`, `waitForDisappearance`)
2. Do NOT use `sleep(...)`.
3. Do NOT rely on localized/raw text selectors as primary selectors.
4. If needed, add missing accessibility IDs in app code and wire them into tests.
5. Keep the test deterministic using fixture + launch flags from `BaseUITestCase`.
6. Add screenshots at meaningful checkpoints for triage.
- `UITestID` and accessibility identifiers
- screen objects under `Tests iOS/Screens/`
- wait helpers (`tapWhenReady`, `waitForExistence`, `waitForDisappearance`, etc.)
2. No `sleep(...)`.
3. No raw/localized text selectors as primary locators.
4. Add missing accessibility IDs only when required, then wire them through current helper patterns.
5. Keep tests deterministic with fixtures and launch flags from `BaseUITestCase`.
6. Follow existing naming/style conventions from current passing tests.
7. Add screenshots at meaningful checkpoints for triage.
Test setup choices:
- Suggested suite file: `Tests iOS/<SUITE_NAME>Tests.swift`
- Suggested test method: `test<FEATURE>_<BEHAVIOR>()`
- Fixture to use: `<empty | single_mood | week_of_moods>`
- Launch overrides if needed:
- `skipOnboarding = <true/false>`
- `bypassSubscription = <true/false>`
- `expireTrial = <true/false>`
Flake-resistance checklist (must satisfy):
- Each test has deterministic starting state (fixture + launch args).
- No arbitrary timing waits.
- Assertions target stable identifiers.
- Test does not depend on current date text formatting unless already stabilized by existing helpers.
- If a new selector is needed, add app-side accessibility identifier first.
Validation requirements:
1. Run targeted suite:
- `xcodebuild -project Feels.xcodeproj -scheme "Feels (iOS)" -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -only-testing:"Tests iOS/<SUITE_NAME>" test`
2. Report pass/fail summary.
3. If failures occur, fix and rerun until green.
Deliverable shape:
- Prefer one suite file with 3 test methods, unless existing suite placement is clearly better.
- Method naming: `test<Feature>_<Behavior>()`.
- Keep helper logic in screen objects/helpers instead of duplicating in test body.
Validation gates (required before done):
1. Run only the 3 new tests (not full suite), e.g.:
- `xcodebuild -project Feels.xcodeproj -scheme "Feels (iOS)" -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -only-testing:"Tests iOS/<SuiteName>/testA" -only-testing:"Tests iOS/<SuiteName>/testB" -only-testing:"Tests iOS/<SuiteName>/testC" test`
2. If failures occur, fix and rerun until green.
3. Run the same targeted command a second time to check flakiness.
4. Only mark complete if both runs pass.
Output format:
1. Files changed
2. Why each change was needed
3. Test run summary
4. Any follow-up risks/gaps
1. Selected QA test IDs/names with short reason for selection.
2. Files changed.
3. Key architecture decisions (how existing patterns were reused).
4. Exact test command(s) executed.
5. Run results for pass #1 and pass #2.
6. Any residual risk/gaps.
```