Skip to content

Commit d19eafb

Browse files
committed
Add splash, theme accents, UI polish & assets
Introduce app-wide theming and UI polish: convert ThemeManager to a shared singleton, add AccentTheme support and persistent accent selection, expose accentColor, and update Color/Theme constants (cornerRadius, shadow, animation values). Add new app logo asset and SplashScreenView; show splash on launch and wire it into CycleOneApp. Remove TestDataSeeder test data seeding file. Improve LogViewModel with formattedDate, hasExistingLog flag, deleteLog, guard against saving empty logs, and cleanup/reset logic; also tidy fetch predicates and logging. Add view helpers and animations (fadeSlideIn, gentlePulse), a premiumCard modifier, and multiple UI/UX refinements across calendar, header, legend, day detail, insights, and history list (chips, cards, timeline indicators, improved spacing/shadows/typography). Update PrivacyPolicy HTML with styling, content clarifications, and dark-mode variables. Misc: many small layout/formatting and accessibility tweaks across views.
1 parent 5f990f3 commit d19eafb

35 files changed

Lines changed: 1741 additions & 476 deletions

CycleOne/App/CycleOneApp.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,28 @@ struct CycleOneApp: App {
1212
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
1313
let persistenceController = PersistenceController.shared
1414

15-
@StateObject private var themeManager = ThemeManager()
15+
@StateObject private var themeManager = ThemeManager.shared
16+
@State private var showSplash = true
1617

1718
var body: some Scene {
1819
WindowGroup {
19-
MainTabView()
20-
.environment(\.managedObjectContext, persistenceController.container.viewContext)
21-
.environmentObject(themeManager)
22-
.preferredColorScheme(themeManager.selectedTheme.colorScheme)
23-
.onAppear {
24-
TestDataSeeder.seed(context: persistenceController.container.viewContext)
20+
ZStack {
21+
MainTabView()
22+
.environment(\.managedObjectContext, persistenceController.container.viewContext)
23+
.environmentObject(themeManager)
24+
.preferredColorScheme(themeManager.selectedTheme.colorScheme)
25+
.opacity(showSplash ? 0 : 1)
26+
27+
if showSplash {
28+
SplashScreenView {
29+
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
30+
showSplash = false
31+
}
32+
}
33+
.environmentObject(themeManager)
34+
.transition(.opacity)
2535
}
36+
}
2637
}
2738
}
2839
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "applogo.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"scale" : "3x"
15+
}
16+
],
17+
"info" : {
18+
"author" : "xcode",
19+
"version" : 1
20+
}
21+
}
1.22 MB
Loading

CycleOne/Core/Extensions/Color+Theme.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@
22
// Color+Theme.swift
33
// CycleOne
44
//
5-
// Created by Antigravity on 3/23/26.
6-
//
75

86
import SwiftUI
97

108
extension Color {
11-
static let themePeriod = Color.pink
12-
static let themeFertile = Color.purple
13-
static let themeOvulation = Color.purple.opacity(0.8)
14-
static let themeAccent = Color.pink
9+
static let themePeriod = Color(red: 1.0, green: 0.42, blue: 0.62)
10+
static let themeFertile = Color(red: 0.69, green: 0.53, blue: 0.87)
11+
static let themeOvulation = Color(red: 0.58, green: 0.42, blue: 0.84)
12+
static var themeAccent: Color {
13+
ThemeManager.shared.selectedAccent.accentColor
14+
}
15+
1516
static let themeBackground = Color(.systemGroupedBackground)
1617
static let themeCard = Color(.systemBackground)
1718
}
1819

1920
enum Theme {
20-
static let cornerRadius: CGFloat = 12
21-
static let shadowRadius: CGFloat = 5
21+
static let cornerRadius: CGFloat = 16
22+
static let shadowRadius: CGFloat = 8
23+
static let animationDuration: Double = 0.3
24+
static let springDamping: Double = 0.7
25+
static let staggerDelay: Double = 0.08
2226
}

CycleOne/Core/Extensions/View+Extensions.swift

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extension View {
1010
self
1111
.padding()
1212
.background(Color(.secondarySystemBackground))
13-
.cornerRadius(12)
13+
.cornerRadius(Theme.cornerRadius)
1414
}
1515

1616
func sectionHeaderStyle() -> some View {
@@ -20,4 +20,62 @@ extension View {
2020
.foregroundColor(.secondary)
2121
.textCase(.uppercase)
2222
}
23+
24+
func premiumCard() -> some View {
25+
self
26+
.padding()
27+
.background(
28+
RoundedRectangle(cornerRadius: Theme.cornerRadius)
29+
.fill(Color(.secondarySystemBackground))
30+
.shadow(
31+
color: Color.themeAccent.opacity(0.08),
32+
radius: Theme.shadowRadius,
33+
x: 0, y: 4
34+
)
35+
)
36+
}
37+
38+
func fadeSlideIn(delay: Double = 0) -> some View {
39+
modifier(FadeSlideModifier(delay: delay))
40+
}
41+
42+
func gentlePulse() -> some View {
43+
modifier(GentlePulseModifier())
44+
}
45+
}
46+
47+
struct FadeSlideModifier: ViewModifier {
48+
let delay: Double
49+
@State private var isVisible = false
50+
51+
func body(content: Content) -> some View {
52+
content
53+
.opacity(isVisible ? 1 : 0)
54+
.offset(y: isVisible ? 0 : 12)
55+
.onAppear {
56+
withAnimation(
57+
.spring(response: 0.5, dampingFraction: 0.8)
58+
.delay(delay)
59+
) {
60+
isVisible = true
61+
}
62+
}
63+
}
64+
}
65+
66+
struct GentlePulseModifier: ViewModifier {
67+
@State private var isPulsing = false
68+
69+
func body(content: Content) -> some View {
70+
content
71+
.scaleEffect(isPulsing ? 1.03 : 1.0)
72+
.onAppear {
73+
withAnimation(
74+
.easeInOut(duration: 2.0)
75+
.repeatForever(autoreverses: true)
76+
) {
77+
isPulsing = true
78+
}
79+
}
80+
}
2381
}

CycleOne/Core/Models/CycleEnums.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55

66
import Foundation
7+
import SwiftUI
78

89
enum FlowLevel: Int16, CaseIterable {
910
case none = 0
@@ -19,6 +20,15 @@ enum FlowLevel: Int16, CaseIterable {
1920
case .heavy: "Heavy"
2021
}
2122
}
23+
24+
var icon: String {
25+
switch self {
26+
case .none: "drop"
27+
case .light: "drop"
28+
case .medium: "drop.fill"
29+
case .heavy: "drop.halffull"
30+
}
31+
}
2232
}
2333

2434
enum Mood: Int16, CaseIterable {
@@ -76,6 +86,24 @@ enum SymptomCategory: String, CaseIterable {
7686
case mood = "Mood & Mental"
7787
case digestion = "Digestion"
7888
case other = "Other"
89+
90+
var icon: String {
91+
switch self {
92+
case .physical: "figure.walk"
93+
case .mood: "brain.head.profile"
94+
case .digestion: "leaf.fill"
95+
case .other: "ellipsis.circle"
96+
}
97+
}
98+
99+
var color: Color {
100+
switch self {
101+
case .physical: .themePeriod
102+
case .mood: .themeFertile
103+
case .digestion: .orange
104+
case .other: .secondary
105+
}
106+
}
79107
}
80108

81109
struct SymptomType: Identifiable, Hashable {
@@ -84,12 +112,21 @@ struct SymptomType: Identifiable, Hashable {
84112
let category: SymptomCategory
85113

86114
static let defaults: [SymptomType] = [
115+
// Physical
87116
SymptomType(id: "cramps", name: "Cramps", category: .physical),
88117
SymptomType(id: "bloating", name: "Bloating", category: .physical),
89118
SymptomType(id: "headache", name: "Headache", category: .physical),
90119
SymptomType(id: "acne", name: "Acne", category: .physical),
91120
SymptomType(id: "breast_tenderness", name: "Breast Tenderness", category: .physical),
121+
SymptomType(id: "back_pain", name: "Back Pain", category: .physical),
122+
SymptomType(id: "dizziness", name: "Dizziness", category: .physical),
123+
// Digestion
92124
SymptomType(id: "nausea", name: "Nausea", category: .digestion),
125+
// Mood & Mental
126+
SymptomType(id: "mood_swings", name: "Mood Swings", category: .mood),
127+
SymptomType(id: "irritability", name: "Irritability", category: .mood),
128+
SymptomType(id: "fatigue", name: "Fatigue", category: .mood),
129+
// Other
93130
SymptomType(id: "cravings", name: "Cravings", category: .other),
94131
SymptomType(id: "insomnia", name: "Insomnia", category: .other),
95132
]

CycleOne/Core/Testing/TestDataSeeder.swift

Lines changed: 0 additions & 83 deletions
This file was deleted.

CycleOne/Core/Theming/ThemeManager.swift

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,72 @@ enum AppTheme: String, CaseIterable, Identifiable {
2424
}
2525
}
2626

27+
enum AccentTheme: String, CaseIterable, Identifiable {
28+
case rose = "Rose"
29+
case lavender = "Lavender"
30+
case ocean = "Ocean"
31+
case sage = "Sage"
32+
case sunset = "Sunset"
33+
34+
var id: String {
35+
rawValue
36+
}
37+
38+
var accentColor: Color {
39+
switch self {
40+
case .rose: Color(red: 1.0, green: 0.42, blue: 0.62)
41+
case .lavender: Color(red: 0.69, green: 0.53, blue: 0.87)
42+
case .ocean: Color(red: 0.20, green: 0.56, blue: 0.82)
43+
case .sage: Color(red: 0.42, green: 0.68, blue: 0.55)
44+
case .sunset: Color(red: 0.93, green: 0.49, blue: 0.33)
45+
}
46+
}
47+
48+
var icon: String {
49+
switch self {
50+
case .rose: "heart.fill"
51+
case .lavender: "sparkles"
52+
case .ocean: "drop.fill"
53+
case .sage: "leaf.fill"
54+
case .sunset: "sun.max.fill"
55+
}
56+
}
57+
}
58+
2759
final class ThemeManager: ObservableObject {
28-
@AppStorage("selected_app_theme") var selectedTheme: AppTheme = .system
60+
static let shared = ThemeManager()
61+
62+
@Published var selectedTheme: AppTheme {
63+
didSet {
64+
UserDefaults.standard.set(
65+
selectedTheme.rawValue,
66+
forKey: "selected_app_theme"
67+
)
68+
}
69+
}
70+
71+
@Published var selectedAccent: AccentTheme {
72+
didSet {
73+
UserDefaults.standard.set(
74+
selectedAccent.rawValue,
75+
forKey: "selected_accent_theme"
76+
)
77+
}
78+
}
79+
80+
init() {
81+
let themeRaw = UserDefaults.standard.string(
82+
forKey: "selected_app_theme"
83+
) ?? AppTheme.system.rawValue
84+
self.selectedTheme = AppTheme(rawValue: themeRaw) ?? .system
85+
86+
let accentRaw = UserDefaults.standard.string(
87+
forKey: "selected_accent_theme"
88+
) ?? AccentTheme.rose.rawValue
89+
self.selectedAccent = AccentTheme(rawValue: accentRaw) ?? .rose
90+
}
91+
92+
var accentColor: Color {
93+
selectedAccent.accentColor
94+
}
2995
}

0 commit comments

Comments
 (0)